smartcardx
Version:
Backend library for communication with smartcards using system native PCSC interface. Plain Iso7816 + EMV + GlobalPlatform functionality.
207 lines (206 loc) • 8.02 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResponseApdu = void 0;
exports.assertResponseIsOk = assertResponseIsOk;
const utils_1 = require("./utils");
const statusDecode_1 = __importDefault(require("./statusDecode"));
class ResponseApdu {
/** Creates a new ResponseAPDU from input
* @param data - optional; binary data or another ResponseAPDU. All data is copied.
*/
static from(data) {
return new ResponseApdu(data);
}
/** Creates a new ResponseAPDU from input
* @param data - optional; binary data or another ResponseAPDU. All data is copied.
*/
constructor(data) {
this.byteArray = new Uint8Array(ResponseApdu.DEF_DATA_BYTES_LENGTH + 2); // data + status(2)
this.bLength = 2;
this.clear();
if (typeof data === 'undefined')
return this;
return this.from(data);
}
/** Overwrites this ResponseAPDU with new data. Any input data is copied into internal ArrayBuffer meaning the original data can be modified without changing this ResponseAPDU
* @param inData - binary data or another ResponseAPDU. All data is copied.
*/
from(inData) {
let inBuffer = new Uint8Array(0);
if (inData instanceof ResponseApdu) {
inBuffer = inData.toByteArray();
}
else {
try {
inBuffer = (0, utils_1.importBinData)(inData);
}
catch (error) {
throw new Error(`Could not create ResponseAPDU from provided data: ${error.message}`);
}
}
if (inBuffer.byteLength < 2) {
throw new Error(`Expected at least 2 bytes of input data, received: ${inBuffer.byteLength} bytes`);
}
if (this.byteArray.byteLength < inBuffer.byteLength) {
this.byteArray = new Uint8Array(inBuffer.byteLength);
}
this.byteArray.set(inBuffer);
this.bLength = inBuffer.byteLength;
return this;
}
/** Returned Uint8Array will reference same memory as this ResponseAPDU, meaning that any change made to it will reflect on this ResponseAPDU */
toByteArray() {
return this.byteArray.subarray(0, this.bLength);
}
/** Returns hexadecimal string representing this response apdu */
toString() {
return (0, utils_1.hexEncode)(this.toByteArray());
}
// clears this ResponseAPDU by setting it's content to "0x0000"
clear() {
this.byteArray.set([0, 0]);
this.bLength = 2;
return this;
}
/** Full length in bytes of this response apdu */
get byteLength() {
return this.bLength;
}
/** Length in bytes of the data part of this response apdu. 0 if no data */
get dataLength() {
if (this.bLength <= 2)
return 0;
return this.bLength - 2;
}
/** Returned byte array will reference same memory as this ResponseAPDU, meaning that any change made to it will reflect on this ResponseAPDU */
getData() {
return this.toByteArray().subarray(0, this.dataLength);
}
/** Returned byte array will reference same memory as this ResponseAPDU, meaning that any change made to it will reflect on this ResponseAPDU */
get data() {
return this.getData();
}
/** Overwrites current data with new data */
setData(inData) {
const sw1 = this.byteArray[this.bLength - 2];
const sw2 = this.byteArray[this.bLength - 1];
let inByteArray;
try {
inByteArray = (0, utils_1.importBinData)(inData);
}
catch (error) {
throw new Error(`Could not set ResponseAPDU data field: ${error.message}`);
}
const requiredByteLength = inByteArray.byteLength + 2;
if (requiredByteLength <= 2) {
this.byteArray.set([sw1, sw2]);
this.bLength = 2;
return this;
}
if (this.byteArray.byteLength < requiredByteLength)
this.byteArray = new Uint8Array(requiredByteLength);
this.byteArray.set(inByteArray);
this.byteArray.set([sw1, sw2], inByteArray.byteLength);
this.bLength = requiredByteLength;
return this;
}
/** Overwrites current data with new data */
set data(data) {
this.setData(data);
}
/** Appends new data to the end of the existing data */
addData(inData) {
let inByteArray;
try {
inByteArray = (0, utils_1.importBinData)(inData);
}
catch (error) {
throw new Error(`Could not add data to ResponseAPDU: ${error.message}`);
}
if (inByteArray.byteLength <= 0) {
return this;
}
const requiredByteLength = inByteArray.byteLength + this.bLength;
const sw1 = this.byteArray[this.bLength - 2];
const sw2 = this.byteArray[this.bLength - 1];
if (this.byteArray.byteLength < requiredByteLength) {
const newByteArray = new Uint8Array(requiredByteLength);
newByteArray.set(this.data);
this.byteArray = newByteArray;
}
this.byteArray.set(inByteArray, this.dataLength);
this.byteArray.set([sw1, sw2], requiredByteLength - 2);
this.bLength = requiredByteLength;
return this;
}
/** Returned byte array will reference same memory as this ResponseAPDU, meaning that any change made to it will reflect on this ResponseAPDU */
getStatus() {
return this.byteArray.subarray(this.dataLength, this.bLength);
}
/** Returned byte array will reference same memory as this ResponseAPDU, meaning that any change made to it will reflect on this ResponseAPDU */
get status() {
return this.getStatus();
}
/** Overwrites current status bytes with new status bytes */
setStatus(inData) {
let inByteArray;
try {
inByteArray = (0, utils_1.importBinData)(inData);
}
catch (error) {
throw new Error(`Could not set ResponseAPDU status field: ${error.message}`);
}
if (inByteArray.byteLength !== 2)
throw new Error(`Could not set ResponseAPDU status field. Expected exactly 2 bytes of data; Received: ${inByteArray.byteLength} bytes`);
this.byteArray.set(inByteArray, this.dataLength);
return this;
}
/** Overwrites current status bytes with new status bytes */
set status(data) {
this.setStatus(data);
}
/** Tries to decode response status and returns a descriptive string. */
get meaning() {
return (0, statusDecode_1.default)(this.status);
}
/** Returns `true` if resporse status(SW1+SW2) is `0x9000` */
get isOk() {
if (this.status[0] === 0x90 && this.status[1] === 0x00)
return true;
return false;
}
/** Returns `true` if resporse SW1 is `0x61` */
get hasMoreBytesAvailable() {
if (this.status[0] === 0x61)
return true;
return false;
}
/** Returns `true` if resporse SW1 is `0x6C` */
get isWrongLe() {
if (this.status[0] === 0x6c)
return true;
return false;
}
/**
* In case response status SW1 is `0x61` or `0x6CXX`, returns SW2.
*
* `0` otherwise.
*/
get availableResponseBytes() {
if (this.status[0] === 0x61 || this.status[0] === 0x6c)
return this.status[1];
return 0;
}
}
exports.ResponseApdu = ResponseApdu;
ResponseApdu.DEF_DATA_BYTES_LENGTH = 256; // no hard limit, but most of the responses will be within 256 bytes of data
/** Thrown if response status bytes are different from "0x9000" */
function assertResponseIsOk(resp) {
if (!resp.isOk) {
throw new Error(`Error response: [${resp.toString()}](${resp.meaning})`);
}
}
exports.default = ResponseApdu;