wipe and provision eeprom

This commit is contained in:
liamcottle
2024-07-15 03:45:11 +12:00
parent f56e726c51
commit 060577ab4b
2 changed files with 340 additions and 20 deletions

View File

@@ -60,6 +60,20 @@
</button> </button>
</div> </div>
<div>
<div>4. Provision eeprom with device info, checksum and signature.</div>
<button @click="provision" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Provision
</button>
</div>
<div>
<div>0. Extra Tools</div>
<button @click="wipe" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Wipe EEPROM
</button>
</div>
</div> </div>
<script> <script>
@@ -180,6 +194,148 @@
await rnode.close(); await rnode.close();
}, },
packInt(value) {
const buffer = new ArrayBuffer(4); // 4 bytes for a 32-bit integer
const view = new DataView(buffer);
view.setUint32(0, value, false); // false for big-endian
return new Uint8Array(buffer);
},
unpackInt(byteArray) {
const buffer = new Uint8Array(byteArray).buffer; // Get the underlying ArrayBuffer from the byte array
const view = new DataView(buffer);
return view.getUint32(0, false); // false for big-endian
},
async wipe() {
// ask for serial port
const serialPort = await this.askForSerialPort();
if(!serialPort){
return;
}
// ask user to confirm
if(!confirm("Are you sure you want to wipe the eeprom on this device?")){
return;
}
// check if device is an rnode
const rnode = await RNode.fromSerialPort(serialPort);
const isRNode = await rnode.detect();
if(!isRNode){
alert("Selected device is not an RNode!");
return;
}
// wipe eeprom
console.log("wiping eeprom");
await rnode.wipeRom();
console.log("wiping eeprom: done");
// must reboot device after wipe
await rnode.reset();
await rnode.close();
// done
alert("eeprom has been wiped!");
},
async provision() {
// ask for serial port
const serialPort = await this.askForSerialPort();
if(!serialPort){
return;
}
// check if device is an rnode
const rnode = await RNode.fromSerialPort(serialPort);
const isRNode = await rnode.detect();
if(!isRNode){
alert("Selected device is not an RNode!");
return;
}
const rom = await rnode.getRomAsObject();
const details = rom.parse();
if(details){
console.log(details);
alert("Eeprom is already provisioned. You must wipe it to reprovision!");
await rnode.close();
return;
}
console.log("device is not provisioned yet, doing it now...");
// determine device info
const product = ROM.PRODUCT_RAK4631;
const model = ROM.MODEL_12;
const hardwareRevision = 0x1;
const serialNumber = 1;
const timestampInSeconds = Math.floor(Date.now() / 1000);
const serialBytes = this.packInt(serialNumber);
const timestampBytes = this.packInt(timestampInSeconds);
// compute device info checksum
const checksum = Utils.md5([
product,
model,
hardwareRevision,
...serialBytes,
...timestampBytes,
]);
console.log("checksum", checksum);
// write device info to eeprom
console.log("writing device info");
await rnode.writeRom(ROM.ADDR_PRODUCT, product);
await rnode.writeRom(ROM.ADDR_MODEL, model);
await rnode.writeRom(ROM.ADDR_HW_REV, hardwareRevision);
await rnode.writeRom(ROM.ADDR_SERIAL, serialBytes[0]);
await rnode.writeRom(ROM.ADDR_SERIAL + 1, serialBytes[1]);
await rnode.writeRom(ROM.ADDR_SERIAL + 2, serialBytes[2]);
await rnode.writeRom(ROM.ADDR_SERIAL + 3, serialBytes[3]);
await rnode.writeRom(ROM.ADDR_MADE, timestampBytes[0]);
await rnode.writeRom(ROM.ADDR_MADE + 1, timestampBytes[1]);
await rnode.writeRom(ROM.ADDR_MADE + 2, timestampBytes[2]);
await rnode.writeRom(ROM.ADDR_MADE + 3, timestampBytes[3]);
console.log("writing device info: done");
// write checksum to eeprom
console.log("writing checksum");
for(var i = 0; i < 16; i++){
await rnode.writeRom(ROM.ADDR_CHKSUM + i, checksum[i]);
}
console.log("writing checksum: done");
// write signature to eeprom
// fixme: actually implement signature, for now it's just zeroed out
console.log("writing signature");
for(var i = 0; i < 128; i++){
// await rnode.writeRom(ROM.ADDR_SIGNATURE + i, signature[i]);
await rnode.writeRom(ROM.ADDR_SIGNATURE + i, 0x00); // fixme: fake signature
}
console.log("writing signature: done");
// write info lock byte to eeprom
console.log("writing lock byte");
await rnode.writeRom(ROM.ADDR_INFO_LOCK, ROM.INFO_LOCK_BYTE);
console.log("writing lock byte: done");
// todo get partition hash from release.json OR directly from the firmware.bin
// partition_filename = fw_filename.replace(".zip", ".bin")
// partition_hash = get_partition_hash(rnode.platform, UPD_DIR+"/"+selected_version+"/"+partition_filename)
// todo set firmware hash in eeprom
// RNS.log("Setting firmware checksum...")
// rnode.set_firmware_hash(partition_hash)
// done
await rnode.reset();
await rnode.close();
console.log("done");
},
}, },
}).mount('#app'); }).mount('#app');
</script> </script>

