-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tmdl): add TM frame support for data link
- Loading branch information
Showing
2 changed files
with
224 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package tmdl | ||
|
||
import ( | ||
"encoding/binary" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
// TMTransferFrame represents a CCSDS TM Space Data Link Protocol Transfer Frame. | ||
type TMTransferFrame struct { | ||
VersionNumber uint8 // 2 bits | ||
SpacecraftID uint16 // 10 bits | ||
VirtualChannelID uint8 // 6 bits | ||
FrameLength uint16 // Length of the frame | ||
FrameSecondaryHeader []byte // Optional secondary header | ||
DataField []byte // Main telemetry data | ||
OperationalControl []byte // 4-byte OCF (if used) | ||
FrameErrorControl uint16 // 16-bit CRC (Error Control) | ||
} | ||
|
||
// NewTMTransferFrame initializes a new TM Transfer Frame. | ||
func NewTMTransferFrame(scid uint16, vcid uint8, data []byte, secondaryHeader []byte, ocf []byte) (*TMTransferFrame, error) { | ||
if len(data) > 65535 { | ||
return nil, errors.New("data field exceeds maximum frame length") | ||
} | ||
|
||
frame := &TMTransferFrame{ | ||
VersionNumber: 0b01, // Default CCSDS TM version | ||
SpacecraftID: scid & 0x03FF, // Mask to 10 bits | ||
VirtualChannelID: vcid & 0x3F, // Mask to 6 bits | ||
FrameLength: uint16(5 + len(secondaryHeader) + len(data) + len(ocf) + 2), // Total frame length including headers and CRC | ||
FrameSecondaryHeader: secondaryHeader, | ||
DataField: data, | ||
OperationalControl: ocf, | ||
} | ||
|
||
// Compute Frame Error Control (CRC-16) | ||
frame.FrameErrorControl = ComputeCRC(frame.EncodeWithoutFEC()) | ||
Check failure on line 38 in pkg/tmdl/frame.go
|
||
|
||
return frame, nil | ||
} | ||
|
||
// Encode converts the TM Transfer Frame to a byte slice. | ||
func (tf *TMTransferFrame) Encode() []byte { | ||
frameData := tf.EncodeWithoutFEC() | ||
|
||
// Append CRC-16 | ||
crcBytes := make([]byte, 2) | ||
binary.BigEndian.PutUint16(crcBytes, tf.FrameErrorControl) | ||
return append(frameData, crcBytes...) | ||
} | ||
|
||
// EncodeWithoutFEC converts the frame to bytes excluding the CRC field. | ||
func (tf *TMTransferFrame) EncodeWithoutFEC() []byte { | ||
header := make([]byte, 5) | ||
|
||
// First 5 bytes: TFVN, SCID, VCID, Length | ||
header[0] = (tf.VersionNumber << 6) | byte(tf.SpacecraftID>>8) | ||
header[1] = byte(tf.SpacecraftID & 0xFF) | ||
header[2] = tf.VirtualChannelID | ||
binary.BigEndian.PutUint16(header[3:], tf.FrameLength) | ||
|
||
// Assemble full frame | ||
frameData := append(header, tf.FrameSecondaryHeader...) | ||
frameData = append(frameData, tf.DataField...) | ||
frameData = append(frameData, tf.OperationalControl...) | ||
|
||
return frameData | ||
} | ||
|
||
// DecodeTMTransferFrame parses a byte slice into a TM Transfer Frame. | ||
func DecodeTMTransferFrame(data []byte) (*TMTransferFrame, error) { | ||
if len(data) < 7 { | ||
return nil, errors.New("frame too short to be a valid TM Transfer Frame") | ||
} | ||
|
||
// Extract Version Number, SCID, and VCID | ||
version := (data[0] >> 6) & 0x03 | ||
scid := (uint16(data[0]&0x03) << 8) | uint16(data[1]) | ||
vcid := data[2] | ||
|
||
// Extract Frame Length | ||
frameLength := binary.BigEndian.Uint16(data[3:5]) | ||
|
||
// Check if the received frame length matches the actual data length | ||
if int(frameLength) != len(data) { | ||
return nil, fmt.Errorf("frame length mismatch: expected %d, got %d", frameLength, len(data)) | ||
} | ||
|
||
// Compute and verify CRC-16 | ||
receivedCRC := binary.BigEndian.Uint16(data[len(data)-2:]) | ||
computedCRC := ComputeCRC(data[:len(data)-2]) | ||
Check failure on line 92 in pkg/tmdl/frame.go
|
||
if receivedCRC != computedCRC { | ||
return nil, fmt.Errorf("CRC mismatch: expected %04X, got %04X", receivedCRC, computedCRC) | ||
} | ||
|
||
// Extract Data Field | ||
dataStart := 5 | ||
dataEnd := len(data) - 2 | ||
frameSecondaryHeader := []byte{} | ||
operationalControl := []byte{} | ||
|
||
// Check if Secondary Header exists (Mission-dependent) | ||
if dataStart < dataEnd { | ||
frameSecondaryHeader = data[dataStart : dataStart+2] // Assuming a 2-byte header | ||
dataStart += 2 | ||
} | ||
|
||
// Extract Operational Control Field (OCF) if present | ||
if dataEnd-dataStart >= 4 { | ||
operationalControl = data[dataEnd-4 : dataEnd] | ||
dataEnd -= 4 | ||
} | ||
|
||
// Extract the main Data Field | ||
dataField := data[dataStart:dataEnd] | ||
|
||
// Construct the TMTransferFrame object | ||
frame := &TMTransferFrame{ | ||
VersionNumber: version, | ||
SpacecraftID: scid, | ||
VirtualChannelID: vcid, | ||
FrameLength: frameLength, | ||
FrameSecondaryHeader: frameSecondaryHeader, | ||
DataField: dataField, | ||
OperationalControl: operationalControl, | ||
FrameErrorControl: receivedCRC, | ||
} | ||
|
||
return frame, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package tmdl_test | ||
|
||
import ( | ||
"github.com/ravisuhag/astro/pkg/tmdl" | ||
"testing" | ||
) | ||
|
||
func TestNewTMTransferFrame(t *testing.T) { | ||
scid := uint16(0x3FF) | ||
vcid := uint8(0x3F) | ||
data := []byte{0x01, 0x02, 0x03, 0x04} | ||
secondaryHeader := []byte{0x05, 0x06} | ||
ocf := []byte{0x07, 0x08, 0x09, 0x0A} | ||
|
||
frame, err := tmdl.NewTMTransferFrame(scid, vcid, data, secondaryHeader, ocf) | ||
if err != nil { | ||
t.Fatalf("Expected no error, got %v", err) | ||
} | ||
|
||
if frame.SpacecraftID != scid&0x03FF { | ||
t.Errorf("Expected SpacecraftID %v, got %v", scid&0x03FF, frame.SpacecraftID) | ||
} | ||
|
||
if frame.VirtualChannelID != vcid&0x3F { | ||
t.Errorf("Expected VirtualChannelID %v, got %v", vcid&0x3F, frame.VirtualChannelID) | ||
} | ||
|
||
expectedLength := uint16(5 + len(secondaryHeader) + len(data) + len(ocf) + 2) | ||
if frame.FrameLength != expectedLength { | ||
t.Errorf("Expected FrameLength %v, got %v", expectedLength, frame.FrameLength) | ||
} | ||
} | ||
|
||
func TestTMTransferFrame_Encode(t *testing.T) { | ||
scid := uint16(0x3FF) | ||
vcid := uint8(0x3F) | ||
data := []byte{0x01, 0x02, 0x03, 0x04} | ||
secondaryHeader := []byte{0x05, 0x06} | ||
ocf := []byte{0x07, 0x08, 0x09, 0x0A} | ||
|
||
frame, err := tmdl.NewTMTransferFrame(scid, vcid, data, secondaryHeader, ocf) | ||
if err != nil { | ||
t.Fatalf("Expected no error, got %v", err) | ||
} | ||
|
||
bytes := frame.Encode() | ||
if len(bytes) != int(frame.FrameLength) { | ||
t.Errorf("Expected byte slice length %v, got %v", frame.FrameLength, len(bytes)) | ||
} | ||
} | ||
|
||
func TestDecodeTMTransferFrame(t *testing.T) { | ||
scid := uint16(0x3FF) | ||
vcid := uint8(0x3F) | ||
data := []byte{0x01, 0x02, 0x03, 0x04} | ||
secondaryHeader := []byte{0x05, 0x06} | ||
ocf := []byte{0x07, 0x08, 0x09, 0x0A} | ||
|
||
frame, err := tmdl.NewTMTransferFrame(scid, vcid, data, secondaryHeader, ocf) | ||
if err != nil { | ||
t.Fatalf("Expected no error, got %v", err) | ||
} | ||
|
||
bytes := frame.Encode() | ||
decodedFrame, err := tmdl.DecodeTMTransferFrame(bytes) | ||
if err != nil { | ||
t.Fatalf("Expected no error, got %v", err) | ||
} | ||
|
||
if decodedFrame.SpacecraftID != frame.SpacecraftID { | ||
t.Errorf("Expected SpacecraftID %v, got %v", frame.SpacecraftID, decodedFrame.SpacecraftID) | ||
} | ||
|
||
if decodedFrame.VirtualChannelID != frame.VirtualChannelID { | ||
t.Errorf("Expected VirtualChannelID %v, got %v", frame.VirtualChannelID, decodedFrame.VirtualChannelID) | ||
} | ||
|
||
if decodedFrame.FrameLength != frame.FrameLength { | ||
t.Errorf("Expected FrameLength %v, got %v", frame.FrameLength, decodedFrame.FrameLength) | ||
} | ||
|
||
if string(decodedFrame.DataField) != string(frame.DataField) { | ||
t.Errorf("Expected DataField %v, got %v", frame.DataField, decodedFrame.DataField) | ||
} | ||
|
||
if string(decodedFrame.FrameSecondaryHeader) != string(frame.FrameSecondaryHeader) { | ||
t.Errorf("Expected FrameSecondaryHeader %v, got %v", frame.FrameSecondaryHeader, decodedFrame.FrameSecondaryHeader) | ||
} | ||
|
||
if string(decodedFrame.OperationalControl) != string(frame.OperationalControl) { | ||
t.Errorf("Expected OperationalControl %v, got %v", frame.OperationalControl, decodedFrame.OperationalControl) | ||
} | ||
} |