class Cosmos::CcsdsTransferFrames::CcsdsTransferFrameProtocol

Given a stream of ccsds transfer frames, extract ccsds space packets based on the first header pointer and packet lengths.

Only read is supported.

Constants

FIRST_HEADER_POINTER_BITS
FIRST_HEADER_POINTER_BIT_OFFSET
FRAME_ERROR_CONTROL_FIELD_LENGTH
FRAME_OPERATIONAL_CONTROL_FIELD_LENGTH
FRAME_PRIMARY_HEADER_LENGTH
FRAME_VIRTUAL_CHANNEL_BITS
FRAME_VIRTUAL_CHANNEL_BIT_OFFSET
IDLE_FRAME_FIRST_HEADER_POINTER
IDLE_PACKET_APID
NO_PACKET_START_FIRST_HEADER_POINTER
SPACE_PACKET_APID_BITS
SPACE_PACKET_APID_BIT_OFFSET
SPACE_PACKET_HEADER_LENGTH
SPACE_PACKET_LENGTH_BITS
SPACE_PACKET_LENGTH_BIT_OFFSET
VIRTUAL_CHANNEL_COUNT
VirtualChannel

Public Class Methods

new( transfer_frame_length, transfer_frame_secondary_header_length, transfer_frame_has_operational_control_field, transfer_frame_has_frame_error_control_field, prefix_packets = false, include_idle_packets = false, allow_empty_data = nil) click to toggle source

@param transfer_frame_length [Integer] Length of transfer frame in bytes @param transfer_frame_secondary_header_length [Integer] Length of

transfer frame secondary header in bytes

@param transfer_frame_has_operational_control_field [Boolean] Flag

indicating if the transfer frame operational control field is
present or not

@param transfer_frame_has_frame_error_control_field [Boolean] Flag

indicating if the transfer frame error control field is present or
not

@param prefix_packets [Boolean] Flag indicating if each space packet should

be prefixed with the transfer frame headers from the frame where
it started.

@param include_idle_packets [Boolean] Flag indicating if idle packets

should be included or discarded.

@param allow_empty_data [true/false/nil] See Protocol#initialize

Calls superclass method
# File lib/cosmos/ccsds_transfer_frames/ccsds_transfer_frame_protocol.rb, line 63
def initialize(
  transfer_frame_length,
  transfer_frame_secondary_header_length,
  transfer_frame_has_operational_control_field,
  transfer_frame_has_frame_error_control_field,
  prefix_packets = false,
  include_idle_packets = false,
  allow_empty_data = nil)
  super(allow_empty_data)

  @frame_length = Integer(transfer_frame_length)

  @frame_headers_length = FRAME_PRIMARY_HEADER_LENGTH + Integer(transfer_frame_secondary_header_length)

  @frame_trailer_length = 0
  has_ocf = ConfigParser.handle_true_false(transfer_frame_has_operational_control_field)
  @frame_trailer_length += FRAME_OPERATIONAL_CONTROL_FIELD_LENGTH if has_ocf
  has_fecf = ConfigParser.handle_true_false(transfer_frame_has_frame_error_control_field)
  @frame_trailer_length += FRAME_ERROR_CONTROL_FIELD_LENGTH if has_fecf

  @frame_data_field_length = @frame_length - @frame_headers_length - @frame_trailer_length

  @packet_prefix_length = 0
  @prefix_packets = ConfigParser.handle_true_false(prefix_packets)
  @packet_prefix_length += @frame_headers_length if @prefix_packets

  @include_idle_packets = ConfigParser.handle_true_false(include_idle_packets)
end

Public Instance Methods

read_data(data) click to toggle source
Calls superclass method
# File lib/cosmos/ccsds_transfer_frames/ccsds_transfer_frame_protocol.rb, line 98
def read_data(data)
  @data << data

  if (@data.length >= @frame_length)
    frame = @data.slice!(0, @frame_length)
    process_frame(frame)
  end

  packet_data = get_packet()

  # Potentially allow blank string to be sent to other protocols if no
  # packet is ready in this one
  if (Symbol === packet_data && packet_data == :STOP && data.length <= 0)
    return super(data)
  end

  return packet_data
end
reset() click to toggle source
Calls superclass method
# File lib/cosmos/ccsds_transfer_frames/ccsds_transfer_frame_protocol.rb, line 92
def reset
  super()
  @data = ''
  @virtual_channels = Array.new(VIRTUAL_CHANNEL_COUNT) { VirtualChannel.new }
end

Private Instance Methods

get_packet() click to toggle source

