|
| 1 | +package tmdl |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/binary" |
| 5 | + "errors" |
| 6 | + "fmt" |
| 7 | +) |
| 8 | + |
| 9 | +// TMTransferFrame represents a CCSDS TM Space Data Link Protocol Transfer Frame. |
| 10 | +type TMTransferFrame struct { |
| 11 | + VersionNumber uint8 // 2 bits |
| 12 | + SpacecraftID uint16 // 10 bits |
| 13 | + VirtualChannelID uint8 // 6 bits |
| 14 | + FrameLength uint16 // Length of the frame |
| 15 | + FrameSecondaryHeader []byte // Optional secondary header |
| 16 | + DataField []byte // Main telemetry data |
| 17 | + OperationalControl []byte // 4-byte OCF (if used) |
| 18 | + FrameErrorControl uint16 // 16-bit CRC (Error Control) |
| 19 | +} |
| 20 | + |
| 21 | +// NewTMTransferFrame initializes a new TM Transfer Frame. |
| 22 | +func NewTMTransferFrame(scid uint16, vcid uint8, data []byte, secondaryHeader []byte, ocf []byte) (*TMTransferFrame, error) { |
| 23 | + if len(data) > 65535 { |
| 24 | + return nil, errors.New("data field exceeds maximum frame length") |
| 25 | + } |
| 26 | + |
| 27 | + frame := &TMTransferFrame{ |
| 28 | + VersionNumber: 0b01, // Default CCSDS TM version |
| 29 | + SpacecraftID: scid & 0x03FF, // Mask to 10 bits |
| 30 | + VirtualChannelID: vcid & 0x3F, // Mask to 6 bits |
| 31 | + FrameLength: uint16(5 + len(secondaryHeader) + len(data) + len(ocf) + 2), // Total frame length including headers and CRC |
| 32 | + FrameSecondaryHeader: secondaryHeader, |
| 33 | + DataField: data, |
| 34 | + OperationalControl: ocf, |
| 35 | + } |
| 36 | + |
| 37 | + // Compute Frame Error Control (CRC-16) |
| 38 | + frame.FrameErrorControl = ComputeCRC(frame.EncodeWithoutFEC()) |
| 39 | + |
| 40 | + return frame, nil |
| 41 | +} |
| 42 | + |
| 43 | +// Encode converts the TM Transfer Frame to a byte slice. |
| 44 | +func (tf *TMTransferFrame) Encode() []byte { |
| 45 | + frameData := tf.EncodeWithoutFEC() |
| 46 | + |
| 47 | + // Append CRC-16 |
| 48 | + crcBytes := make([]byte, 2) |
| 49 | + binary.BigEndian.PutUint16(crcBytes, tf.FrameErrorControl) |
| 50 | + return append(frameData, crcBytes...) |
| 51 | +} |
| 52 | + |
| 53 | +// EncodeWithoutFEC converts the frame to bytes excluding the CRC field. |
| 54 | +func (tf *TMTransferFrame) EncodeWithoutFEC() []byte { |
| 55 | + header := make([]byte, 5) |
| 56 | + |
| 57 | + // First 5 bytes: TFVN, SCID, VCID, Length |
| 58 | + header[0] = (tf.VersionNumber << 6) | byte(tf.SpacecraftID>>8) |
| 59 | + header[1] = byte(tf.SpacecraftID & 0xFF) |
| 60 | + header[2] = tf.VirtualChannelID |
| 61 | + binary.BigEndian.PutUint16(header[3:], tf.FrameLength) |
| 62 | + |
| 63 | + // Assemble full frame |
| 64 | + frameData := append(header, tf.FrameSecondaryHeader...) |
| 65 | + frameData = append(frameData, tf.DataField...) |
| 66 | + frameData = append(frameData, tf.OperationalControl...) |
| 67 | + |
| 68 | + return frameData |
| 69 | +} |
| 70 | + |
| 71 | +// DecodeTMTransferFrame parses a byte slice into a TM Transfer Frame. |
| 72 | +func DecodeTMTransferFrame(data []byte) (*TMTransferFrame, error) { |
| 73 | + if len(data) < 7 { |
| 74 | + return nil, errors.New("frame too short to be a valid TM Transfer Frame") |
| 75 | + } |
| 76 | + |
| 77 | + // Extract Version Number, SCID, and VCID |
| 78 | + version := (data[0] >> 6) & 0x03 |
| 79 | + scid := (uint16(data[0]&0x03) << 8) | uint16(data[1]) |
| 80 | + vcid := data[2] |
| 81 | + |
| 82 | + // Extract Frame Length |
| 83 | + frameLength := binary.BigEndian.Uint16(data[3:5]) |
| 84 | + |
| 85 | + // Check if the received frame length matches the actual data length |
| 86 | + if int(frameLength) != len(data) { |
| 87 | + return nil, fmt.Errorf("frame length mismatch: expected %d, got %d", frameLength, len(data)) |
| 88 | + } |
| 89 | + |
| 90 | + // Compute and verify CRC-16 |
| 91 | + receivedCRC := binary.BigEndian.Uint16(data[len(data)-2:]) |
| 92 | + computedCRC := ComputeCRC(data[:len(data)-2]) |
| 93 | + if receivedCRC != computedCRC { |
| 94 | + return nil, fmt.Errorf("CRC mismatch: expected %04X, got %04X", receivedCRC, computedCRC) |
| 95 | + } |
| 96 | + |
| 97 | + // Extract Data Field |
| 98 | + dataStart := 5 |
| 99 | + dataEnd := len(data) - 2 |
| 100 | + frameSecondaryHeader := []byte{} |
| 101 | + operationalControl := []byte{} |
| 102 | + |
| 103 | + // Check if Secondary Header exists (Mission-dependent) |
| 104 | + if dataStart < dataEnd { |
| 105 | + frameSecondaryHeader = data[dataStart : dataStart+2] // Assuming a 2-byte header |
| 106 | + dataStart += 2 |
| 107 | + } |
| 108 | + |
| 109 | + // Extract Operational Control Field (OCF) if present |
| 110 | + if dataEnd-dataStart >= 4 { |
| 111 | + operationalControl = data[dataEnd-4 : dataEnd] |
| 112 | + dataEnd -= 4 |
| 113 | + } |
| 114 | + |
| 115 | + // Extract the main Data Field |
| 116 | + dataField := data[dataStart:dataEnd] |
| 117 | + |
| 118 | + // Construct the TMTransferFrame object |
| 119 | + frame := &TMTransferFrame{ |
| 120 | + VersionNumber: version, |
| 121 | + SpacecraftID: scid, |
| 122 | + VirtualChannelID: vcid, |
| 123 | + FrameLength: frameLength, |
| 124 | + FrameSecondaryHeader: frameSecondaryHeader, |
| 125 | + DataField: dataField, |
| 126 | + OperationalControl: operationalControl, |
| 127 | + FrameErrorControl: receivedCRC, |
| 128 | + } |
| 129 | + |
| 130 | + return frame, nil |
| 131 | +} |
0 commit comments