You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
5.0 KiB
JavaScript
117 lines
5.0 KiB
JavaScript
8 months ago
|
"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 {
|
||
|
/**
|
||
|
* 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;
|