Get a packet from the virtual channel packet queues of stored packets from processed frames.

If idle packets are not included, extracted idle packets are discarded and extraction is retried until a non-idle packet is found or no more complete packets are left in any of the virtual channel packet queues.

@return [String] Packet data, if the queues contained at least one

complete packet.

@return [Symbol] :STOP, if the queues do not contain any complete

packets.
# File lib/cosmos/ccsds_transfer_frames/ccsds_transfer_frame_protocol.rb, line 136
def get_packet
  @virtual_channels.each do |vc|
    # avoid extracting incomplete packets
    while (vc.packet_queue.length >= 2 ||
        (vc.packet_queue.length == 1 &&
         vc.pending_incomplete_packet_bytes_left == 0))
      packet_data = vc.packet_queue.shift

      return packet_data if (@include_idle_packets ||
                             packet_data.length < SPACE_PACKET_HEADER_LENGTH)

      apid = BinaryAccessor.read(
        @packet_prefix_length * 8 + SPACE_PACKET_APID_BIT_OFFSET,
        SPACE_PACKET_APID_BITS,
        :UINT,
        packet_data,
        :BIG_ENDIAN)
      return packet_data unless (apid == IDLE_PACKET_APID)
    end
  end
  # If the packet queues contains any more whole packets they will be
  # handled in subsequent calls to this method. Cosmos will ensure that
  # read_data() is called until it returns :STOP, which allows us to
  # clear all whole packets.

  # no complete packet for any virtual channel
  return :STOP
end
handle_packet_continuation(virtual_channel, frame_data_field, first_header_pointer) click to toggle source

Handle packet continuation when processing a transfer frame.

First ensures that any incomplete packet has enough data for the packet header to determine its length and then tries to complete it.

If the first header pointer indicates that a packet starts in this frame, the frame_data_field parameter will be modified by removing everything before the first header pointer.

@param virtual_channel [Int] Transfer frame virtual channel. @param frame_data_field [String] Transfer frame data field. @param first_header_pointer [Int] First header pointer value.

# File lib/cosmos/ccsds_transfer_frames/ccsds_transfer_frame_protocol.rb, line 210
def handle_packet_continuation(virtual_channel, frame_data_field, first_header_pointer)
  vc = @virtual_channels[virtual_channel]

  if (vc.packet_queue.length == 0 ||
      vc.pending_incomplete_packet_bytes_left == 0)
    # no packet in queue to be continued

    return if (first_header_pointer == NO_PACKET_START_FIRST_HEADER_POINTER)

    frame_data_field.replace(frame_data_field[first_header_pointer..-1])
    return
  end

  packet_continuation = nil
  if (first_header_pointer == NO_PACKET_START_FIRST_HEADER_POINTER)
    packet_continuation = frame_data_field
  else
    packet_continuation = frame_data_field.slice!(0, first_header_pointer)
  end

  if (vc.packet_queue[-1].length < @packet_prefix_length + SPACE_PACKET_HEADER_LENGTH)
    # Pending incomplete packet does not yet heave header, try to
    # complete header and get length before processing further.
    rest_of_packet_header_length = vc.pending_incomplete_packet_bytes_left
    if (rest_of_packet_header_length > packet_continuation.length)
      # Not enough continuation to complete packet header, first header
      # pointer takes precedence and packet is cut short.
      vc.packet_queue[-1] << packet_continuation
      vc.pending_incomplete_packet_bytes_left = 0
      return
    end
    vc.packet_queue[-1] << packet_continuation.slice!(0, rest_of_packet_header_length)

    # actual length in ccsds space packet is stored value plus one
    space_packet_data_field_length = BinaryAccessor.read(
      @packet_prefix_length * 8 + SPACE_PACKET_LENGTH_BIT_OFFSET,
      SPACE_PACKET_LENGTH_BITS,
      :UINT,
      vc.packet_queue[-1],
      :BIG_ENDIAN) + 1
    vc.pending_incomplete_packet_bytes_left = space_packet_data_field_length
  end

  if (first_header_pointer == NO_PACKET_START_FIRST_HEADER_POINTER)
    # packet continues past this frame or ends exactly at end of this
    # frame according to first header pointer

    if (vc.pending_incomplete_packet_bytes_left < packet_continuation.length)
      # Packet length is inconsistent with first header pointer, since it
      # indicates that the packet ends before the end of this frame.
      #
      # Complete the packet based on the packet length and ignore the
      # rest of the data in the frame (will use first header pointer to
      # re-sync with start of next packet in a later frame).
      vc.packet_queue[-1] << packet_continuation[0, vc.pending_incomplete_packet_bytes_left]
      vc.pending_incomplete_packet_bytes_left = 0
      return
    end

    # First header pointer and packet length are consistent, append whole frame.
    vc.packet_queue[-1] << packet_continuation
    vc.pending_incomplete_packet_bytes_left -= frame_data_field.length
    return
  end

  # packet ends before the end of this frame according to first header
  # pointer

  if (vc.pending_incomplete_packet_bytes_left < packet_continuation.length)
    # Packet length is inconsistent with first header pointer, since it
    # indicates that the packet ends before the first header pointer.
    #
    # Complete the packet based on the packet length and ignore the data
    # between the packet end and the first header pointer.
    packet_continuation.replace(packet_continuation[0, vc.pending_incomplete_packet_bytes_left])
  end

  # If the packet length is too long compared to the first header
  # pointer, the first header pointer takes precedence and the packet is
  # cut short.

  vc.packet_queue[-1] << packet_continuation
  vc.pending_incomplete_packet_bytes_left = 0
