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(
bytes,
size,

View File

@@ -2,6 +2,7 @@ let SEGMENT_DURATION = 30;
let PACKET_SIZE_BITS = 8;
let DATA_SIZE_BITS = 8;
let DATA_SIZE_CRC_BITS = 8;
let DATA_CRC_BITS = 8;
let BITS_PER_SEGMENT = 1;
let PACKET_ENCODING = false;
let PACKET_ENCODING_SIZE = 7;
@@ -14,6 +15,7 @@ export const changeConfiguration = (config) => {
packetSizeBitCount,
dataSizeBitCount,
dataSizeCrcBitCount,
dataCrcBitCount,
bitsPerSegment,
packetEncoding,
packetEncodingBitCount,
@@ -23,6 +25,7 @@ export const changeConfiguration = (config) => {
PACKET_SIZE_BITS = packetSizeBitCount;
DATA_SIZE_BITS = dataSizeBitCount;
DATA_SIZE_CRC_BITS = dataSizeCrcBitCount;
DATA_CRC_BITS = dataCrcBitCount;
BITS_PER_SEGMENT = bitsPerSegment;
PACKET_ENCODING = packetEncoding;
PACKET_ENCODING_SIZE = packetEncodingBitCount;
@@ -81,7 +84,7 @@ export const canSendPacket = () => {
}
export const getPacketEncodingBlockCount = () =>
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 getPacketizationBitCountFromByteCount = (byteCount) =>
getPacketizationBitCountFromBitCount(byteCount * 8);

View File

@@ -5,25 +5,59 @@ class PacketizationPanel extends BasePanel {
constructor() {
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.addInputNumber('size-power', 5, {min: 0, max: 16, eventName: 'sizePowerChange', translation: 'power of 2'});
this.addText(' ');
this.addDynamicText('size')
this.addDynamicText('size', 'n/a')
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', [
{ text: 'Interleaving', id: 'interleaving', checked: true, eventName: 'interleavingChange' },
{ text: 'Error Correction', id: 'error-correction', checked: true, eventName: 'errorCorrectionChange' },
]);
this.addEventListener('sizePowerChange', this.handleSizePowerChange);
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) => {
this.setValueById('size-power', value);
this.handleSizePowerChange({value});
@@ -33,6 +67,22 @@ class PacketizationPanel extends BasePanel {
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');
setInterleaving = (value) => this.setCheckedById('interleaving', value);

View File

@@ -1,5 +1,10 @@
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']);
@@ -102,6 +107,23 @@ export const getAllPacketBits = () => {
}
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) => {
const bits = [];
const packet = BITS[packetIndex] ?? [];
@@ -121,10 +143,19 @@ export const getPacketBitsDecoded = (packetIndex, defaultBit = 0) => {
const bits = getPacketBits(packetIndex, defaultBit);
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 header = STREAM_HEADERS[name];
if(!header) return [];
const { index, length } = header;
if(length === 0) return [];
const packetCount = getPacketReceivedCount();
const bits = [];
for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) {
@@ -136,6 +167,48 @@ const getStreamHeaderBits = name => {
export const getTransferByteCount = () => {
const name = 'transfer byte count';
const length = STREAM_HEADERS[name].length;
if(length === 0) return 1;
const bits = getStreamHeaderBits(name);
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) => {
parseInt(bits
if(bits.length === 0) return 0;
if(bitLength <= 0) return 0;
return parseInt(bits
// only grab the bits we need
.slice(0, bitLength)
// combine into string
.join('')
// Assume missing bits were zeros
.padEnd(bitLength, '0')
.padEnd(bitLength, '0'),
2
);
}

View File

@@ -79,7 +79,8 @@
<h2>Decoded</h2>
<div>
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>
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>

292
index.js
View File

@@ -16,13 +16,23 @@ import PacketizationPanel from "./Panels/PacketizationPanel";
import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel";
import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel";
import GraphConfigurationPanel from './Panels/GraphConfigurationPanel'
import {
bitsToInt,
bitsToBytes,
bitsToText,
bytesToBits,
bytesToText,
numberToBytes,
numberToBits,
numberToHex,
textToBits,
textToBytes,
} from './converters';
var audioContext;
var microphoneStream;
var microphoneNode;
var analyser;
var sentDataTextArea;
const MAXIMUM_PACKETIZATION_SIZE_BITS = 16;
const CRC_BIT_COUNT = 8;
// bits as they are sent
let SENT_ORIGINAL_TEXT = '';
@@ -96,6 +106,10 @@ function handleWindowLoad() {
signalPanel.setTimeoutMilliseconds(60);
packetizationPanel.setSizePower(5);
packetizationPanel.setDataSizePower(16);
packetizationPanel.setDataSizeCrc(8);
packetizationPanel.setDataCrc(16);
packetizationPanel.setErrorCorrection(true);
packetizationPanel.setInterleaving(true);
@@ -153,6 +167,9 @@ function handleWindowLoad() {
configurationChanged();
});
packetizationPanel.addEventListener('errorCorrectionChange', configurationChanged);
packetizationPanel.addEventListener('dataSizePowerChange', configurationChanged);
packetizationPanel.addEventListener('dataSizeCrcChange', configurationChanged);
packetizationPanel.addEventListener('dataCrcChange', configurationChanged);
availableFskPairsPanel.addEventListener('change', (event) => {
frequencyGraphPanel.setFskPairs(event.selected);
@@ -252,6 +269,10 @@ function updateStreamManager() {
StreamManager.setPacketEncoding(
packetizationPanel.getErrorCorrection() ? HammingEncoding : undefined
);
const xferCountLength = packetizationPanel.getDataSizePower();
const xferCountCrcLength = xferCountLength === 0 ? 0 : packetizationPanel.getDataSizeCrc();
const xferCrcLength = packetizationPanel.getDataCrc();
StreamManager.changeConfiguration({
bitsPerPacket: PacketUtils.getPacketMaxBitCount(),
segmentsPerPacket: PacketUtils.getPacketSegmentCount(),
@@ -259,11 +280,15 @@ function updateStreamManager() {
streamHeaders: {
'transfer byte count': {
index: 0,
length: MAXIMUM_PACKETIZATION_SIZE_BITS
length: xferCountLength
},
'transfer byte count crc': {
index: MAXIMUM_PACKETIZATION_SIZE_BITS,
length: CRC_BIT_COUNT
index: xferCountLength,
length: xferCountCrcLength
},
'transfer byte crc': {
index: xferCountLength + xferCountCrcLength,
length: xferCrcLength
},
}
});
@@ -276,8 +301,9 @@ function updatePacketUtils() {
PacketUtils.changeConfiguration({
segmentDurationMilliseconds: signalPanel.getSegmentDurationMilliseconds(),
packetSizeBitCount: packetizationPanel.getSizePower(),
dataSizeBitCount: MAXIMUM_PACKETIZATION_SIZE_BITS,
dataSizeCrcBitCount: CRC_BIT_COUNT,
dataSizeBitCount: packetizationPanel.getDataSizePower(),
dataSizeCrcBitCount: packetizationPanel.getDataSizeCrc(),
dataCrcBitCount: packetizationPanel.getDataCrc(),
bitsPerSegment,
packetEncoding: packetizationPanel.getErrorCorrection(),
packetEncodingBitCount: ERROR_CORRECTION_BLOCK_SIZE,
@@ -375,22 +401,20 @@ function percentInFrequency(hz, frequencyResolution) {
return percent;
}
function logSent(text) {
// display what is being sent
sentDataTextArea.value += text + '\n';
sentDataTextArea.scrollTop = sentDataTextArea.scrollHeight;
}
function sendBytes(bytes) {
const byteCount = bytes.length;
if(byteCount === 0) {
logSent('Nothing to send!');
document.getElementById('sent-data').innerText = 'Nothing to send!';
return;
} else if(byteCount > 0xFFFF) {
logSent('Too much to send!');
} else if(byteCount > packetizationPanel.getDataSize()) {
document.getElementById('sent-data').innerText = `Attempted to send too much data. Limit is ${Humanize.byteSize(packetizationPanel.getDataSize())}. Tried to send ${Humanize.byteSize(byteCount)}`;
return;
}
AudioReceiver.reset();
StreamManager.reset();
frequencyGraphPanel.start();
const bits = bytesToBits(bytes);
SENT_ORIGINAL_TEXT = bytesToText(bytes);
@@ -398,12 +422,44 @@ function sendBytes(bytes) {
// packetization headers
// data length
const dataLengthBits = numberToBits(bytes.length, MAXIMUM_PACKETIZATION_SIZE_BITS);
// crc on data length
const dataLengthCrcBits = numberToBits(CRC.check(bitsToBytes(dataLengthBits), CRC_BIT_COUNT), CRC_BIT_COUNT);
let dataLengthBits = [];
let dataLengthCrcBits = [];
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
bits.unshift(...dataLengthBits, ...dataLengthCrcBits);
bits.unshift(
...dataLengthBits,
...dataLengthCrcBits,
...dataCrcBits
);
const bitCount = bits.length;
@@ -418,13 +474,10 @@ function sendBytes(bytes) {
const packetCount = PacketUtils.getPacketCount(bitCount);
const totalDurationSeconds = PacketUtils.getDataTransferDurationSeconds(bitCount);
const errorCorrectionBits = [];
AudioSender.beginAt(startSeconds);
// send all packets
for(let i = 0; i < packetCount; i++) {
let packet = PacketUtils.getPacketBits(bits, i);
errorCorrectionBits.push(...packet);
SENT_ENCODED_BITS.push(...packet);
if(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() {
const channelCount = availableFskPairsPanel.getSelectedFskPairs().length;
let allRawBits = StreamManager.getStreamBits();
let allEncodedBits = StreamManager.getAllPacketBits();
let allDecodedBits = getTransferredCorrectedBits();
let allDecodedBits = StreamManager.getAllPacketBitsDecoded();
// get packet data before removing decoded bits
const transmissionByteCount = parseTransmissionByteCount(allDecodedBits);
const transmissionByteCountCrc = parseTransmissionByteCountCrc(allDecodedBits)
const transmissionByteCountActualCrc = CRC.check(
bitsToBytes(
numberToBits(
transmissionByteCount,
MAXIMUM_PACKETIZATION_SIZE_BITS
)
), CRC_BIT_COUNT
);
const trustedLength = transmissionByteCountCrc === transmissionByteCountActualCrc;
const transmissionByteCount = StreamManager.getTransferByteCount();
const transmissionByteCountCrc = StreamManager.getTransferByteCountCrc();
const transmissionByteCountActualCrc = StreamManager.getTransferByteCountActualCrc();
const trustedLength = StreamManager.isTransferByteCountTrusted();
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
allDecodedBits = removeDecodedHeadersAndPadding(allDecodedBits);
@@ -604,10 +647,10 @@ function handleStreamManagerChange() {
'');
document.getElementById('received-packet-original-bytes').innerText = transmissionByteCount.toLocaleString();
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';
if(!trustedLength) {
packetCrc.innerText += ' (Expected 0x' + asHex(2)(transmissionByteCountActualCrc) + ')';
packetCrc.innerText += ' (Expected ' + numberToHex(packetizationPanel.getDataSizeCrc())(transmissionByteCountActualCrc) + ')';
}
document.getElementById('received-encoded-bits-error-percent').innerText = (
@@ -619,44 +662,23 @@ function handleStreamManagerChange() {
document.getElementById('received-decoded-bits-error-percent').innerText = (
Math.floor((1 - (correctedDecodedBits / allDecodedBits.length)) * 10000) * 0.01
).toLocaleString();
// ArrayBuffer / ArrayBufferView
const receivedText = bitsToText(allDecodedBits);
const bytes = StreamManager.getDataBytes();
const receivedText = bytesToText(bytes);
messagePanel.setReceived(
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() {
const dataByteCount = parseTransmissionByteCount();
const dataByteCount = StreamManager.getTransferByteCount();
const bitCount = PacketUtils.getPacketizationBitCountFromByteCount(dataByteCount);
const segments = getTotalSegmentCount(bitCount);
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) {
const sizeBits = MAXIMUM_PACKETIZATION_SIZE_BITS;
const sizeBits = packetizationPanel.getDataSizePower();
const dataSize = ERROR_CORRECTION_DATA_SIZE;
const blockSize = ERROR_CORRECTION_BLOCK_SIZE;
let bitsNeeded = sizeBits;
@@ -675,7 +697,10 @@ function removeEncodedPadding(bits) {
const dataByteCount = StreamManager.getTransferByteCount();
// 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;
if(packetizationPanel.getErrorCorrection()) {
const blocks = Math.ceil(encodingBitCount / dataSize);
@@ -691,17 +716,51 @@ function removeEncodedPadding(bits) {
return bits;
}
function removeDecodedHeadersAndPadding(bits) {
const sizeBits = MAXIMUM_PACKETIZATION_SIZE_BITS;
let bitCount = bits.length / 8;
if(bits.length >= sizeBits) {
bitCount = bitsToInt(bits.slice(0, sizeBits), sizeBits);
const sizeBitCount = packetizationPanel.getDataSizePower();
const sizeCrcBitCount = packetizationPanel.getDataSizeCrc();
const dataCrcBitCount = packetizationPanel.getDataCrc();
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 packetIndex = Math.floor(i / packetBitSize);
@@ -780,63 +839,10 @@ function getAudioContext() {
}
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() {
if(messagePanel.getSendButtonText() === 'Stop') {
AudioSender.stop();
} else {
AudioReceiver.reset();
StreamManager.reset();
frequencyGraphPanel.start();
const text = messagePanel.getMessage();
sendBytes(textToBytes(text));
}