Initial commit

This commit is contained in:
Marcelo
2025-11-20 15:27:34 -06:00
commit cc72c9fc5d
3221 changed files with 737477 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
/// <reference types="node" />
/// <reference types="node" />
import { Transform, TransformCallback, TransformOptions } from 'stream';
import { SpacePacket, SpacePacketHeader } from './utils';
export { SpacePacket, SpacePacketHeader };
/** The optional configuration object, only needed if either of the two fields of the secondary header need their length defined */
export interface SpacePacketOptions extends Omit<TransformOptions, 'objectMode'> {
/** The length of the Time Code Field in octets, if present */
timeCodeFieldLength?: number;
/** The length of the Ancillary Data Field in octets, if present */
ancillaryDataFieldLength?: number;
}
/**
* A Transform stream that accepts a stream of octet data and converts it into an object
* representation of a CCSDS Space Packet. See https://public.ccsds.org/Pubs/133x0b2e1.pdf for a
* description of the Space Packet format.
*/
export declare class SpacePacketParser extends Transform {
timeCodeFieldLength: number;
ancillaryDataFieldLength: number;
dataBuffer: Buffer;
headerBuffer: Buffer;
dataLength: number;
expectingHeader: boolean;
dataSlice: number;
header?: SpacePacketHeader;
/**
* A Transform stream that accepts a stream of octet data and emits object representations of
* CCSDS Space Packets once a packet has been completely received.
* @param {Object} [options] Configuration options for the stream
* @param {Number} options.timeCodeFieldLength The length of the time code field within the data
* @param {Number} options.ancillaryDataFieldLength The length of the ancillary data field within the data
*/
constructor(options?: SpacePacketOptions);
/**
* Bundle the header, secondary header if present, and the data into a JavaScript object to emit.
* If more data has been received past the current packet, begin the process of parsing the next
* packet(s).
*/
pushCompletedPacket(): void;
/**
* Build the Stream's headerBuffer property from the received Buffer chunk; extract data from it
* if it's complete. If there's more to the chunk than just the header, initiate handling the
* packet data.
* @param chunk - Build the Stream's headerBuffer property from
*/
extractHeader(chunk: Buffer): void;
_transform(chunk: Buffer, encoding: BufferEncoding, cb: TransformCallback): void;
_flush(cb: TransformCallback): void;
}

View File

