Class: OpenC3::BurstProtocol

Inherits:
Protocol show all
Defined in:
lib/openc3/interfaces/protocols/burst_protocol.rb,
ext/openc3/ext/burst_protocol/burst_protocol.c

Overview

Reads all data available on the interface and creates a packet with that data.

Constant Summary collapse

DEFAULT_MAX_BUFFER_SIZE =

Maximum number of bytes which will be buffered while waiting for a complete packet. This bounds the memory a peer can cause the interface to allocate by declaring an enormous packet length (LENGTH, PREIDENTIFIED) or by never sending a terminator (TERMINATED). Deliberately much larger than any realistic packet so it does not affect existing configurations. Override with the OPENC3_PROTOCOL_MAX_BUFFER_SIZE environment variable.

100_000_000

Instance Attribute Summary

Attributes inherited from Protocol

#allow_empty_data, #extra, #interface

Instance Method Summary collapse

Methods inherited from Protocol

#connect_reset, #disconnect_reset, #post_write_interface, #protocol_cmd, #read_packet, #read_protocol_input_base, #read_protocol_output_base, #write_protocol_input_base, #write_protocol_output_base

Constructor Details

#initialize(discard_leading_bytes = 0, sync_pattern = nil, fill_fields = false, allow_empty_data = nil) ⇒ BurstProtocol

Returns a new instance of BurstProtocol.

Parameters:

  • discard_leading_bytes (Integer) (defaults to: 0)

    The number of bytes to discard from the binary data after reading. Note that this is often used to remove a sync pattern from the final packet data.

  • sync_pattern (String) (defaults to: nil)

    String representing a hex number ("0x1234") that will be searched for in the raw data. Bytes encountered before this pattern is found are discarded.

  • fill_fields (Boolean) (defaults to: false)

    Fill any required fields when writing packets

  • allow_empty_data (true/false/nil) (defaults to: nil)

    See Protocol#initialize



42
43
44
45
46
47
48
49
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 42

def initialize(discard_leading_bytes = 0, sync_pattern = nil, fill_fields = false, allow_empty_data = nil)
  super(allow_empty_data) # Calls reset()
  @discard_leading_bytes = discard_leading_bytes.to_i
  @sync_pattern = ConfigParser.handle_nil(sync_pattern)
  @sync_pattern = @sync_pattern.hex_to_byte_string if @sync_pattern
  @fill_fields = ConfigParser.handle_true_false(fill_fields)
  @max_buffer_size = Integer(ENV.fetch('OPENC3_PROTOCOL_MAX_BUFFER_SIZE', DEFAULT_MAX_BUFFER_SIZE))
end

Instance Method Details

#check_buffer_sizeObject

Raises if the accumulation buffer has grown past @max_buffer_size. Called from read_data by both the Ruby and the C implementations. Raising causes Interface#read to log the error and disconnect, which drops the offending peer and resets this protocol.



209
210
211
212
213
214
215
216
217
218
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 209

def check_buffer_size
  length = @data.length
  return if length <= @max_buffer_size

  # Release the buffer and clear any partially reduced state now rather than
  # waiting for the interface to disconnect and reset() us
  reset()
  raise "#{@interface ? @interface.name : ""}: Protocol buffer of #{length} bytes exceeds maximum of #{@max_buffer_size} bytes. " \
        "Increase OPENC3_PROTOCOL_MAX_BUFFER_SIZE."
end

#handle_sync_patternBoolean

Returns control code (nil, :STOP).

Returns:

  • (Boolean)

    control code (nil, :STOP)



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
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 119

def handle_sync_pattern
  if @sync_pattern and @sync_state == :SEARCHING
    loop do
      # Make sure we have some data to look for a sync word in
      return :STOP if @data.length < @sync_pattern.length

      # Find the beginning of the sync pattern
      sync_index = @data.index(@sync_pattern.getbyte(0).chr)
      if sync_index
        # Make sure we have enough data for the whole sync pattern past this index
        return :STOP if @data.length < (sync_index + @sync_pattern.length)

        # Check for the rest of the sync pattern
        found = true
        index = sync_index
        @sync_pattern.each_byte do |byte|
          if @data.getbyte(index) != byte
            found = false
            break
          end
          index += 1
        end

        if found
          if sync_index != 0
            log_discard(sync_index, true)
            # Delete Data Before Sync Pattern
            @data.replace(@data[sync_index..-1])
          end
          @sync_state = :FOUND
          return nil

        else # not found
          log_discard(sync_index + 1, false)
          # Delete Data Before and including first character of suspected sync Pattern
          @data.replace(@data[(sync_index + 1)..-1])
          next
        end # if found

      else # sync_index = nil
        log_discard(@data.length, false)
        @data.replace('')
        return :STOP
      end # unless sync_index.nil?
    end # end loop
  end # if @sync_pattern
  nil
end

#log_discard(length, found) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 220