end
process_frame(frame) click to toggle source

Extract packets from a transfer frame and store them in the packet queue.

First handles packet continuation of any incomplete packet and then handles the rest of the packets in the frame.

@param frame [String] Transfer frame data.

# File lib/cosmos/ccsds_transfer_frames/ccsds_transfer_frame_protocol.rb, line 171
def process_frame(frame)
  first_header_pointer = BinaryAccessor.read(
    FIRST_HEADER_POINTER_BIT_OFFSET,
    FIRST_HEADER_POINTER_BITS,
    :UINT,
    frame,
    :BIG_ENDIAN)

  return if (first_header_pointer == IDLE_FRAME_FIRST_HEADER_POINTER)

  virtual_channel = BinaryAccessor.read(
    FRAME_VIRTUAL_CHANNEL_BIT_OFFSET,
    FRAME_VIRTUAL_CHANNEL_BITS,
    :UINT,
    frame,
    :BIG_ENDIAN)

  frame_data_field = frame[@frame_headers_length, @frame_data_field_length]

  handle_packet_continuation(virtual_channel, frame_data_field, first_header_pointer)

  return if (first_header_pointer == NO_PACKET_START_FIRST_HEADER_POINTER)

  frame_headers = frame[0, @frame_headers_length]
  store_packets(virtual_channel, frame_headers, frame_data_field)
end
store_packets(virtual_channel, frame_headers, frame_data_field) click to toggle source

Extract all packets from the remaining frame data field, and store them in the packet queue.

It is assumed that packet continuation data from any previously unfinished packets has been removed from the frame data field prior, and hence that the given remaining frame data field starts at a space packet header.

Handles both complete packets and unfinished packets which will be finished in a later frame via handle_packet_continuation().

@param virtual_channel [Int] Transfer frame virtual channel. @param frame_headers [String] Transfer frame headers, only used if prefixing packets. @param frame_data_field [String] (Remaining) transfer frame data field.

# File lib/cosmos/ccsds_transfer_frames/ccsds_transfer_frame_protocol.rb, line 309
def store_packets(virtual_channel, frame_headers, frame_data_field)
  vc = @virtual_channels[virtual_channel]
  while (frame_data_field.length > 0) do
    if (frame_data_field.length < SPACE_PACKET_HEADER_LENGTH)
      if (@prefix_packets)
        vc.packet_queue << (frame_headers.clone << frame_data_field)
      else
        vc.packet_queue << frame_data_field
      end
      vc.pending_incomplete_packet_bytes_left = SPACE_PACKET_HEADER_LENGTH - frame_data_field.length
      return
    end

    # actual length in ccsds space packet is stored value plus one
    space_packet_data_field_length = BinaryAccessor.read(
      SPACE_PACKET_LENGTH_BIT_OFFSET,
      SPACE_PACKET_LENGTH_BITS,
      :UINT,
      frame_data_field,
      :BIG_ENDIAN) + 1
    space_packet_length = SPACE_PACKET_HEADER_LENGTH + space_packet_data_field_length

    if (space_packet_length > frame_data_field.length)
      if (@prefix_packets)
        vc.packet_queue << (frame_headers.clone << frame_data_field)
      else
        vc.packet_queue << frame_data_field
      end
      vc.pending_incomplete_packet_bytes_left = space_packet_length - frame_data_field.length
      return
    end

    if (@prefix_packets)
      vc.packet_queue << (frame_headers.clone << frame_data_field.slice!(0, space_packet_length))
    else
      vc.packet_queue << frame_data_field.slice!(0, space_packet_length)
    end
  end
end