-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- New better UI - Tester for mitigation of side-channel attack
- Loading branch information
Showing
10 changed files
with
1,122 additions
and
378 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
export function toBinaryString(value) { | ||
let str = (value >>> 0).toString(2); | ||
return '0b' + str.padStart(Math.ceil(str.length / 8) * 8, '0'); | ||
} | ||
|
||
export function reverseByte(b) { | ||
b = (b & 0b11110000) >> 4 | (b & 0b00001111) << 4; | ||
b = (b & 0b11001100) >> 2 | (b & 0b00110011) << 2; | ||
b = (b & 0b10101010) >> 1 | (b & 0b01010101) << 1; | ||
return b; | ||
} | ||
|
||
export class BitDataView extends DataView { | ||
#u8; | ||
|
||
constructor(buffer) { | ||
super(buffer); | ||
this.#u8 = new Uint8Array(buffer); | ||
this.length = 0; | ||
} | ||
|
||
getBit(index) { | ||
const v = this.#u8[index >> 3]; | ||
const offset = index & 0x7; | ||
return (v >> (7-offset)) & 1; | ||
}; | ||
|
||
setBit(index, value) { | ||
this.length = Math.max(this.length, index + 1); | ||
const offset = index & 0x7; | ||
if (value) { | ||
this.#u8[index >> 3] |= (0x80 >> offset); | ||
} else { | ||
this.#u8[index >> 3] &= ~(0x80 >> offset); | ||
} | ||
}; | ||
|
||
shift16Left(amount, index = 0) { | ||
let view = new DataView(this.buffer); | ||
let value = view.getUint16(index, false); | ||
view.setInt16(index, value << amount, false); | ||
} | ||
|
||
[Symbol.iterator]() { | ||
return { | ||
current: 0, | ||
last: this.buffer.byteLength * 8, | ||
view: this, | ||
|
||
next() { | ||
if (this.current <= this.last) { | ||
return { done: false, value: this.view.getBit(this.current++) }; | ||
} else { | ||
return { done: true }; | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
|
||
export class MessageEncoder { | ||
encode(message) { | ||
const enc = new TextEncoder("utf-8"); | ||
|
||
// We send 32 bits per ASCII character: | ||
// |position| with the first bit set to 1 indicating a position | ||
// |checksum| and alignment byte for the position | ||
// |value| Non extended ASCII character | ||
// |checksum| and alignment byte for the value | ||
// | ||
// A checksum is the reversed complement, e.g for 0110 1000 | ||
// the complement will be 10010111 and reversed it will be 11101001 | ||
const letters = [...enc.encode(message)].flatMap((value, position) => { | ||
const pos = position | 0b10000000; | ||
const vPos = reverseByte(~pos); | ||
const vValue = reverseByte(~value); | ||
return [ pos, vPos, value, vValue ] | ||
}); | ||
|
||
return new Uint8Array([...letters]); | ||
} | ||
} | ||
|
||
export class MessageDecoder { | ||
decode(uInt8Array) { | ||
const dec = new TextDecoder("utf-8"); | ||
const view = new BitDataView(uInt8Array.buffer); | ||
const output = []; | ||
|
||
const POSITION_BIT = 0b10000000; | ||
let position = 0; | ||
|
||
for (let i = 0; i < uInt8Array.length; i += 2) { | ||
const byte = uInt8Array[i]; | ||
const checksum = uInt8Array[i + 1]; | ||
|
||
const isPosition = (byte & POSITION_BIT) === POSITION_BIT; | ||
const lastPosition = position; | ||
if (isPosition) { | ||
position = byte & 0b01111111; | ||
} | ||
|
||
if (byte !== reverseByte(~checksum)) { | ||
console.warn(`Checksum ${toBinaryString(checksum)} test failed for byte ${toBinaryString(byte)}`); | ||
if (isPosition && lastPosition + 1 === position) { | ||
console.warm(`Position ${position} appears to be correct, so we trust it.`) | ||
} else { | ||
continue; | ||
} | ||
} | ||
|
||
if (position > uInt8Array.length / 4) { | ||
console.warn(`Decoded position '${position}', but value seems corrupt.`) | ||
console.log(uInt8Array.length); | ||
position = -1; | ||
} | ||
|
||
if (!isPosition) { | ||
let char = dec.decode(new Uint8Array([byte])); | ||
if (position < 0) { | ||
console.warn(`Decoded '${char}', but character position is unknown.`); | ||
continue; | ||
} | ||
output[position] = char; | ||
position = -1; | ||
} | ||
} | ||
|
||
return [...output.values()].map(v => v ? v : '\ufffd').join(''); | ||
} | ||
} | ||
|
||
//let enc = new MessageEncoder(); | ||
//let dec = new MessageDecoder(); | ||
|
||
//let encoded = enc.encode("hello world"); | ||
//console.log(encoded); | ||
|
||
// h 0 1 2 3 | ||
// e 4 5 6 7 | ||
// l 8 9 10 11 | ||
// l 12 13 14 15 | ||
// o 16 17 18 19 | ||
// _ 20 21 22 23 | ||
// w 24 25 26 27 | ||
|
||
// encoded[5] = 0b01001111; // break position checksum | ||
// encoded[8] = 0b01001111; // break position 2 (l) | ||
// encoded[27] = 0b01001111; // break char 'w' | ||
|
||
// console.log(encoded); | ||
// console.log(dec.decode(encoded)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { MessageEncoder, BitDataView, toBinaryString } from "./binutils.js"; | ||
|
||
export class BitChannelBroadcaster extends EventTarget { | ||
#pressureGeneratorFn; | ||
|
||
constructor(pressureGeneratorFn) { | ||
super(); | ||
this.#pressureGeneratorFn = pressureGeneratorFn; | ||
} | ||
|
||
async sendMessage(msg, calibration) { | ||
const enc = new MessageEncoder(); | ||
const bits = new BitDataView(enc.encode(msg).buffer); | ||
|
||
const timeout = delay => new Promise(resolve => setTimeout(resolve, delay)); | ||
|
||
while (true) { | ||
let i = 0; | ||
let lastDwordIndex = 0; | ||
for (let bit of bits) { | ||
let byteIndex = Math.floor(i++ / 8); | ||
let dwordIndex = byteIndex % 4; | ||
|
||
this.dispatchEvent(new CustomEvent("bitsent", { detail: { value: bit } })); | ||
|
||
let byte = bits.getUint8(byteIndex); | ||
let chkByte = bits.getUint8(byteIndex + 1); | ||
|
||
if (dwordIndex !== lastDwordIndex) { | ||
if (dwordIndex === 0) { | ||
let pos = byte - 128; | ||
this.dispatchEvent(new CustomEvent("datachange", { | ||
detail: { | ||
type: "position", | ||
value: pos, | ||
data: new Uint8Array([byte, chkByte]) | ||
} | ||
})); | ||
} else if (dwordIndex === 2) { | ||
const dec = new TextDecoder("utf-8"); | ||
const ch = dec.decode(bits.buffer.slice(byteIndex, byteIndex + 1)); | ||
this.dispatchEvent(new CustomEvent("datachange", { | ||
detail: { | ||
type: "character", | ||
value: ch, | ||
data: new Uint8Array([byte, chkByte]) | ||
} | ||
})); | ||
} | ||
} | ||
lastDwordIndex = dwordIndex; | ||
|
||
this.#pressureGeneratorFn(bit ? calibration.one : calibration.zero); | ||
await timeout(calibration.delay); | ||
|
||
console.log("Sending 'reset'"); | ||
this.#pressureGeneratorFn(calibration.reset); | ||
await timeout(2 * calibration.delay); | ||
} | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { MessageEncoder, MessageDecoder, BitDataView, reverseByte } from "./binutils.js"; | ||
|
||
export class BitChannelObserver extends EventTarget { | ||
#lastTimestamp; | ||
#lastState = 1; | ||
#millis = [0, 0, 0]; // zero, reset, one | ||
#observer; | ||
|
||
#bitIndex = 0; | ||
#align = 0; | ||
#decoder = new MessageDecoder; | ||
#view = new BitDataView(new ArrayBuffer(2, { maxByteLength: 256 })); | ||
|
||
constructor() { | ||
super(); | ||
let lastValue = -1; | ||
|
||
const map = state => { | ||
switch(state) { | ||
case "nominal": | ||
case "fair": | ||
return 0; | ||
case "serious": | ||
return 1; | ||
case "critical": | ||
return 2; | ||
} | ||
} | ||
|
||
this.#observer = new PressureObserver(changes => { | ||
let value = map(changes[0].state); | ||
if (value !== lastValue) { | ||
this.#processRawState(value); | ||
} | ||
lastValue = value; | ||
}); | ||
} | ||
|
||
async observe(runTest = false) { | ||
if (!runTest) { | ||
this.#lastTimestamp = performance.now(); | ||
return await this.#observer.observe("cpu"); | ||
} | ||
|
||
const enc = new MessageEncoder(); | ||
const data = [...enc.encode("hello world")]; | ||
// Add some disalignment | ||
const brokenData = [23, 19, | ||
...data.slice(0, 8), | ||
15, 101, | ||
...data.slice(8)]; | ||
|
||
const bits = new BitDataView(new Uint8Array(brokenData).buffer); | ||
console.log(bits) | ||
for (let bit of bits) { | ||
this.#processData(bit); | ||
} | ||
} | ||
|
||
#processRawState(state) { | ||
let start = this.#lastTimestamp; | ||
this.#lastTimestamp = performance.now(); | ||
let time = (this.#lastTimestamp - start); | ||
|
||
this.#millis[this.#lastState] += time; | ||
this.#lastState = state; | ||
|
||
if (this.#millis[1] > 6_000) { | ||
const toDispatch = this.#millis[0] > this.#millis[2] ? 0 : 1; | ||
this.#processData(toDispatch); | ||
this.#millis = [0, 0, 0]; | ||
} | ||
} | ||
|
||
#processData(bit) { | ||
this.#view.setBit(this.#bitIndex, bit); | ||
|
||
this.dispatchEvent(new CustomEvent("bitreceived", { detail: { value: bit } })); | ||
|
||
if (++this.#bitIndex % 16 !== 0) { | ||
return; | ||
} | ||
|
||
// We have one word (two bytes) now! | ||
|
||
const byteIndex = this.#view.buffer.byteLength - 2; | ||
|
||
const value = this.#view.getUint8(byteIndex); | ||
const checksum = this.#view.getUint8(byteIndex + 1); | ||
|
||
if (value !== reverseByte(~checksum)) { | ||
// Checksum for byte doesn't match. | ||
// We might have gotten a wrong value, or a value too much or too little. | ||
// In most cases this requires around two new bytes to find alignment. | ||
console.log(`Misaligned or corrupt data, shifting to find new alignment (${++this.#align})...`); | ||
//console.log(toBinaryString(view.getUint8(byteIndex)), toBinaryString(view.getUint8(byteIndex + 1))); | ||
this.#bitIndex--; | ||
this.#view.shift16Left(1, byteIndex); | ||
return; | ||
} | ||
|
||
// We have a valid word (two bytes) now! | ||
|
||
const message = this.#decoder.decode(new Uint8Array(this.#view.buffer)); | ||
this.#view.buffer.resize(this.#view.buffer.byteLength + 2); | ||
this.#align = 0; | ||
|
||
this.dispatchEvent(new CustomEvent("messagechange", { detail: { value: message } })); | ||
} | ||
} |
Oops, something went wrong.