Skip to content

Commit

Permalink
feat(tmdl): add TM frame support for data link
Browse files Browse the repository at this point in the history
  • Loading branch information
ravisuhag committed Feb 12, 2025
1 parent f9e2906 commit 4dc2ecc
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 0 deletions.
131 changes: 131 additions & 0 deletions pkg/tmdl/frame.go
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

View workflow job for this annotation

GitHub Actions / checks

undefined: ComputeCRC

Check failure on line 38 in pkg/tmdl/frame.go

View workflow job for this annotation

GitHub Actions / checks

undefined: ComputeCRC

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

View workflow job for this annotation

GitHub Actions / checks

undefined: ComputeCRC (typecheck)

Check failure on line 92 in pkg/tmdl/frame.go

View workflow job for this annotation

GitHub Actions / checks

undefined: ComputeCRC) (typecheck)
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
}
93 changes: 93 additions & 0 deletions pkg/tmdl/frame_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package tmdl_test

import (
"github.com/ravisuhag/astro/pkg/tmdl"

Check failure on line 4 in pkg/tmdl/frame_test.go

View workflow job for this annotation

GitHub Actions / checks

could not import github.com/ravisuhag/astro/pkg/tmdl (-: # 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)
}
}

0 comments on commit 4dc2ecc

Please sign in to comment.