"use strict"; /* Compute the (p7-like) checksum of a buffer */ function p7Checksum(buffer, length) { //Sum individual bytes in Subtype (ST) and following fields let checksum = 0; let i = 1; for(length -= 2 ; i < length ; i++ ) checksum+=buffer[i]; /* NOT + 1, as defined in fxReverse documentation. * Be sure it's under 256! */ return (((~checksum) + 1) & 0xFF); } /* Return an array with the ascii values of each hex digit*/ function p7HexToAscii(hexNumber, digits) { return p7StringToAscii(hexNumber.toString(16).toUpperCase().padStart(digits, 0)); } /* Return an array with the ascii value of each character */ function p7StringToAscii(string) { return string.split('').map(letter => letter.charCodeAt(0)); } /* Create a basic (Non-Extended) packet and push it to p7SendBuffer */ /* return it's length (6) */ function p7MakeBasicPacket(type, subtype) { //Set Type (T) field p7SendBuffer.set([type], 0); //Set Subtype (ST) field p7SendBuffer.set(p7HexToAscii(subtype, 2), 1); //Set Extended (EX) field p7SendBuffer.set([0x30], 3); //Set Checksum (CS) field p7SendBuffer.set(p7HexToAscii(p7Checksum(p7SendBuffer, 6), 2), 4); //Return basic packet length return 6; } /* Fill Data (D) field of a command packet in p7SendBuffer */ /* return Data (D) field length */ function p7MakeCommandPacketDataField(forceOverwrite, dataType, fileSize, commandArguments) { //Set Overwrite (OW) field p7SendBuffer.set([0x30, (forceOverwrite) ? ((forceOverwrite == 1) ? 0x31 : 0x32) : 0x30], 8); //Set Data type (DT) field ???? p7SendBuffer.set([0x30, 0x30], 10); //Set Filesize (FS) field p7SendBuffer.set(p7HexToAscii(fileSize, 8), 12); let packetSize = 20; //Set Size of Data {1-6} (SD{1-6}) field for (let i = 0 ; i < 6 ; packetSize += 2 , i++) p7SendBuffer.set((commandArguments[i]) ? p7HexToAscii(commandArguments[i].length, 2) : [0x30, 0x30], packetSize); //Set Data {1-6} (D{1-6}) field for (let i = 0 ; i < 6 ; i++) if (commandArguments[i]) { p7SendBuffer.set(p7StringToAscii(commandArguments[i]), packetSize); packetSize += commandArguments[i].length; } return packetSize - 8; /* Type, Subtype, Extended, Data size fields of the packet */; } /* Create an extended command packet and push it to p7SendBuffer */ /* return it's length */ function p7MakeExtendedCommandPacket(subtype, forceOverwrite, dataType, fileSize, commandArguments) { //Set Type (T) field p7SendBuffer.set([0x01], 0); //Set Subtype (ST) field p7SendBuffer.set(p7HexToAscii(subtype, 2), 1); //Set Extended (EX) field p7SendBuffer.set([0x31], 3); //Set Data (D) field let dataSize = p7MakeCommandPacketDataField(forceOverwrite, dataType, fileSize, commandArguments); //Set Data size (DS) field p7SendBuffer.set(p7HexToAscii(dataSize, 4), 4); let packetSize = dataSize + 10 /* Type, Subtype, Extended, Data size and Checksum fields */; //Set Checksum (CS) field p7SendBuffer.set(p7HexToAscii(p7Checksum(p7SendBuffer, packetSize), 2), packetSize - 2); //Return it's length return packetSize; } /* Create a data packet and push it to p7SendBuffer */ /* return it's length */ function p7MakeDataPacket(subType, totalNumber, currentNumber, data) { packetSize = data.length + 18 /* Type, Subtype, Extended, Data size, Data['Total Number'], Data['Current number'] and checksum fields */; //Set Type (T) field p7SendBuffer.set([0x02], 0); //Set Subtype (ST) field p7SendBuffer.set(p7HexToAscii(subType, 2), 1); //Set Extended (EX) field p7SendBuffer.set([0x31], 3); //Set Data size (DS) field p7SendBuffer.set(p7HexToAscii(data.length + 8 /* Total number (TN) and Current number (CN) fields */, 4), 4); //Set Total number (TN) field p7SendBuffer.set(p7HexToAscii(totalNumber, 4), 8); //Set Current number (CN) field p7SendBuffer.set(p7HexToAscii(currentNumber, 4), 12); //Set Data (DD) field p7SendBuffer.set(data, 16); //Set Checksum (CS) field p7SendBuffer.set(p7HexToAscii(p7Checksum(p7SendBuffer, packetSize), 2), packetSize - 2); //Return it's length return packetSize; } /* Receive a packet and parse it's properties to p7ReceivedPacketInfo */ /* Make sure their aren't checksums error (ask for a resend if necessary) */ async function p7ReceivePacket() { console.log("%cReceiving...", "color: orange"); let checksumError = 0; let err = 0; do { //Send an error packet with subtype resend request if there was a checksum error if (checksumError) if (err = await p7SendBasicPacket(p7PacketType.error, errorSubtype.resendRequest)) return "Couldn't send an error packet with resend request subtype (there was a checksum error) : " + err; let transferInResult = 0; //Recieve and parse the packet do { transferInResult = await device.transferIn(2, 64).then( (result) => { return result; }, (error) => { err = error; }); if (err) return "Couldn't receive the first 64 bytes of the packet : " + err; } while(!transferInResult.data.byteLength); p7ReceivedPacketInfo.packetSize = 4; /*Type, Subtype and Extended field*/ //Copy the received buffer to p7ReceiveBuffer p7ReceiveBuffer.set(new Uint8Array(transferInResult.data.buffer), 0); //Set Type (T) field p7ReceivedPacketInfo.type = p7ReceiveBuffer[0]; //Set Subtype (ST) field p7ReceivedPacketInfo.subtype = Number('0x' + String.fromCharCode(p7ReceiveBuffer[1], p7ReceiveBuffer[2]), 16); //Set Extended (EX) field p7ReceivedPacketInfo.extended = (p7ReceiveBuffer[3] === 0x31); if (!p7ReceivedPacketInfo.extended) { p7ReceivedPacketInfo.packetSize += 2; /*Checksum field*/ //Set Checksum (CS) field p7ReceivedPacketInfo.checksum = Number('0x' + String.fromCharCode(p7ReceiveBuffer[4], p7ReceiveBuffer[5]), 16); //Compute the checksum checksumError = (p7ReceivedPacketInfo.checksum !== p7Checksum(p7ReceiveBuffer, p7ReceivedPacketInfo.packetSize)); } else { p7ReceivedPacketInfo.packetSize += 4; /*Data size field*/ //Set Data size (DS) field p7ReceivedPacketInfo.dataSize = Number('0x' + String.fromCharCode.apply(null, p7ReceiveBuffer.slice(4, 8)), 10); //Add the checksum bytes p7ReceivedPacketInfo.dataSize+=2; p7ReceivedPacketInfo.packetSize += (p7ReceivedPacketInfo.dataSize > 56) ? 56 : p7ReceivedPacketInfo.dataSize; p7ReceivedPacketInfo.dataSize -= 56; //Receive the rest of the data while(p7ReceivedPacketInfo.dataSize > 0) { transferInResult = await device.transferIn(2, 64).then( (result) => { return result; }, (error) => { err = error; }); if (err) return "Couldn't receive the " + packetSize + "th and following 64 bytes of the packet : " + err; p7ReceiveBuffer.set(new Uint8Array(transferInResult.data.buffer), p7ReceivedPacketInfo.packetSize); p7ReceivedPacketInfo.packetSize += (p7ReceivedPacketInfo.dataSize > 64) ? 64 : p7ReceivedPacketInfo.dataSize; p7ReceivedPacketInfo.dataSize-=64; } //Set Checksum (CS) field p7ReceivedPacketInfo.checksum = Number('0x' + String.fromCharCode(p7ReceiveBuffer[p7ReceivedPacketInfo.packetSize - 2], p7ReceiveBuffer[p7ReceivedPacketInfo.packetSize - 1]), 16); //Compute the checksum checksumError = (p7ReceivedPacketInfo.checksum !== p7Checksum(p7ReceiveBuffer, p7ReceivedPacketInfo.packetSize)); } } while (checksumError) console.log(p7ReceivedPacketInfo); return 0; } /* Send a packet */ /* Receive the response and resend the packet if the response was an error with subtype resend request */ /* Return 0 on success and the error on failure */ async function p7SendPacket(packetLength) { console.log(p7SendBuffer.slice(0, packetLength)); let err = 0; //Send the packet and return the error on failure if (err = await device.transferOut(1, p7SendBuffer.slice(0, packetLength)).then((success) => 0, (failure) => failure)) return "Couldn't send the packet : " + err; //Receive the packet and return the error on failure if (err = await p7ReceivePacket()) return "Couldn't receive the response packet : " + err; //Resend the packet if the response was an error packet with subtype resend request and return the error on failure for (let i = 0 ; (p7ReceivedPacketInfo.type === p7PacketType.error && p7ReceivedPacketInfo.subtype === errorSubtype.resendRequest) ; i++) { if (err = await device.transferOut(1, p7SendBuffer.slice(0, packetLength)).then((success) => {console.log(success) ; return 0}, (failure) => {return failure})) return "Couldn't send the packet : " + err; if (err = await p7ReceivePacket()) return "Couldn't receive the response packet : " + err; if (i > 2) return "Too much resend request, the calculator might be broken."; } //Everything went right return 0; } /* Send a basic (non-Extended) packet */ /* Return 0 on success and the error on failure */ async function p7SendBasicPacket(type, subtype) { console.log("%cSending basic packet...", "color: green"); return await p7SendPacket(p7MakeBasicPacket(type, subtype)); } /* Send an extended command packet */ /* Return 0 on success and the error on failure */ async function p7SendExtendedCommandPacket(subtype, forceOverwrite, dataType, fileSize, commandArguments) { console.log("%cSending extended command packet...", "color: green"); return await p7SendPacket(p7MakeExtendedCommandPacket(subtype, forceOverwrite, dataType, fileSize, commandArguments)); } /* Send an extended data packet */ /* Return 0 on success and the error on failure */ async function p7SendDataPacket(subtype, totalNumber, currentNumber, data) { console.log("%cSending data packet...", "color: green"); return p7SendPacket(p7MakeDataPacket(subtype, totalNumber, currentNumber, data)); } /* Split the data into 256 bytes-long buffer and send all of them */ /* Return 0 on success and the error on failure */ async function p7SendData(subtype, data) { let err = 0; let lastPacketSize = (data.byteLength & 255); //Equivalent to data.byteLength % 256, but way faster ! let totalPacketNumber = (data.byteLength >> 8); //Equivalent to data.byteLength / 256, but way way waaaaaay faster, and return an integer quotient, not a decimal one !! console.log(lastPacketSize + ' ' + totalPacketNumber); totalPacketNumber += (lastPacketSize) ? 1 : 0; let currentPacketNumber = 1; let currentPacketFirstByteIndex = 0; //Send all the 256 bytes-long data packets for (; currentPacketNumber < totalPacketNumber ; currentPacketNumber++) if (err = await p7SendDataPacket(subtype, totalPacketNumber, currentPacketNumber, data.slice(currentPacketFirstByteIndex, (currentPacketFirstByteIndex += 256)))) return 'Couldn\'t send the ' + currentPacketNumber + ' data packet : ' + err; //Send the last data packet (if it exists) if (lastPacketSize) if (err = await p7SendDataPacket(subtype, totalPacketNumber, currentPacketNumber, data.slice(currentPacketFirstByteIndex, currentPacketFirstByteIndex + lastPacketSize))) return 'Couldn\'t send the last data packet (' + lastPacketSize + ' bytes) : ' + err; console.log(p7SendBuffer.slice(0, 64)); console.log(p7ReceivedPacketInfo); //Everything went allright return 0; } /* Set all device information feilds, and trim string's overage ('ÿ') */ function p7DecodeExtendedAckPacket() { p7DeviceInformation['Hardware identifier'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(8, 16)); p7DeviceInformation['Processor identifier'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(16, 32)); p7DeviceInformation['Preprogrammed ROM capacity'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(32, 40)); p7DeviceInformation['Flash ROM capacity'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(40, 48)); p7DeviceInformation['RAM capacity'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(48, 56)); p7DeviceInformation['Prepogrammed ROM version'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(56, 72)).replaceAll('ÿ', '');; p7DeviceInformation['Boot code version'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(72, 88)).replaceAll('ÿ', ''); p7DeviceInformation['Boot code offset'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(88, 96)).replaceAll('ÿ', ''); p7DeviceInformation['Boot code size'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(96, 104)).replaceAll('ÿ', ''); p7DeviceInformation['Os code version'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(104, 120)).replaceAll('ÿ', ''); p7DeviceInformation['Os code offset'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(120, 128)); p7DeviceInformation['Os code size'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(128, 136)); p7DeviceInformation['Protocol version'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(136, 140)); p7DeviceInformation['Product ID'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(140, 156)).replaceAll('ÿ', '');; p7DeviceInformation['Name set by user in system'] = String.fromCharCode.apply(null, p7ReceiveBuffer.slice(156, 172)).replaceAll('ÿ', ''); return 0; } /* Decode a terminate packet and close the connection */ async function p7DecodeTerminatePacket() { let err = 0; switch (p7ReceivedPacketInfo.subtype) { case terminateSubtype.default: alert('The calculator asked to close the connection.'); case terminateSubtype.userRequest: alert('The calculator said you closed the connection!'); case terminateSubtype.timeouts: alert('The calculator closed the connection because it was inactive for more than 6 minutes.'); case terminateSubtype.overwriteRequest: alert('The calculator didn\'t accept the overwrite request and closed the connection.'); default: alert('The calculator asked to terminate communication for an unknown reason : 0x' + p7ReceivedPacketInfo.subtype.toString(16) + ' (the hex code of the reason).'); } if (err = await p7SendBasicPacket(p7PacketType.ack, ackSubtype.default)) return 'Couldn\'t send acknowledgement packet : ' + err; return (err = await p7Exit()) ? 'Couldn\'t exit properly : ' + err : 0; } /* Return the value (as an number) of Filesize (FS) subfield of Data (D) field of an extended command packet */ function p7CommandPacketGetFilesize() { return Number('0x' + String.fromCharCode.apply(null, p7ReceiveBuffer.slice(12, 20)), 16); } /* Return the trimed receive buffer only containing Data (DD) subfield of Data (D) field of an extended command packet */ function p7CommandPacketGetField(field) { let fieldStart = 32; /* D1 field first byte's position */ for (let i = 0 ; i < 2 * field ; i += 2) fieldStart += Number('0x' + String.fromCharCode(p7ReceiveBuffer[20 + i], p7ReceiveBuffer[21 + i]), 16); let fieldEnd = fieldStart + Number('0x' + String.fromCharCode(p7ReceiveBuffer[20 + 2 * field], p7ReceiveBuffer[21 + 2 * field]), 16); return p7ReceiveBuffer.slice(fieldStart, fieldEnd); } /* Connect to the calculator */ /* Return 0 on success and the error on failure */ async function p7Initialization() { let err = 0; err = await navigator.usb.requestDevice({filters:[{'vendorId': 0x07cf , 'productId': 0x6101}, {'vendorId': 0x07cf , 'productId': 0x6102}]}).then( (selectedDevice) => { device = selectedDevice; return 0; }, (error) => error ); if (err) return "Couldn't get the selected device (maybe the user didn't select one) : " + err; if (err = await device.open().then(() => 0, (error) => error)) return "Couldn't connect to the calculator : " + err; //It's the only existing configuration (should be selected by default), just checkin' if (err = await device.selectConfiguration(1).then(() => 0, (error) => error)) return "Couldn't select the configuration (configurationValue : 1) : " + err; if (err = await device.claimInterface(0 /* It's the only existing one */).then(() => 0, (error) => error)) return "Couldn't claim the interface (interfaceNumber : 0) : " + err; console.log(device); console.log('%cInitialisation packet', "font-size: 14px; color: white"); if (err = await p7SendBasicPacket(p7PacketType['check'], checkSubtype['initialization'])) return "Failed to send init packet : " + err; if (!(p7ReceivedPacketInfo.type === p7PacketType.ack && p7ReceivedPacketInfo.subtype === ackSubtype.default)) return "Not the expected response packet."; /* TODO: handle this error */ console.log('%cRequesting device\'s informations', "font-size: 14px; color: white"); if (err = await p7SendBasicPacket(p7PacketType['command'], sysCommandSubtype['getDeviceInfo'])) return "Failed to request device info : " + err; if (!(p7ReceivedPacketInfo.type === p7PacketType.ack && p7ReceivedPacketInfo.subtype === ackSubtype.extendedAck)) return "Not the expected response packet."; /* TODO: add a handle to this error */ p7DecodeExtendedAckPacket(); return 0; } /* Disconnect from the calculator */ /* Return 0 on success and the error on failure */ async function p7Exit() { let err = 0; return (err = await device.close().then((success) => {return 0}, (failure) => {return failure})) ? "There was an error disconnecting from the calculator : " + err : 0; } /* Return an object describing a file (or a directory) */ function createFileObject(device, path, name, size) { //Directories never have size, only their own path return (size) ? {type: 'file', path: path, name: name, size: size} : {type: 'directory', path: path}; }