UNPKG

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
"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;