// Item type codes const U8 = 0x01; const I8 = 0x11; const U16 = 0x02; const I16 = 0x12; const U32 = 0x04; const I32 = 0x14; const U64 = 0x08; const I64 = 0x18; const SZ = 0x21; const BLOB = 0x41; const BLOB_DATA = 0x42; const BLOB_IDX = 0x48; // Few Page constants const HEADER_SIZE = 32; const BITMAPARRAY_OFFSET = 32; const BITMAPARRAY_SIZE_IN_BYTES = 32; const FIRST_ENTRY_OFFSET = 64; const SINGLE_ENTRY_SIZE = 32; const CHUNK_ANY = 0xFF; const ACTIVE = 0xFFFFFFFE; const FULL = 0xFFFFFFFC; const VERSION2 = 0xFE; String.prototype.replaceAt = function(index, character) { return this.substr(0, index) + character + this.substr(index + character.length); }; Array.prototype.replaceAt = function(index, newArray) { return this.slice(0, index).concat(newArray).concat(this.slice(index + newArray.length)); }; async function generate(params) { /* Generate NVS Partition */ let input_size = checkSize(params.size); const input_file = makeFileIterator(await getTemplateFile(params.input)); let binaryOutput = {data: []}; let nvs_obj = nvsOpen(binaryOutput, input_size); logMsg('Creating NVS binary'); let line = input_file.next(); // Comments are skipped while(line.value.startsWith('#')) { line = input_file.next(); } header = line.value.split(','); while(true) { line = input_file.next(); let value = line.value.split(','); if (value.length == 1 && value.includes('')) { break; } data = Object.fromEntries(zipLongest(header, value)); if (data.key in params.data) { data.value = params.data[data.key]; } try { // Check key length if (data.key.length > 15) { throw('Length of key ' + data.key + ' should be <= 15 characters.'); } writeEntry(nvs_obj, data.key, data.type, data.encoding, data.value); } catch(e) { throw(e) } } nvsClose(nvs_obj); return binaryOutput.data; } function nvsOpen(result_obj, input_size) { /* Wrapper to create and NVS class object. This object can later be used to set key-value pairs :param result_obj: File/Stream object to dump resultant binary. If data is to be dumped into memory, one way is to use BytesIO object :param input_size: Size of Partition :return: NVS class instance */ return new NVS(result_obj, input_size); } function writeEntry(nvs_instance, key, datatype, encoding, value) { /* Wrapper to set key-value pair in NVS format :param nvs_instance: Instance of an NVS class returned by nvsOpen() :param key: Key of the data :param datatype: Data type. Valid values are "file", "data" and "namespace" :param encoding: Data encoding. Valid values are "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "string", "binary", "hex2bin" and "base64" :param value: Data value in ascii encoded string format for "data" datatype and filepath for "file" datatype :return: None */ if (datatype == 'file') { throw("Files are not supported"); } if (datatype == 'namespace') { nvs_instance.writeNamespace(key); } else { nvs_instance.writeEntry(key, value, encoding); } } function nvsClose(nvs_instance) { /* Wrapper to finish writing to NVS and write data to file/stream object provided to nvsOpen method :param nvs_instance: Instance of NVS class returned by nvsOpen() :return: None */ nvs_instance.finish(); } function checkSize(size) { /* Checks for input partition size :param size: Input partition size */ try { // Set size let input_size = parseInt(size) if (input_size % 4096 != 0) { throw('Size of partition must be multiple of 4096'); } // Update size as a page needs to be reserved of size 4KB input_size = input_size - Page.PAGE_PARAMS['max_size'] if (input_size < (2 * Page.PAGE_PARAMS['max_size'])) { throw('Minimum NVS partition size needed is 0x3000 bytes.') } return input_size } catch(e) { errorMsg(e); return; } } class Page { static PAGE_PARAMS = { 'max_size': 4096, 'max_old_blob_size': 1984, 'max_new_blob_size': 4000, 'max_entries': 126 } constructor(page_num, isRsrvPage) { this.entry_num = 0; this.bitmap_array = []; this.page_buf = new Array(Page.PAGE_PARAMS['max_size']).fill(0xFF); if (!isRsrvPage) { this.bitmap_array = this.createBitmapArray(); this.setHeader(page_num); } } setHeader(page_num) { // set page state to active let page_header = new Array(32).fill(0xFF); page_header = page_header.replaceAt(0, struct.pack(' Page.PAGE_PARAMS['max_old_blob_size']) { if (encoding == 'string') { throw new InputError(' Input File: Size (' + datalen + ') exceeds max allowed length `' + Page.PAGE_PARAMS['max_old_blob_size'] + '` bytes for key `' + key + '`.') } } // Calculate no. of entries data will require let rounded_size = (datalen + 31) & ~31; let data_entry_count = parseInt(rounded_size / 32); let total_entry_count = data_entry_count + 1; // +1 for the entry header // Check if page is already full and new page is needed to be created right away if (this.entry_num >= Page.PAGE_PARAMS['max_entries']) { throw new PageFullError(); } else if ((this.entry_num + total_entry_count) >= Page.PAGE_PARAMS['max_entries']) { if (!(['hex2bin', 'binary', 'base64'].includes(encoding))) { throw new PageFullError() } } // Entry header let entry_struct = new Array(32).fill(0xFF); // Set Namespace Index entry_struct[0] = ns_index; // Set Span if (encoding == 'string') { entry_struct[2] = data_entry_count + 1; } // Set Chunk Index entry_struct[3] = CHUNK_ANY; // set key let key_array = new Array(16).fill(0x00); entry_struct = entry_struct.replaceAt(8, key_array); entry_struct = entry_struct.replaceAt(8, toByteArray(key)); // set Type if (encoding == 'string') { entry_struct[1] = SZ; } else if (encoding in ['hex2bin', 'binary', 'base64']) { entry_struct[1] = BLOB; } if (['hex2bin', 'binary', 'base64'].includes(encoding)) { entry_struct = this.writeVarLenBinaryData(entry_struct, ns_index, key,data, datalen, total_entry_count, encoding, nvs_obj) } else { this.writeSinglePageEntry(entry_struct, data, datalen, data_entry_count, nvs_obj) } } /* Low-level function to write data of primitive type into page buffer. */ writePrimitiveData(key, data, encoding, ns_index,nvs_obj) { // Check if entry exceeds max number of entries allowed per page if (this.entry_num >= Page.PAGE_PARAMS['max_entries']) { throw new PageFullError(); } let entry_struct = new Array(32).fill(0xFF); entry_struct[0] = ns_index; // namespace index entry_struct[2] = 0x01; // Span entry_struct[3] = CHUNK_ANY; // write key let key_array = new Array(16).fill(0x00); entry_struct = entry_struct.replaceAt(8, key_array); entry_struct = entry_struct.replaceAt(8, toByteArray(key)); if (encoding == 'u8') { entry_struct[1] = U8; entry_struct = entry_struct.replaceAt(24, struct.pack('