@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpacePacketParser = void 0;
const stream_1 = require("stream");
const utils_1 = require("./utils");
/**
* A Transform stream that accepts a stream of octet data and converts it into an object
* representation of a CCSDS Space Packet. See https://public.ccsds.org/Pubs/133x0b2e1.pdf for a
* description of the Space Packet format.
*/
class SpacePacketParser extends stream_1.Transform {
timeCodeFieldLength;
ancillaryDataFieldLength;
dataBuffer;
headerBuffer;
dataLength;
expectingHeader;
dataSlice;
header;
/**
* A Transform stream that accepts a stream of octet data and emits object representations of
* CCSDS Space Packets once a packet has been completely received.
* @param {Object} [options] Configuration options for the stream
* @param {Number} options.timeCodeFieldLength The length of the time code field within the data
* @param {Number} options.ancillaryDataFieldLength The length of the ancillary data field within the data
*/
constructor(options = {}) {
super({ ...options, objectMode: true });
// Set the constants for this Space Packet Connection; these will help us parse incoming data
// fields:
this.timeCodeFieldLength = options.timeCodeFieldLength || 0;
this.ancillaryDataFieldLength = options.ancillaryDataFieldLength || 0;
this.dataSlice = this.timeCodeFieldLength + this.ancillaryDataFieldLength;
// These are stateful based on the current packet being received:
this.dataBuffer = Buffer.alloc(0);
this.headerBuffer = Buffer.alloc(0);
this.dataLength = 0;
this.expectingHeader = true;
}
/**
* Bundle the header, secondary header if present, and the data into a JavaScript object to emit.
* If more data has been received past the current packet, begin the process of parsing the next
* packet(s).
*/
pushCompletedPacket() {
if (!this.header) {
throw new Error('Missing header');
}
const timeCode = Buffer.from(this.dataBuffer.slice(0, this.timeCodeFieldLength));
const ancillaryData = Buffer.from(this.dataBuffer.slice(this.timeCodeFieldLength, this.timeCodeFieldLength + this.ancillaryDataFieldLength));
const data = Buffer.from(this.dataBuffer.slice(this.dataSlice, this.dataLength));
const completedPacket = {
header: { ...this.header },
data: data.toString(),
};
if (timeCode.length > 0 || ancillaryData.length > 0) {
completedPacket.secondaryHeader = {};
if (timeCode.length) {
completedPacket.secondaryHeader.timeCode = timeCode.toString();
}
if (ancillaryData.length) {
completedPacket.secondaryHeader.ancillaryData = ancillaryData.toString();
}
}
this.push(completedPacket);
// If there is an overflow (i.e. we have more data than the packet we just pushed) begin parsing
// the next packet.
const nextChunk = Buffer.from(this.dataBuffer.slice(this.dataLength));
if (nextChunk.length >= utils_1.HEADER_LENGTH) {
this.extractHeader(nextChunk);
}
else {
this.headerBuffer = nextChunk;
this.dataBuffer = Buffer.alloc(0);
this.expectingHeader = true;
this.dataLength = 0;
this.header = undefined;
}
}
/**
* Build the Stream's headerBuffer property from the received Buffer chunk; extract data from it
* if it's complete. If there's more to the chunk than just the header, initiate handling the
* packet data.
* @param chunk - Build the Stream's headerBuffer property from
*/
extractHeader(chunk) {
const headerAsBuffer = Buffer.concat([this.headerBuffer, chunk]);
const startOfDataBuffer = headerAsBuffer.slice(utils_1.HEADER_LENGTH);
if (headerAsBuffer.length >= utils_1.HEADER_LENGTH) {
this.header = (0, utils_1.convertHeaderBufferToObj)(headerAsBuffer);
this.dataLength = this.header.dataLength;
this.headerBuffer = Buffer.alloc(0);
this.expectingHeader = false;
}
else {
this.headerBuffer = headerAsBuffer;
}
if (startOfDataBuffer.length > 0) {
this.dataBuffer = Buffer.from(startOfDataBuffer);
if (this.dataBuffer.length >= this.dataLength) {
this.pushCompletedPacket();
}
}
}
_transform(chunk, encoding, cb) {
if (this.expectingHeader) {
this.extractHeader(chunk);
}
else {
this.dataBuffer = Buffer.concat([this.dataBuffer, chunk]);
if (this.dataBuffer.length >= this.dataLength) {
this.pushCompletedPacket();
}
}
cb();
}
_flush(cb) {
const remaining = Buffer.concat([this.headerBuffer, this.dataBuffer]);
const remainingArray = Array.from(remaining);
this.push(remainingArray);
cb();
}
}
exports.SpacePacketParser = SpacePacketParser;

View File

@@ -0,0 +1,28 @@
export declare const HEADER_LENGTH = 6;
export interface SpacePacketHeader {
versionNumber: string | number;
identification: {
apid: number;
secondaryHeader: number;
type: number;
};
sequenceControl: {
packetName: number;
sequenceFlags: number;
};
dataLength: number;
}
export interface SpacePacket {
header: SpacePacketHeader;
secondaryHeader?: {
timeCode?: string;
ancillaryData?: string;
};
data: string;
}
/**
* Converts a Buffer of any length to an Object representation of a Space Packet header, provided
* the received data is in the correct format.
* @param buf - The buffer containing the Space Packet Header Data
*/
export declare const convertHeaderBufferToObj: (buf: Buffer) => SpacePacketHeader;

View File

@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertHeaderBufferToObj = exports.HEADER_LENGTH = void 0;
exports.HEADER_LENGTH = 6;
/**
* For numbers less than 255, will ensure that their string representation is at least 8 characters long.
*/
const toOctetStr = (num) => {
let str = Number(num).toString(2);
while (str.length < 8) {
str = `0${str}`;
}
return str;
};
/**
* Converts a Buffer of any length to an Object representation of a Space Packet header, provided
* the received data is in the correct format.
* @param buf - The buffer containing the Space Packet Header Data
*/
const convertHeaderBufferToObj = (buf) => {
const headerStr = Array.from(buf.slice(0, exports.HEADER_LENGTH)).reduce((accum, curr) => `${accum}${toOctetStr(curr)}`, '');
const isVersion1 = headerStr.slice(0, 3) === '000';
const versionNumber = isVersion1 ? 1 : 'UNKNOWN_VERSION';
const type = Number(headerStr[3]);
const secondaryHeader = Number(headerStr[4]);
const apid = parseInt(headerStr.slice(5, 16), 2);
const sequenceFlags = parseInt(headerStr.slice(16, 18), 2);
const packetName = parseInt(headerStr.slice(18, 32), 2);
const dataLength = parseInt(headerStr.slice(-16), 2) + 1;
return {
versionNumber,
identification: {
apid,
secondaryHeader,
type,
},
sequenceControl: {
packetName,
sequenceFlags,
},
dataLength,
};
};
exports.convertHeaderBufferToObj = convertHeaderBufferToObj;