/
fixed_protocol.rb
172 lines (153 loc) · 6.07 KB
/
fixed_protocol.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# encoding: ascii-8bit
# Copyright 2022 Ball Aerospace & Technologies Corp.
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# Modified by OpenC3, Inc.
# All changes Copyright 2022, OpenC3, Inc.
# All Rights Reserved
#
# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.
require 'openc3/config/config_parser'
require 'openc3/interfaces/protocols/burst_protocol'
module OpenC3
# Delineates packets by identifying them and then
# reading out their entire fixed length. Packets lengths can vary but
# they must all be fixed.
class FixedProtocol < BurstProtocol
# @param min_id_size [Integer] The minimum amount of data needed to
# identify a packet.
# @param discard_leading_bytes (see BurstProtocol#initialize)
# @param sync_pattern (see BurstProtocol#initialize)
# @param telemetry [Boolean] Whether the interface is returning
# telemetry (true) or commands (false)
# @param fill_fields (see BurstProtocol#initialize)
# @param unknown_raise Whether to raise an exception on an unknown packet
# @param allow_empty_data [true/false/nil] See Protocol#initialize
def initialize(
min_id_size,
discard_leading_bytes = 0,
sync_pattern = nil,
telemetry = true,
fill_fields = false,
unknown_raise = false,
allow_empty_data = nil
)
super(discard_leading_bytes, sync_pattern, fill_fields, allow_empty_data)
@min_id_size = Integer(min_id_size)
@telemetry = telemetry
@unknown_raise = ConfigParser.handle_true_false(unknown_raise)
@received_time = nil
@target_name = nil
@packet_name = nil
end
# Set the received_time, target_name and packet_name which we recorded when
# we identified this packet. The server will also do this but since we know
# the information here, we perform this optimization.
def read_packet(packet)
packet.received_time = @received_time
packet.target_name = @target_name
packet.packet_name = @packet_name
return packet
end
protected
# Identifies an unknown buffer of data as a Packet. The raw data is
# returned but the packet that matched is recorded so it can be set in the
# read_packet callback.
#
# @return [String|Symbol] The identified packet data or :STOP if more data
# is required to build a packet
def identify_and_finish_packet
packet_data = nil
identified_packet = nil
if @telemetry
target_names = @interface.tlm_target_names
else
target_names = @interface.cmd_target_names
end
target_names.each do |target_name|
target_packets = nil
unique_id_mode = false
begin
if @telemetry
target_packets = System.telemetry.packets(target_name)
target = System.targets[target_name]
unique_id_mode = target.tlm_unique_id_mode if target
else
target_packets = System.commands.packets(target_name)
target = System.targets[target_name]
unique_id_mode = target.cmd_unique_id_mode if target
end
rescue RuntimeError
# No commands/telemetry for this target
next
end
if unique_id_mode
target_packets.each do |_packet_name, packet|
if packet.identify?(@data[@discard_leading_bytes..-1])
identified_packet = packet
break
end
end
else
# Do a hash lookup to quickly identify the packet
if target_packets.length > 0
packet = target_packets.first[1]
key = packet.read_id_values(@data[@discard_leading_bytes..-1])
if @telemetry
hash = System.telemetry.config.tlm_id_value_hash[target_name]
else
hash = System.commands.config.cmd_id_value_hash[target_name]
end
identified_packet = hash[key]
identified_packet = hash['CATCHALL'.freeze] unless identified_packet
end
end
if identified_packet
if identified_packet.defined_length + @discard_leading_bytes > @data.length
# Check if need more data to finish packet
return :STOP
end
# Set some variables so we can update the packet in
# read_packet
@received_time = Time.now.sys
@target_name = identified_packet.target_name
@packet_name = identified_packet.packet_name
# Get the data from this packet
# Previous implementation looked like the following:
# packet_data = @data.slice!(0, identified_packet.defined_length + @discard_leading_bytes)
# But slice! is 6x slower at small packets (1024)
# and 1000x slower at large packets (1Mb)
# Run test/benchmarks/string_mod_benchmark.rb for details
# Triple dot range because it's effectively a length calculation and we start with 0
packet_data = @data[0...(identified_packet.defined_length + @discard_leading_bytes)]
@data = @data[(identified_packet.defined_length + @discard_leading_bytes)..-1]
break
end
end
unless identified_packet
raise "Unknown data received by FixedProtocol" if @unknown_raise
# Unknown packet? Just return all the current data
@received_time = nil
@target_name = nil
@packet_name = nil
packet_data = @data.clone
@data.replace('')
end
return packet_data, @extra
end
def reduce_to_single_packet
return :STOP if @data.length < @min_id_size
identify_and_finish_packet()
end
end
end