@ledgerhq/devices
Version:
101 lines • 4.16 kB
JavaScript
import { TransportError } from "@ledgerhq/errors";
const Tag = 0x05;
function asUInt16BE(value) {
const b = Buffer.alloc(2);
b.writeUInt16BE(value, 0);
return b;
}
const initialAcc = {
data: Buffer.alloc(0),
dataLength: 0,
sequence: 0,
};
/**
* Object to handle HID frames (encoding and decoding)
*
* @param channel
* @param packetSize The HID protocol packet size in bytes (usually 64)
*/
const createHIDframing = (channel, packetSize) => {
return {
/**
* Frames/encodes an APDU message into HID USB packets/frames
*
* @param apdu The APDU message to send, in a Buffer containing [cla, ins, p1, p2, data length, data(if not empty)]
* @returns an array of HID USB frames ready to be sent
*/
makeBlocks(apdu) {
// Encodes the APDU length in 2 bytes before the APDU itself.
// The length is measured as the number of bytes.
// As the size of the APDU `data` should have been added in 1 byte just before `data`,
// the minimum size of an APDU is 5 bytes.
let data = Buffer.concat([asUInt16BE(apdu.length), apdu]);
const blockSize = packetSize - 5;
const nbBlocks = Math.ceil(data.length / blockSize);
// Fills data with 0-padding
data = Buffer.concat([data, Buffer.alloc(nbBlocks * blockSize - data.length + 1).fill(0)]);
const blocks = [];
for (let i = 0; i < nbBlocks; i++) {
const head = Buffer.alloc(5);
head.writeUInt16BE(channel, 0);
head.writeUInt8(Tag, 2);
head.writeUInt16BE(i, 3);
// `slice` and not `subarray`: this might not be a Node Buffer, but probably only a Uint8Array
const chunk = data.slice(i * blockSize, (i + 1) * blockSize);
blocks.push(Buffer.concat([head, chunk]));
}
return blocks;
},
/**
* Reduces HID USB packets/frames to one response.
*
* @param acc The value resulting from (accumulating) the previous call of reduceResponse.
* On first call initialized to `initialAcc`. The accumulator enables handling multi-frames messages.
* @param chunk Current chunk to reduce into accumulator
* @returns An accumulator value updated with the current chunk
*/
reduceResponse(acc, chunk) {
let { data, dataLength, sequence } = acc || initialAcc;
if (chunk.readUInt16BE(0) !== channel) {
throw new TransportError("Invalid channel", "InvalidChannel");
}
if (chunk.readUInt8(2) !== Tag) {
throw new TransportError("Invalid tag", "InvalidTag");
}
if (chunk.readUInt16BE(3) !== sequence) {
throw new TransportError("Invalid sequence", "InvalidSequence");
}
// Gets the total length of the response from the 1st frame
if (!acc) {
dataLength = chunk.readUInt16BE(5);
}
sequence++;
// The total length on the 1st frame takes 2 more bytes
const chunkData = chunk.slice(acc ? 5 : 7);
data = Buffer.concat([data, chunkData]);
// Removes any 0 padding
if (data.length > dataLength) {
data = data.slice(0, dataLength);
}
return {
data,
dataLength,
sequence,
};
},
/**
* Returns the response message that has been reduced from the HID USB frames
*
* @param acc The accumulator
* @returns A Buffer containing the cleaned response message, or null if no response message, or undefined if the
* accumulator is incorrect (message length is not valid)
*/
getReducedResult(acc) {
if (acc && acc.dataLength === acc.data.length) {
return acc.data;
}
},
};
};
export default createHIDframing;
//# sourceMappingURL=hid-framing.js.map