add data crc
This commit is contained in:
3
CRC.js
3
CRC.js
@@ -1,3 +1,6 @@
|
||||
// CRC codes are not signed
|
||||
export const INVALID = -1;
|
||||
|
||||
function calcCrc(
|
||||
bytes,
|
||||
size,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
290
index.js
290
index.js
@@ -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);
|
||||
let dataLengthBits = [];
|
||||
let dataLengthCrcBits = [];
|
||||
let dataSizeCrcNumber = 0;
|
||||
const dataLengthBitLength = packetizationPanel.getDataSizePower();
|
||||
if(dataLengthBitLength !== 0) {
|
||||
dataLengthBits = numberToBits(bytes.length, dataLengthBitLength);
|
||||
|
||||
// crc on data length
|
||||
const dataLengthCrcBits = numberToBits(CRC.check(bitsToBytes(dataLengthBits), CRC_BIT_COUNT), CRC_BIT_COUNT);
|
||||
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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user