def log_discard(length, found)
  Logger.error("#{@interface ? @interface.name : ""}: Sync #{'not ' unless found}found. Discarding #{length} bytes of data.")
  if @data.length >= 0
    Logger.error(sprintf("Starting: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n",
                         @data.length >= 1 ? @data.getbyte(0) : 0,
                         @data.length >= 2 ? @data.getbyte(1) : 0,
                         @data.length >= 3 ? @data.getbyte(2) : 0,
                         @data.length >= 4 ? @data.getbyte(3) : 0,
                         @data.length >= 5 ? @data.getbyte(4) : 0,
                         @data.length >= 6 ? @data.getbyte(5) : 0))
  end
end

#read_data(*args) ⇒ String|nil

Reads from the interface. It can look for a sync pattern before creating a Packet. It can discard a set number of bytes at the beginning before creating the Packet.

Note: On the first call to this from any interface read(), data will contain a blank string. Blank string is an opportunity for protocols to return any queued up packets. If they have no queued up packets, they should pass the blank string down to chained protocols giving them the same opportunity.

Returns:

  • (String|nil)

    Data for a packet consisting of the bytes read



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
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 70

def read_data(data, extra = nil)
  @data << data
  check_buffer_size()
  @extra = extra unless (data.length == 0 and extra.nil?) # Maintain extra from last read read_data

  while true
    control = handle_sync_pattern()
    return control if control and data.length > 0 # Only return here if not blank string test

    # Reduce the data to a single packet
    packet_data, extra = reduce_to_single_packet()
    if packet_data == :RESYNC
      @sync_state = :SEARCHING
      next if data.length > 0 # Only immediately resync if not blank string test
    end

    # Potentially allow blank string to be sent to other protocols if no packet is ready in this one
    if Symbol === packet_data
      if (data.length <= 0) and packet_data != :DISCONNECT
        # On blank string test, return blank string (unless we had a packet or need disconnect)
        # The base class handles the special case of returning STOP if on the last protocol in the
        # chain
        return super(data, extra)
      else
        return packet_data, extra # Return any control code if not on blank string test
      end
    end

    @sync_state = :SEARCHING

    # Discard leading bytes if necessary
    packet_data.replace(packet_data[@discard_leading_bytes..-1]) if @discard_leading_bytes > 0
    return packet_data, extra
  end
end

#read_detailsObject



242
243
244
245
246
247
248
249
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 242

def read_details
  result = super()
  result['discard_leading_bytes'] = @discard_leading_bytes
  result['sync_pattern'] = @sync_pattern.inspect
  result['fill_fields'] = @fill_fields
  result['max_buffer_size'] = @max_buffer_size
  return result
end

#reduce_to_single_packetObject



269
270
271
272
273
274
275
276
277
278
279
# File 'ext/openc3/ext/burst_protocol/burst_protocol.c', line 269

def reduce_to_single_packet
  if @data.length <= 0
    # Need some data
    return :STOP
  end

  # Reduce to packet data and clear data for next packet
  packet_data = @data.clone
  @data.replace('')
  return packet_data, @extra
end

#resetObject



51
52
53
54
55
56
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 51

def reset
  super()
  @data = ''
  @data.force_encoding('ASCII-8BIT')
  @sync_state = :SEARCHING
end

#write_data(data, extra = nil) ⇒ String

Called to perform modifications on write data before sending it to the interface

Parameters:

  • data (String)

    Raw packet data

Returns:

  • (String)

    Potentially modified packet data



190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 190

def write_data(data, extra = nil)
  # If we're filling the sync pattern and discarding the leading bytes
  # during a read then we need to put them back during a write.
  # If we're discarding the bytes then by definition they can't be part
  # of the packet so we just modify the data.
  if @fill_fields && @discard_leading_bytes > 0
    data = ("\x00" * @discard_leading_bytes) << data
    if @sync_pattern
      BinaryAccessor.write(@sync_pattern, 0, @sync_pattern.length * 8, :BLOCK,
                           data, :BIG_ENDIAN, :ERROR)
    end
  end
  return super(data, extra)
end

#write_detailsObject



233
234
235
236
237
238
239
240
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 233

def write_details
  result = super()
  result['discard_leading_bytes'] = @discard_leading_bytes
  result['sync_pattern'] = @sync_pattern.inspect
  result['fill_fields'] = @fill_fields
  result['max_buffer_size'] = @max_buffer_size
  return result
end

#write_packet(packet) ⇒ Packet

Called to perform modifications on a command packet before it is sent

Parameters:

  • packet (Packet)

    Original packet

Returns:

  • (Packet)

    Potentially modified packet



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/openc3/interfaces/protocols/burst_protocol.rb', line 174

def write_packet(packet)
  # If we're filling the sync pattern and the sync pattern is part of the
  # packet (since we're not discarding any leading bytes) then we have to
  # fill the sync pattern in the actual packet so do it here.
  if @fill_fields && @sync_pattern && @discard_leading_bytes == 0
    # Directly write the packet buffer and fill in the sync pattern
    BinaryAccessor.write(@sync_pattern, 0, @sync_pattern.length * 8, :BLOCK,
                         packet.buffer(false), :BIG_ENDIAN, :ERROR)
  end
  packet
end