204
rnode.js
View File

@@ -1,3 +1,49 @@
class Utils {
/**
* Waits for the provided milliseconds, and then resolves.
* @param millis
* @returns {Promise<void>}
*/
static async sleepMillis(millis) {
await new Promise((resolve) => {
setTimeout(resolve, millis);
});
}
static bytesToHex(bytes) {
for(var hex = [], i = 0; i < bytes.length; i++){
var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
hex.push((current >>> 4).toString(16));
hex.push((current & 0xF).toString(16));
}
return hex.join("");
}
static md5(data) {
var bytes = [];
const hash = CryptoJS.MD5(CryptoJS.enc.Hex.parse(this.bytesToHex(data)));
for(var i = 0; i < hash.sigBytes; i++){
bytes.push((hash.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
return bytes;
}
static packUInt32BE(value) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, value, false);
return new Uint8Array(buffer);
}
static unpackUInt32BE(byteArray) {
const buffer = new Uint8Array(byteArray).buffer;
const view = new DataView(buffer);
return view.getUint32(0, false);
}
}
class RNode { class RNode {
KISS_FEND = 0xC0; KISS_FEND = 0xC0;
@@ -25,18 +71,31 @@ class RNode {
CMD_DEV_HASH = 0x56; CMD_DEV_HASH = 0x56;
CMD_FW_VERSION = 0x50; CMD_FW_VERSION = 0x50;
CMD_ROM_READ = 0x51; CMD_ROM_READ = 0x51;
CMD_ROM_WRITE = 0x52;
CMD_CONF_SAVE = 0x53; CMD_CONF_SAVE = 0x53;
CMD_CONF_DELETE = 0x54; CMD_CONF_DELETE = 0x54;
CMD_FW_HASH = 0x58;
CMD_UNLOCK_ROM = 0x59;
ROM_UNLOCK_BYTE = 0xF8;
CMD_HASHES = 0x60; CMD_HASHES = 0x60;
CMD_FW_UPD = 0x61;
CMD_BT_CTRL = 0x46; CMD_BT_CTRL = 0x46;
CMD_BT_PIN = 0x62; CMD_BT_PIN = 0x62;
CMD_DETECT = 0x08; CMD_DETECT = 0x08;
DETECT_REQ = 0x73; DETECT_REQ = 0x73;
DETECT_RESP = 0x46; DETECT_RESP = 0x46;
RADIO_STATE_OFF = 0x00;
RADIO_STATE_ON = 0x01;
RADIO_STATE_ASK = 0xFF;
CMD_ERROR = 0x90
ERROR_INITRADIO = 0x01
ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03
PLATFORM_AVR = 0x90; PLATFORM_AVR = 0x90;
PLATFORM_ESP32 = 0x80; PLATFORM_ESP32 = 0x80;
PLATFORM_NRF52 = 0x70; PLATFORM_NRF52 = 0x70;
@@ -462,6 +521,75 @@ class RNode {
]); ]);
} }
async setFrequency(frequencyInHz) {
const c1 = frequencyInHz >> 24;
const c2 = frequencyInHz >> 16 & 0xFF;
const c3 = frequencyInHz >> 8 & 0xFF;
const c4 = frequencyInHz & 0xFF;
await this.sendKissCommand([
this.CMD_FREQUENCY,
c1,
c2,
c3,
c4,
]);
}
async setBandwidth(bandwidthInHz) {
const c1 = bandwidthInHz >> 24;
const c2 = bandwidthInHz >> 16 & 0xFF;
const c3 = bandwidthInHz >> 8 & 0xFF;
const c4 = bandwidthInHz & 0xFF;
await this.sendKissCommand([
this.CMD_BANDWIDTH,
c1,
c2,
c3,
c4,
]);
}
async setTxPower(db) {
await this.sendKissCommand([
this.CMD_TXPOWER,
db,
]);
}
async setSpreadingFactor(spreadingFactor) {
await this.sendKissCommand([
this.CMD_SF,
spreadingFactor,
]);
}
async setCodingRate(codingRate) {
await this.sendKissCommand([
this.CMD_CR,
codingRate,
]);
}
async setRadioStateOn() {
await this.sendKissCommand([
this.CMD_RADIO_STATE,
this.RADIO_STATE_ON,
]);
}
async setRadioStateOff() {
await this.sendKissCommand([
this.CMD_RADIO_STATE,
this.RADIO_STATE_OFF,
]);
}
// setTNCMode // setTNCMode
async saveConfig() { async saveConfig() {
await this.sendKissCommand([ await this.sendKissCommand([
@@ -478,6 +606,46 @@ class RNode {
]); ]);
} }
async indicateFirmwareUpdate() {
await this.sendKissCommand([
this.CMD_FW_UPD,
0x01,
]);
}
async setFirmwareHash(hash) {
await this.sendKissCommand([
this.CMD_FW_HASH,
...hash,
]);
}
async writeRom(address, value) {
// write to rom
await this.sendKissCommand([
this.CMD_ROM_WRITE,
address,
value,
]);
// wait a bit to allow device to write to rom
await Utils.sleepMillis(85);
}
async wipeRom() {
await this.sendKissCommand([
this.CMD_UNLOCK_ROM,
this.ROM_UNLOCK_BYTE,
]);
// wiping can take up to 30 seconds
await Utils.sleepMillis(30000);
}
async getRomAsObject() { async getRomAsObject() {
const rom = await this.getRom(); const rom = await this.getRom();
return new ROM(rom); return new ROM(rom);
@@ -622,12 +790,12 @@ class ROM {
} }
getCalculatedChecksum() { getCalculatedChecksum() {
return this.md5([ return Utils.md5([
this.getProduct(), this.getProduct(),
this.getModel(), this.getModel(),
this.getHardwareRevision(), this.getHardwareRevision(),
this.getSerialNumber(), ...this.getSerialNumber(),
this.getMade(), ...this.getMade(),
]); ]);
} }
@@ -665,15 +833,6 @@ class ROM {
return this.eeprom[ROM.ADDR_CONF_OK] === ROM.CONF_OK_BYTE; return this.eeprom[ROM.ADDR_CONF_OK] === ROM.CONF_OK_BYTE;
} }
md5(data) {
var bytes = [];
const hash = CryptoJS.MD5(data);
for(var i = 0; i < hash.sigBytes; i++){
bytes.push((hash.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
return bytes;
}
parse() { parse() {
// ensure info lock byte is set // ensure info lock byte is set
@@ -681,18 +840,23 @@ class ROM {
return null; return null;
} }
// parse expected details // convert to hex
const checksumHex = Utils.bytesToHex(this.getChecksum());
const calculatedChecksumHex = Utils.bytesToHex(this.getCalculatedChecksum());
const signatureHex = Utils.bytesToHex(this.getSignature());
// add details
var details = { var details = {
is_provisioned: true, is_provisioned: true,
is_configured: this.isConfigured(), is_configured: this.isConfigured(),
product: this.getProduct(), product: this.getProduct(),
model: this.getModel(), model: this.getModel(),
hardware_revision: this.getHardwareRevision(), hardware_revision: this.getHardwareRevision(),
serial_number: this.getSerialNumber(), serial_number: Utils.unpackUInt32BE(this.getSerialNumber()),
made: this.getMade(), made: Utils.unpackUInt32BE(this.getMade()),
checksum: this.getChecksum(), checksum: checksumHex,
signature: this.getSignature(), calculated_checksum: calculatedChecksumHex,
calculated_checksum: this.getCalculatedChecksum(), signature: signatureHex,
} }
// if configured, add configuration to details // if configured, add configuration to details
@@ -707,7 +871,7 @@ class ROM {
}; };
} }
// if checksum in eeprom does not match calculated checksum, it is not provisioned // if checksum in eeprom does not match checksum calculated from info, it is not provisioned
if(details.checksum !== details.calculated_checksum){ if(details.checksum !== details.calculated_checksum){
details.is_provisioned = false; details.is_provisioned = false;
} }