add data crc

This commit is contained in:
Lewis Moten
2024-05-12 17:36:54 -04:00
parent 56e951e3e1
commit 5bc9157659
7 changed files with 342 additions and 153 deletions

3
CRC.js
View File

@@ -1,3 +1,6 @@
// CRC codes are not signed
export const INVALID = -1;
function calcCrc( function calcCrc(
bytes, bytes,
size, size,

View File

@@ -2,6 +2,7 @@ let SEGMENT_DURATION = 30;
let PACKET_SIZE_BITS = 8; let PACKET_SIZE_BITS = 8;
let DATA_SIZE_BITS = 8; let DATA_SIZE_BITS = 8;
let DATA_SIZE_CRC_BITS = 8; let DATA_SIZE_CRC_BITS = 8;
let DATA_CRC_BITS = 8;
let BITS_PER_SEGMENT = 1; let BITS_PER_SEGMENT = 1;
let PACKET_ENCODING = false; let PACKET_ENCODING = false;
let PACKET_ENCODING_SIZE = 7; let PACKET_ENCODING_SIZE = 7;
@@ -14,6 +15,7 @@ export const changeConfiguration = (config) => {
packetSizeBitCount, packetSizeBitCount,
dataSizeBitCount, dataSizeBitCount,
dataSizeCrcBitCount, dataSizeCrcBitCount,
dataCrcBitCount,
bitsPerSegment, bitsPerSegment,
packetEncoding, packetEncoding,
packetEncodingBitCount, packetEncodingBitCount,
@@ -23,6 +25,7 @@ export const changeConfiguration = (config) => {
PACKET_SIZE_BITS = packetSizeBitCount; PACKET_SIZE_BITS = packetSizeBitCount;
DATA_SIZE_BITS = dataSizeBitCount; DATA_SIZE_BITS = dataSizeBitCount;
DATA_SIZE_CRC_BITS = dataSizeCrcBitCount; DATA_SIZE_CRC_BITS = dataSizeCrcBitCount;
DATA_CRC_BITS = dataCrcBitCount;
BITS_PER_SEGMENT = bitsPerSegment; BITS_PER_SEGMENT = bitsPerSegment;
PACKET_ENCODING = packetEncoding; PACKET_ENCODING = packetEncoding;
PACKET_ENCODING_SIZE = packetEncodingBitCount; PACKET_ENCODING_SIZE = packetEncodingBitCount;
@@ -81,7 +84,7 @@ export const canSendPacket = () => {
} }
export const getPacketEncodingBlockCount = () => export const getPacketEncodingBlockCount = () =>
isPacketEncoded() ? Math.floor(getPacketMaxBitCount() / packetEncodingBlockSize()) : 0; isPacketEncoded() ? Math.floor(getPacketMaxBitCount() / packetEncodingBlockSize()) : 0;
export const getPacketizationHeaderBitCount = () => DATA_SIZE_BITS + DATA_SIZE_CRC_BITS; export const getPacketizationHeaderBitCount = () => DATA_SIZE_BITS + DATA_SIZE_CRC_BITS + DATA_CRC_BITS;
export const getPacketizationBitCountFromBitCount = (bitCount) => bitCount + getPacketizationHeaderBitCount(); export const getPacketizationBitCountFromBitCount = (bitCount) => bitCount + getPacketizationHeaderBitCount();
export const getPacketizationBitCountFromByteCount = (byteCount) => export const getPacketizationBitCountFromByteCount = (byteCount) =>
getPacketizationBitCountFromBitCount(byteCount * 8); getPacketizationBitCountFromBitCount(byteCount * 8);

View File

@@ -5,25 +5,59 @@ class PacketizationPanel extends BasePanel {
constructor() { constructor() {
super('Packetization'); super('Packetization');
this.openField('Packet Size'); this.openField('Max Data Size');
this.addText('2^');
this.addInputNumber('data-size-power', 16, {min: 0, max: 32, eventName: 'dataSizePowerChange', translation: 'power of 2'});
this.addText(' ');
this.addDynamicText('data-size', 'n/a')
this.closeField();
this.openField('CRC on Size');
this.addDropdown('data-size-crc', [
{text: 'None', value: 0},
{text: 'CRC-8', value: 8},
{text: 'CRC-16', value: 16},
{text: 'CRC-32', value: 32},
], 'dataSizeCrcChange');
this.closeField();
this.openField('CRC on Data');
this.addDropdown('data-crc', [
{text: 'None', value: 0},
{text: 'CRC-8', value: 8},
{text: 'CRC-16', value: 16},
{text: 'CRC-32', value: 32},
], 'dataCrcChange');
this.closeField();
this.addSection('Packets');
this.openField('Size');
this.addText('2^'); this.addText('2^');
this.addInputNumber('size-power', 5, {min: 0, max: 16, eventName: 'sizePowerChange', translation: 'power of 2'}); this.addInputNumber('size-power', 5, {min: 0, max: 16, eventName: 'sizePowerChange', translation: 'power of 2'});
this.addText(' '); this.addText(' ');
this.addDynamicText('size') this.addDynamicText('size', 'n/a')
this.closeField(); this.closeField();
this.addSection('Encoding'); this.addCheckboxes('packet-encoding', [
{ text: 'Error Correction', id: 'error-correction', checked: true, eventName: 'errorCorrectionChange' },
]);
this.addSection('Sampling Period');
this.addCheckboxes('packet-encoding', [ this.addCheckboxes('packet-encoding', [
{ text: 'Interleaving', id: 'interleaving', checked: true, eventName: 'interleavingChange' }, { text: 'Interleaving', id: 'interleaving', checked: true, eventName: 'interleavingChange' },
{ text: 'Error Correction', id: 'error-correction', checked: true, eventName: 'errorCorrectionChange' },
]); ]);
this.addEventListener('sizePowerChange', this.handleSizePowerChange); this.addEventListener('sizePowerChange', this.handleSizePowerChange);
this.dispatcher.emit('sizePowerChange', {value: this.getSize()}); this.dispatcher.emit('sizePowerChange', {value: this.getSize()});
this.addEventListener('dataSizePowerChange', this.handleDataSizePowerChange);
this.dispatcher.emit('dataSizePowerChange', {value: this.getDataSizePower()});
this.dispatcher.emit('dataSizeChange', {value: this.getDataSize()});
}; };
getSizePower = () => parseInt(this.getValueById('size-power')); getSizePower = () => this.getNumberById('size-power');
setSizePower = (value) => { setSizePower = (value) => {
this.setValueById('size-power', value); this.setValueById('size-power', value);
this.handleSizePowerChange({value}); this.handleSizePowerChange({value});
@@ -33,6 +67,22 @@ class PacketizationPanel extends BasePanel {
this.setValueById('size', byteSize(this.getSize())); this.setValueById('size', byteSize(this.getSize()));
} }
getDataSizePower = () => this.getNumberById('data-size-power');
setDataSizePower = (value) => {
this.setValueById('data-size-power', value);
this.handleDataSizePowerChange({value});
}
getDataSize = () => 2 ** this.getDataSizePower();
handleDataSizePowerChange = () => {
this.setValueById('data-size', byteSize(this.getDataSize()));
}
getDataSizeCrc = () => this.getNumberById('data-size-crc');
setDataSizeCrc = bitLength => this.setValueById('data-size-crc', bitLength)
getDataCrc = () => this.getNumberById('data-crc');
setDataCrc = bitLength => this.setValueById('data-crc', bitLength)
getInterleaving = () => this.getCheckedById('interleaving'); getInterleaving = () => this.getCheckedById('interleaving');
setInterleaving = (value) => this.setCheckedById('interleaving', value); setInterleaving = (value) => this.setCheckedById('interleaving', value);

View File

@@ -1,5 +1,10 @@
import Dispatcher from "./Dispatcher"; import Dispatcher from "./Dispatcher";
import { bitsToInt } from "./converters"; import * as CRC from './CRC';
import {
bitsToBytes,
bitsToInt,
numberToBytes
} from "./converters";
const dispatcher = new Dispatcher('StreamManager', ['change']); const dispatcher = new Dispatcher('StreamManager', ['change']);
@@ -102,6 +107,23 @@ export const getAllPacketBits = () => {
} }
return bits; return bits;
} }
export const getAllPacketBitsDecoded = () => {
const packetCount = getPacketReceivedCount();
const bits = [];
for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) {
bits.push(...getPacketBitsDecoded(packetIndex));
}
return bits;
}
export const getDataBytes = () => {
const bits = getAllPacketBitsDecoded();
bits.splice(0, getStreamHeaderBitCount());
const bytes = bitsToBytes(bits);
if(isTransferByteCountTrusted()) {
bytes.length = getTransferByteCount();
}
return bytes;
}
export const getPacketBits = (packetIndex, defaultBit = 0) => { export const getPacketBits = (packetIndex, defaultBit = 0) => {
const bits = []; const bits = [];
const packet = BITS[packetIndex] ?? []; const packet = BITS[packetIndex] ?? [];
@@ -121,10 +143,19 @@ export const getPacketBitsDecoded = (packetIndex, defaultBit = 0) => {
const bits = getPacketBits(packetIndex, defaultBit); const bits = getPacketBits(packetIndex, defaultBit);
return PACKET_ENCODING.decode(bits); return PACKET_ENCODING.decode(bits);
} }
const getStreamHeaderBitCount = () => {
return Object.keys(STREAM_HEADERS).reduce((lastBit, key) => {
const {index = 0, length = 0} = STREAM_HEADERS[key];
if(length === 0) return lastBit;
if(lastBit < index + length) return index + length;
return lastBit;
}, 0);
}
const getStreamHeaderBits = name => { const getStreamHeaderBits = name => {
const header = STREAM_HEADERS[name]; const header = STREAM_HEADERS[name];
if(!header) return []; if(!header) return [];
const { index, length } = header; const { index, length } = header;
if(length === 0) return [];
const packetCount = getPacketReceivedCount(); const packetCount = getPacketReceivedCount();
const bits = []; const bits = [];
for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) { for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) {
@@ -136,6 +167,48 @@ const getStreamHeaderBits = name => {
export const getTransferByteCount = () => { export const getTransferByteCount = () => {
const name = 'transfer byte count'; const name = 'transfer byte count';
const length = STREAM_HEADERS[name].length; const length = STREAM_HEADERS[name].length;
if(length === 0) return 1;
const bits = getStreamHeaderBits(name); const bits = getStreamHeaderBits(name);
return bitsToInt(bits, length); return bitsToInt(bits, length);
} }
export const getTransferByteCountCrc = () => {
const name = 'transfer byte count crc';
const length = STREAM_HEADERS[name].length;
if(length === 0) return 0;
const bits = getStreamHeaderBits(name);
if(bits.length !== length) return CRC.INVALID;
return bitsToInt(bits, length);
}
export const getTransferByteCountActualCrc = () => {
const countBits = getStreamHeaderBits('transfer byte count').length;
if(countBits === 0) return 0;
const crcBits = getStreamHeaderBits('transfer byte count crc').length;
if(crcBits === 0) return 0;
const count = getTransferByteCount();
const bytesOfCount = numberToBytes(count, countBits);
return CRC.check(bytesOfCount, crcBits)
}
export const isTransferByteCountTrusted = () => {
return getTransferByteCountCrc() === getTransferByteCountActualCrc();
}
export function getTransferDataCrc() {
const name = 'transfer byte crc';
const length = STREAM_HEADERS[name].length;
if(length === 0) return 0;
const bits = getStreamHeaderBits(name);
if(bits.length !== length) return CRC.INVALID;
return bitsToInt(bits, length);
}
export function getTransferActualDataCrc() {
const name = 'transfer byte crc';
const length = STREAM_HEADERS[name].length;
if(length === 0) return 0;
const crcBits = getStreamHeaderBits(name).length;
const bytes = getDataBytes();
return CRC.check(bytes, crcBits);
}
export const isTransferDataTrusted = () => {
return getTransferDataCrc() === getTransferActualDataCrc();
}

View File

@@ -1,10 +1,63 @@
export const numberToBytes = (number, bitLength) => {
const byteCount = Math.ceil(bitLength/8);
const bytes = [];
for(let i = 0; i < byteCount; i++) {
bytes.push((number >> (8 * (byteCount - 1 - i))) & 0xFF);
}
return bytes;
}
export const numberToHex = (bitLength) => {
const digits = Math.ceil(bitLength / 4);
return (number) => '0x' + number.toString(16).padStart(digits, '0').toUpperCase();
}
export function numberToBits(number, bitLength) {
const bits = [];
for(let i = bitLength - 1; i >= 0; i--)
bits.push((number >> i) & 1);
return bits;
}
export function bytesToText(bytes) {
if(!(bytes instanceof ArrayBuffer || ArrayBuffer.isView(bytes))) {
bytes = new Uint8Array(bytes).buffer;
}
return new TextDecoder().decode(bytes);
}
export function bytesToBits(bytes) {
return bytes.reduce((bits, byte) => [
...bits,
...byte.toString(2).padStart(8, '0').split('').map(Number)
], []);
}
export function textToBytes(text) {
return new TextEncoder().encode(text);
}
export function textToBits(text) {
return bytesToBits(textToBytes(text));
}
export function bitsToText(bits) {
const bytes = new Uint8Array(bitsToBytes(bits));
return bytesToText(bytes.buffer);
}
export function bitsToBytes(bits) {
const bytes = [];
for(let i = 0; i < bits.length; i+= 8) {
bytes.push(parseInt(bits.slice(i, i + 8).join(''), 2));
}
return bytes;
}
export const bitsToInt = (bits, bitLength) => { export const bitsToInt = (bits, bitLength) => {
parseInt(bits if(bits.length === 0) return 0;
if(bitLength <= 0) return 0;
return parseInt(bits
// only grab the bits we need // only grab the bits we need
.slice(0, bitLength) .slice(0, bitLength)
// combine into string // combine into string
.join('') .join('')
// Assume missing bits were zeros // Assume missing bits were zeros
.padEnd(bitLength, '0') .padEnd(bitLength, '0'),
2
); );
} }

View File

@@ -79,7 +79,8 @@
<h2>Decoded</h2> <h2>Decoded</h2>
<div> <div>
Bytes: <span id="received-packet-original-bytes">N/A</span><br> Bytes: <span id="received-packet-original-bytes">N/A</span><br>
Length CRC-8: <span id="received-packet-original-bytes-crc">N/A</span><br> Length CRC: <span id="received-packet-original-bytes-crc">N/A</span><br>
Data CRC: <span id="received-packet-original-data-crc">N/A</span><br>
<h4>Errors</h4> <h4>Errors</h4>
Encoded Segments: <span id="received-raw-bits-error-percent">N/A</span>%<br> Encoded Segments: <span id="received-raw-bits-error-percent">N/A</span>%<br>
Encoded Packets: <span id="received-encoded-bits-error-percent">N/A</span>%<br> Encoded Packets: <span id="received-encoded-bits-error-percent">N/A</span>%<br>

292
index.js
View File

@@ -16,13 +16,23 @@ import PacketizationPanel from "./Panels/PacketizationPanel";
import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel"; import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel";
import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel"; import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel";
import GraphConfigurationPanel from './Panels/GraphConfigurationPanel' import GraphConfigurationPanel from './Panels/GraphConfigurationPanel'
import {
bitsToInt,
bitsToBytes,
bitsToText,
bytesToBits,
bytesToText,
numberToBytes,
numberToBits,
numberToHex,
textToBits,
textToBytes,
} from './converters';
var audioContext; var audioContext;
var microphoneStream; var microphoneStream;
var microphoneNode; var microphoneNode;
var analyser; var analyser;
var sentDataTextArea; var sentDataTextArea;
const MAXIMUM_PACKETIZATION_SIZE_BITS = 16;
const CRC_BIT_COUNT = 8;
// bits as they are sent // bits as they are sent
let SENT_ORIGINAL_TEXT = ''; let SENT_ORIGINAL_TEXT = '';
@@ -96,6 +106,10 @@ function handleWindowLoad() {
signalPanel.setTimeoutMilliseconds(60); signalPanel.setTimeoutMilliseconds(60);
packetizationPanel.setSizePower(5); packetizationPanel.setSizePower(5);
packetizationPanel.setDataSizePower(16);
packetizationPanel.setDataSizeCrc(8);
packetizationPanel.setDataCrc(16);
packetizationPanel.setErrorCorrection(true); packetizationPanel.setErrorCorrection(true);
packetizationPanel.setInterleaving(true); packetizationPanel.setInterleaving(true);
@@ -153,6 +167,9 @@ function handleWindowLoad() {
configurationChanged(); configurationChanged();
}); });
packetizationPanel.addEventListener('errorCorrectionChange', configurationChanged); packetizationPanel.addEventListener('errorCorrectionChange', configurationChanged);
packetizationPanel.addEventListener('dataSizePowerChange', configurationChanged);
packetizationPanel.addEventListener('dataSizeCrcChange', configurationChanged);
packetizationPanel.addEventListener('dataCrcChange', configurationChanged);
availableFskPairsPanel.addEventListener('change', (event) => { availableFskPairsPanel.addEventListener('change', (event) => {
frequencyGraphPanel.setFskPairs(event.selected); frequencyGraphPanel.setFskPairs(event.selected);
@@ -252,6 +269,10 @@ function updateStreamManager() {
StreamManager.setPacketEncoding( StreamManager.setPacketEncoding(
packetizationPanel.getErrorCorrection() ? HammingEncoding : undefined packetizationPanel.getErrorCorrection() ? HammingEncoding : undefined
); );
const xferCountLength = packetizationPanel.getDataSizePower();
const xferCountCrcLength = xferCountLength === 0 ? 0 : packetizationPanel.getDataSizeCrc();
const xferCrcLength = packetizationPanel.getDataCrc();
StreamManager.changeConfiguration({ StreamManager.changeConfiguration({
bitsPerPacket: PacketUtils.getPacketMaxBitCount(), bitsPerPacket: PacketUtils.getPacketMaxBitCount(),
segmentsPerPacket: PacketUtils.getPacketSegmentCount(), segmentsPerPacket: PacketUtils.getPacketSegmentCount(),
@@ -259,11 +280,15 @@ function updateStreamManager() {
streamHeaders: { streamHeaders: {
'transfer byte count': { 'transfer byte count': {
index: 0, index: 0,
length: MAXIMUM_PACKETIZATION_SIZE_BITS length: xferCountLength
}, },
'transfer byte count crc': { 'transfer byte count crc': {
index: MAXIMUM_PACKETIZATION_SIZE_BITS, index: xferCountLength,
length: CRC_BIT_COUNT length: xferCountCrcLength
},
'transfer byte crc': {
index: xferCountLength + xferCountCrcLength,
length: xferCrcLength
}, },
} }
}); });
@@ -276,8 +301,9 @@ function updatePacketUtils() {
PacketUtils.changeConfiguration({ PacketUtils.changeConfiguration({
segmentDurationMilliseconds: signalPanel.getSegmentDurationMilliseconds(), segmentDurationMilliseconds: signalPanel.getSegmentDurationMilliseconds(),
packetSizeBitCount: packetizationPanel.getSizePower(), packetSizeBitCount: packetizationPanel.getSizePower(),
dataSizeBitCount: MAXIMUM_PACKETIZATION_SIZE_BITS, dataSizeBitCount: packetizationPanel.getDataSizePower(),
dataSizeCrcBitCount: CRC_BIT_COUNT, dataSizeCrcBitCount: packetizationPanel.getDataSizeCrc(),
dataCrcBitCount: packetizationPanel.getDataCrc(),
bitsPerSegment, bitsPerSegment,
packetEncoding: packetizationPanel.getErrorCorrection(), packetEncoding: packetizationPanel.getErrorCorrection(),
packetEncodingBitCount: ERROR_CORRECTION_BLOCK_SIZE, packetEncodingBitCount: ERROR_CORRECTION_BLOCK_SIZE,
@@ -375,22 +401,20 @@ function percentInFrequency(hz, frequencyResolution) {
return percent; return percent;
} }
function logSent(text) {
// display what is being sent
sentDataTextArea.value += text + '\n';
sentDataTextArea.scrollTop = sentDataTextArea.scrollHeight;
}
function sendBytes(bytes) { function sendBytes(bytes) {
const byteCount = bytes.length; const byteCount = bytes.length;
if(byteCount === 0) { if(byteCount === 0) {
logSent('Nothing to send!'); document.getElementById('sent-data').innerText = 'Nothing to send!';
return; return;
} else if(byteCount > 0xFFFF) { } else if(byteCount > packetizationPanel.getDataSize()) {
logSent('Too much to send!'); document.getElementById('sent-data').innerText = `Attempted to send too much data. Limit is ${Humanize.byteSize(packetizationPanel.getDataSize())}. Tried to send ${Humanize.byteSize(byteCount)}`;
return; return;
} }
AudioReceiver.reset();
StreamManager.reset();
frequencyGraphPanel.start();
const bits = bytesToBits(bytes); const bits = bytesToBits(bytes);
SENT_ORIGINAL_TEXT = bytesToText(bytes); SENT_ORIGINAL_TEXT = bytesToText(bytes);
@@ -398,12 +422,44 @@ function sendBytes(bytes) {
// packetization headers // packetization headers
// data length // data length
const dataLengthBits = numberToBits(bytes.length, MAXIMUM_PACKETIZATION_SIZE_BITS); let dataLengthBits = [];
// crc on data length let dataLengthCrcBits = [];
const dataLengthCrcBits = numberToBits(CRC.check(bitsToBytes(dataLengthBits), CRC_BIT_COUNT), CRC_BIT_COUNT); let dataSizeCrcNumber = 0;
const dataLengthBitLength = packetizationPanel.getDataSizePower();
if(dataLengthBitLength !== 0) {
dataLengthBits = numberToBits(bytes.length, dataLengthBitLength);
// crc on data length
const dataSizeCrcBitLength = packetizationPanel.getDataSizeCrc();
if(dataSizeCrcBitLength !== 0) {
const bytes = bitsToBytes(dataLengthBits);
dataSizeCrcNumber = CRC.check(bytes, dataSizeCrcBitLength);
dataLengthCrcBits = numberToBits(dataSizeCrcNumber, dataSizeCrcBitLength);
}
}
// crc on data
let dataCrcBits = [];
const dataCrcBitLength = packetizationPanel.getDataCrc();
let dataCrcNumber = 0;
if(dataCrcBitLength !== 0) {
dataCrcNumber = CRC.check(bytes, dataCrcBitLength);
dataCrcBits = numberToBits(dataCrcNumber, dataCrcBitLength);
}
console.log('sending headers', {
// dataLengthBits,
// dataLengthCrcBits,
// dataCrcBits,
dataCrcHex: numberToHex(packetizationPanel.getDataCrc())(dataCrcNumber),
dataSizeCrcHex: numberToHex(packetizationPanel.getDataSizeCrc())(dataSizeCrcNumber),
})
// prefix with headers // prefix with headers
bits.unshift(...dataLengthBits, ...dataLengthCrcBits); bits.unshift(
...dataLengthBits,
...dataLengthCrcBits,
...dataCrcBits
);
const bitCount = bits.length; const bitCount = bits.length;
@@ -418,13 +474,10 @@ function sendBytes(bytes) {
const packetCount = PacketUtils.getPacketCount(bitCount); const packetCount = PacketUtils.getPacketCount(bitCount);
const totalDurationSeconds = PacketUtils.getDataTransferDurationSeconds(bitCount); const totalDurationSeconds = PacketUtils.getDataTransferDurationSeconds(bitCount);
const errorCorrectionBits = [];
AudioSender.beginAt(startSeconds); AudioSender.beginAt(startSeconds);
// send all packets // send all packets
for(let i = 0; i < packetCount; i++) { for(let i = 0; i < packetCount; i++) {
let packet = PacketUtils.getPacketBits(bits, i); let packet = PacketUtils.getPacketBits(bits, i);
errorCorrectionBits.push(...packet);
SENT_ENCODED_BITS.push(...packet); SENT_ENCODED_BITS.push(...packet);
if(packet.length > packetBitCount) { if(packet.length > packetBitCount) {
console.error('Too many bits in the packet. tried to send %s, limited to %s', packet.length, packetBitCount); console.error('Too many bits in the packet. tried to send %s, limited to %s', packet.length, packetBitCount);
@@ -522,40 +575,30 @@ function resumeGraph() {
} }
} }
function getTransferredCorrectedBits() {
const bits = [];
const packetCount = StreamManager.getPacketReceivedCount();
for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) {
let packetBits = StreamManager.getPacketBits(packetIndex);
if(packetizationPanel.getErrorCorrection()) {
bits.push(...HammingEncoding.decode(packetBits));
} else {
bits.push(...packetBits);
}
}
return bits;
}
function handleStreamManagerChange() { function handleStreamManagerChange() {
const channelCount = availableFskPairsPanel.getSelectedFskPairs().length; const channelCount = availableFskPairsPanel.getSelectedFskPairs().length;
let allRawBits = StreamManager.getStreamBits(); let allRawBits = StreamManager.getStreamBits();
let allEncodedBits = StreamManager.getAllPacketBits(); let allEncodedBits = StreamManager.getAllPacketBits();
let allDecodedBits = getTransferredCorrectedBits(); let allDecodedBits = StreamManager.getAllPacketBitsDecoded();
// get packet data before removing decoded bits // get packet data before removing decoded bits
const transmissionByteCount = parseTransmissionByteCount(allDecodedBits); const transmissionByteCount = StreamManager.getTransferByteCount();
const transmissionByteCountCrc = parseTransmissionByteCountCrc(allDecodedBits) const transmissionByteCountCrc = StreamManager.getTransferByteCountCrc();
const transmissionByteCountActualCrc = CRC.check( const transmissionByteCountActualCrc = StreamManager.getTransferByteCountActualCrc();
bitsToBytes(
numberToBits( const trustedLength = StreamManager.isTransferByteCountTrusted();
transmissionByteCount,
MAXIMUM_PACKETIZATION_SIZE_BITS
)
), CRC_BIT_COUNT
);
const trustedLength = transmissionByteCountCrc === transmissionByteCountActualCrc;
const totalBitsTransferring = parseTotalBitsTransferring(allDecodedBits); const totalBitsTransferring = parseTotalBitsTransferring(allDecodedBits);
const transmissionDataCrc = StreamManager.getTransferDataCrc();
const transmissionDataActualCrc = StreamManager.getTransferActualDataCrc();
const transmissionCrc = document.getElementById('received-packet-original-data-crc');
transmissionCrc.innerText = numberToHex(packetizationPanel.getDataCrc())(transmissionDataCrc);
const trustedData = StreamManager.isTransferDataTrusted();
transmissionCrc.className = trustedData ? 'bit-correct' : 'bit-wrong';
if(!trustedData) {
transmissionCrc.innerText += ' (Expected ' + numberToHex(packetizationPanel.getDataCrc())(transmissionDataActualCrc) + ')';
}
// reduce all decoded bits based on original data sent // reduce all decoded bits based on original data sent
allDecodedBits = removeDecodedHeadersAndPadding(allDecodedBits); allDecodedBits = removeDecodedHeadersAndPadding(allDecodedBits);
@@ -604,10 +647,10 @@ function handleStreamManagerChange() {
''); '');
document.getElementById('received-packet-original-bytes').innerText = transmissionByteCount.toLocaleString(); document.getElementById('received-packet-original-bytes').innerText = transmissionByteCount.toLocaleString();
const packetCrc = document.getElementById('received-packet-original-bytes-crc'); const packetCrc = document.getElementById('received-packet-original-bytes-crc');
packetCrc.innerText = '0x' + asHex(2)(transmissionByteCountCrc); packetCrc.innerText = numberToHex(packetizationPanel.getDataSizeCrc())(transmissionByteCountCrc);
packetCrc.className = trustedLength ? 'bit-correct' : 'bit-wrong'; packetCrc.className = trustedLength ? 'bit-correct' : 'bit-wrong';
if(!trustedLength) { if(!trustedLength) {
packetCrc.innerText += ' (Expected 0x' + asHex(2)(transmissionByteCountActualCrc) + ')'; packetCrc.innerText += ' (Expected ' + numberToHex(packetizationPanel.getDataSizeCrc())(transmissionByteCountActualCrc) + ')';
} }
document.getElementById('received-encoded-bits-error-percent').innerText = ( document.getElementById('received-encoded-bits-error-percent').innerText = (
@@ -619,44 +662,23 @@ function handleStreamManagerChange() {
document.getElementById('received-decoded-bits-error-percent').innerText = ( document.getElementById('received-decoded-bits-error-percent').innerText = (
Math.floor((1 - (correctedDecodedBits / allDecodedBits.length)) * 10000) * 0.01 Math.floor((1 - (correctedDecodedBits / allDecodedBits.length)) * 10000) * 0.01
).toLocaleString(); ).toLocaleString();
// ArrayBuffer / ArrayBufferView
const receivedText = bitsToText(allDecodedBits); const bytes = StreamManager.getDataBytes();
const receivedText = bytesToText(bytes);
messagePanel.setReceived( messagePanel.setReceived(
receivedText.split('').reduce(textExpectorReducer(SENT_ORIGINAL_TEXT), '') receivedText.split('').reduce(textExpectorReducer(SENT_ORIGINAL_TEXT), '')
); );
} }
function asHex(length) {
return (number) => number.toString(16).padStart(length, '0').toUpperCase();
}
function parseDataTransferDurationMilliseconds() {
const decodedBits = getTransferredCorrectedBits();
const byteCount = parseTransmissionByteCount(decodedBits);
return PacketUtils.getDataTransferDurationMillisecondsFromByteCount(byteCount);
}
function parseTotalBitsTransferring() { function parseTotalBitsTransferring() {
const dataByteCount = parseTransmissionByteCount(); const dataByteCount = StreamManager.getTransferByteCount();
const bitCount = PacketUtils.getPacketizationBitCountFromByteCount(dataByteCount); const bitCount = PacketUtils.getPacketizationBitCountFromByteCount(dataByteCount);
const segments = getTotalSegmentCount(bitCount); const segments = getTotalSegmentCount(bitCount);
return segments * availableFskPairsPanel.getSelectedFskPairs().length; return segments * availableFskPairsPanel.getSelectedFskPairs().length;
} }
function parseTransmissionByteCountCrc() {
let decodedBits = getTransferredCorrectedBits();
const offset = MAXIMUM_PACKETIZATION_SIZE_BITS;
decodedBits = decodedBits.slice(offset, offset + CRC_BIT_COUNT);
return bitsToInt(decodedBits, CRC_BIT_COUNT);
}
function parseTransmissionByteCount() {
let decodedBits = getTransferredCorrectedBits();
decodedBits = decodedBits.slice(0, MAXIMUM_PACKETIZATION_SIZE_BITS);
while(decodedBits.length < MAXIMUM_PACKETIZATION_SIZE_BITS) {
// assume maximum value possible
// until we have enough bits to find the real size
decodedBits.push(1);
}
return bitsToInt(decodedBits, MAXIMUM_PACKETIZATION_SIZE_BITS);
}
function removeEncodedPadding(bits) { function removeEncodedPadding(bits) {
const sizeBits = MAXIMUM_PACKETIZATION_SIZE_BITS; const sizeBits = packetizationPanel.getDataSizePower();
const dataSize = ERROR_CORRECTION_DATA_SIZE; const dataSize = ERROR_CORRECTION_DATA_SIZE;
const blockSize = ERROR_CORRECTION_BLOCK_SIZE; const blockSize = ERROR_CORRECTION_BLOCK_SIZE;
let bitsNeeded = sizeBits; let bitsNeeded = sizeBits;
@@ -675,7 +697,10 @@ function removeEncodedPadding(bits) {
const dataByteCount = StreamManager.getTransferByteCount(); const dataByteCount = StreamManager.getTransferByteCount();
// determine how many decoded bits need to be sent (including the size) // determine how many decoded bits need to be sent (including the size)
const totalBits = (dataByteCount * 8) + MAXIMUM_PACKETIZATION_SIZE_BITS + CRC_BIT_COUNT; let totalBits = (dataByteCount * 8);
totalBits += packetizationPanel.getDataSizePower();
if(packetizationPanel.getDataSizePower() !== 0) totalBits += packetizationPanel.getDataSizeCrc();
totalBits += packetizationPanel.getDataCrc();
let encodingBitCount = totalBits; let encodingBitCount = totalBits;
if(packetizationPanel.getErrorCorrection()) { if(packetizationPanel.getErrorCorrection()) {
const blocks = Math.ceil(encodingBitCount / dataSize); const blocks = Math.ceil(encodingBitCount / dataSize);
@@ -691,17 +716,51 @@ function removeEncodedPadding(bits) {
return bits; return bits;
} }
function removeDecodedHeadersAndPadding(bits) { function removeDecodedHeadersAndPadding(bits) {
const sizeBits = MAXIMUM_PACKETIZATION_SIZE_BITS; const sizeBitCount = packetizationPanel.getDataSizePower();
let bitCount = bits.length / 8; const sizeCrcBitCount = packetizationPanel.getDataSizeCrc();
if(bits.length >= sizeBits) { const dataCrcBitCount = packetizationPanel.getDataCrc();
bitCount = bitsToInt(bits.slice(0, sizeBits), sizeBits); let byteCount;
let offset = 0;
if(sizeBitCount !== 0) {
offset += sizeBitCount;
// header bits only?
if(bits.length <= offset) return [];
byteCount = bitsToInt(bits.slice(0, sizeBitCount), sizeBitCount);
if(sizeCrcBitCount !== 0) {
offset += sizeBitCount;
// header bits only?
if(bits.length <= offset) return [];
let countCrc = bitsToInt(bits.slice(sizeBitCount, sizeBitCount + sizeCrcBitCount), sizeCrcBitCount);
let actualCountCrc = CRC.check(numberToBytes(sizeCrcBitCount, sizeBitCount), sizeCrcBitCount);
// can we trust the size?
if(countCrc !== actualCountCrc) {
if(dataCrcBitCount !== 0) {
offset += dataCrcBitCount;
if(bits.length <= offset) return [];
}
// Change based off of header bits
byteCount = (bits.length - offset) / 8;
}
} else if(dataCrcBitCount !== 0) {
offset += dataCrcBitCount;
if(bits.length <= offset) return [];
}
// remove headers and excessive bits
const bitCount = byteCount * 8;
return bits.slice(offset, offset + bitCount).splice(bitCount);
} else {
// size not included. Means 1 byte max
// crc not valid on size
// crc valid on byte
const dataCrcBitCount = packetizationPanel.getDataCrc();
if(dataCrcBitCount === 0) {
// bits are pure data for 1 byte
return bits.slice(0, 8);
} else {
// get byte after data crc
return bits.slice(dataCrcBitCount, dataCrcBitCount + 8);
}
} }
// remove size and crc header
bits.splice(0, sizeBits + CRC_BIT_COUNT);
// remove excessive bits
bits.splice(bitCount * 8);
return bits;
} }
const bitReducer = (packetBitSize, blockSize, blockCallback) => (all, bit, i) => { const bitReducer = (packetBitSize, blockSize, blockCallback) => (all, bit, i) => {
const packetIndex = Math.floor(i / packetBitSize); const packetIndex = Math.floor(i / packetBitSize);
@@ -780,63 +839,10 @@ function getAudioContext() {
} }
return audioContext; return audioContext;
} }
function bitsToInt(bits, bitLength) {
// only grab the bits we need
const bitString = bits.slice(0, bitLength)
// combine into string
.join('')
// Assume missing bits were zeros
.padEnd(bitLength, '0');
// parse as int
return parseInt(bitString, 2);
}
function intToBytes(int, bitLength) {
const byteCount = Math.ceil(bitLength/8);
const bytes = [];
for(let i = 0; i < byteCount; i++) {
bytes.push((int >> (8 * (byteCount - 1 - i))) & 0xFF);
}
return bytes;
}
function numberToBits(number, bitLength) {
const bits = [];
for(let i = bitLength - 1; i >= 0; i--)
bits.push((number >> i) & 1);
return bits;
}
function bytesToText(bytes) {
return new TextDecoder().decode(bytes);
}
function textToBytes(text) {
return new TextEncoder().encode(text);
}
function bytesToBits(bytes) {
return bytes.reduce((bits, byte) => [
...bits,
...byte.toString(2).padStart(8, '0').split('').map(Number)
], []);
}
function bitsToBytes(bits) {
const bytes = [];
for(let i = 0; i < bits.length; i+= 8) {
bytes.push(parseInt(bits.slice(i, i + 8).join(''), 2));
}
return bytes;
}
function textToBits(text) {
return bytesToBits(textToBytes(text));
}
function bitsToText(bits) {
const bytes = new Uint8Array(bitsToBytes(bits));
return bytesToText(bytes.buffer);
}
function handleSendButtonClick() { function handleSendButtonClick() {
if(messagePanel.getSendButtonText() === 'Stop') { if(messagePanel.getSendButtonText() === 'Stop') {
AudioSender.stop(); AudioSender.stop();
} else { } else {
AudioReceiver.reset();
StreamManager.reset();
frequencyGraphPanel.start();
const text = messagePanel.getMessage(); const text = messagePanel.getMessage();
sendBytes(textToBytes(text)); sendBytes(textToBytes(text));
} }