def verify_data(self): from stm32_crc import crc32 data_crc = crc32(self.resource_data[self.data_offset:]) assert data_crc == self.manifest['crc'] for entry in self.table: chunk_crc = crc32(self.get_chunk(entry['file_id'])) assert chunk_crc == entry['crc']
def deserialize(cls, f_in): # Parse manifest: manifest = f_in.read(cls.MANIFEST_SIZE_BYTES) fmt = cls.MANIFEST_FMT (num_files, crc, timestamp) = struct.unpack(fmt, manifest) resource_pack = cls() # Parse table entries: resource_pack.table_entries = [] for n in xrange(num_files): table_entry = f_in.read(cls.TABLE_ENTRY_SIZE_BYTES) fmt = cls.TABLE_ENTRY_FMT file_id, offset, length, crc = struct.unpack(fmt, table_entry) if file_id == 0: break if file_id != n + 1: raise Exception("File ID is expected to be %u, but was %u" % (n + 1, file_id)) resource_pack.table_entries.append((offset, length, crc)) if len(resource_pack.table_entries) != num_files: raise Exception("Number of files in manifest is %u, but actual" "number is %u" % (num_files, n)) # Fetch the contents: for entry in resource_pack.table_entries: offset, length, crc = entry f_in.seek(offset + cls.CONTENT_START_OFFSET) content = f_in.read(length) calculated_crc = stm32_crc.crc32(content) if calculated_crc != crc: raise Exception("Entry %s does not match CRC of content (0x%x)" % (entry, calculated_crc)) resource_pack.contents.append(content) resource_pack.num_files = num_files resource_pack.timestamp = timestamp return resource_pack
def commit(self): if self._crc: crc = self._crc else: crc = stm32_crc.crc32(self._buffer) data = pack("!bII", 3, self._token & 0xFFFFFFFF, crc) self._pebble._send_message("PUTBYTES", data)
def cmd_resource_header(args): if (len(args.resource_pair_list) % 2) != 0: raise Exception( "resource_pair_list list must have an even number of entries") with open(args.output_header, 'w') as output_file: output_file.write(""" #pragma once // // AUTOGENERATED BY tools/generate_resource_code.py // DO NOT MODIFY // #include <stdint.h> #include "{resource_header}" typedef enum {{ INVALID_RESOURCE = 0, DEFAULT_MENU_ICON = 0, // Friendly synonym for use in `PBL_APP_INFO()` calls """.format(resource_header=args.resource_include)) for i in range(1, len(args.resource_pair_list), 2): output_file.write(" RESOURCE_ID_" + args.resource_pair_list[i] + ",\n") output_file.write(""" } ResourceId; """) if args.version_def_name: with open(args.data_file, 'rb') as f: crc = stm32_crc.crc32(f.read()) output_file.write(""" static const ResBankVersion {} = {{ .crc = {}, .timestamp = {} }}; """.format(args.version_def_name, crc, args.timestamp)) output_file.write(""" static const uint32_t resource_crc_table[] = { """) for i in range(0, len(args.resource_pair_list), 2): with open(args.resource_pair_list[i], 'rb') as f: output_file.write(" " + str(stm32_crc.crc32(f.read())) + ",\n") output_file.write('};\n\n')
def save_pbpack(fname, rsrcs): """ Outputs a handful of resources to a file. |rsrcs| is a list of resources, with the first mapping to resource index "1". Although the PebbleOS resource structure permits a sparse mapping -- i.e., one in which one must read the whole resource table to find the index that one desires -- the RebbleOS resource loader simply ignores the ID number in each table entry, and indexes directly in to find what it wants. (And, further, every Pebble pbpack that I can find only has them in order.) So we, in keeping, will only generate things like that. """ # First, turn the resource table into a list of entries, including # index, offset, size, and CRC. def mk_ent(data): assert (len(data) > 0) ent = { "idx": mk_ent.idx, "offset": mk_ent.offset, "size": len(data), "crc": crc32(data), "data": data } mk_ent.offset += len(data) mk_ent.idx += 1 return ent mk_ent.offset = 0 mk_ent.idx = 1 rsrc_ents = [mk_ent(data) for data in rsrcs] with open(fname, 'wb+') as f: f.write(struct.pack('I', len(rsrc_ents))) # Number of resources. # We'll come back to the big CRC later. # Write out the table. f.seek(TAB_OFS) for ent in rsrc_ents: f.write( struct.pack('iiiI', ent["idx"], ent["offset"], ent["size"], ent["crc"])) # Write out the resources themselves. for ent in rsrc_ents: f.seek(RES_OFS + ent["offset"]) f.write(ent["data"]) # Now compute the CRC of the whole show. f.seek(RES_OFS) alldata = f.read() totlen = f.tell() f.seek(4) f.write(struct.pack('I', crc32(alldata))) return totlen
def serialize_manifest(self, crc=None, timestamp=None): if crc is None: all_contents = b"".join(self.contents) crc = stm32_crc.crc32(all_contents) if timestamp is None: timestamp = self.timestamp fmt = self.MANIFEST_FMT return struct.pack(fmt, len(self.table), crc, timestamp)
def populate(manifest, what, filename): with open(filename, "rb") as f: stat = os.fstat(f.fileno()) data = f.read() manifest[what]["crc"] = crc32(data) manifest[what]["size"] = len(data) manifest[what]["timestamp"] = int(stat.st_mtime) return data
def serialize(self, f_out): all_contents = b"".join(self.contents) crc = stm32_crc.crc32(all_contents) table = self.serialize_table() manifest = self.serialize_manifest(crc) f_out.write(manifest) f_out.write(table) f_out.write(all_contents) return crc
def cmd_resource_header(args): if (len(args.resource_pair_list) % 2) != 0: raise Exception("resource_pair_list list must have an even number of entries") with open(args.output_header, 'w') as output_file: output_file.write(""" #pragma once // // AUTOGENERATED BY tools/generate_resource_code.py // DO NOT MODIFY // #include <stdint.h> #include "{resource_header}" typedef enum {{ INVALID_RESOURCE = 0, DEFAULT_MENU_ICON = 0, // Friendly synonym for use in `PBL_APP_INFO()` calls """.format(resource_header=args.resource_include)) for i in range(1, len(args.resource_pair_list), 2): output_file.write(" RESOURCE_ID_" + args.resource_pair_list[i] + ",\n") output_file.write(""" } ResourceId; """) if args.version_def_name: with open(args.data_file, 'rb') as f: crc = stm32_crc.crc32(f.read()) output_file.write(""" static const ResBankVersion {} = {{ .crc = {}, .timestamp = {} }}; """.format(args.version_def_name, crc, args.timestamp)) output_file.write(""" static const uint32_t resource_crc_table[] = { """) for i in range(0, len(args.resource_pair_list), 2): with open(args.resource_pair_list[i], 'rb') as f: output_file.write(" " + str(stm32_crc.crc32(f.read())) + ",\n") output_file.write('};\n\n')
def mk_ent(data): ent = { "idx": mk_ent.idx, "offset": mk_ent.offset, "size": len(data), "crc": crc32(data), "data": data } mk_ent.offset += len(data) mk_ent.idx += 1 return ent
def install_firmware(self, pbz_path, recovery=False): """Install a firmware bundle to the target watch.""" resources = None pbz = zipfile.ZipFile(pbz_path) binary = pbz.read("tintin_fw.bin") # Calculate CRC in advance to avoid timeout on slow hardware bincrc = stm32_crc.crc32(binary) if not recovery: resources = pbz.read("system_resources.pbpack") if resources: rescrc = stm32_crc.crc32(resources) self.system_message("FIRMWARE_START") time.sleep(2) if resources: client = PutBytesClient(self, 0, "SYS_RESOURCES", resources, rescrc) self.register_endpoint("PUTBYTES", client.handle_message) client.init() while not client._done and not client._error: time.sleep(0.2) if client._error: raise PebbleError(self.id, "Failed to send firmware resources %s/system_resources.pbpack" % pbz_path) client = PutBytesClient(self, 0, "RECOVERY" if recovery else "FIRMWARE", binary, bincrc) self.register_endpoint("PUTBYTES", client.handle_message) client.init() while not client._done and not client._error: time.sleep(0.2) if client._error: raise PebbleError(self.id, "Failed to send firmware binary %s/tintin_fw.bin" % pbz_path) self.system_message("FIRMWARE_COMPLETE")
def commit(self): data = pack("!bII", 3, self._token & 0xFFFFFFFF, stm32_crc.crc32(self._buffer)) self._pebble._send_message("PUTBYTES", data)
def inject_metadata(target_binary): if target_binary[-4:] != '.bin': raise InvalidBinaryError def get_symbol_addr(elf_file, symbol): global cached_nm_output if not cached_nm_output: nm_process = Popen(['arm-none-eabi-nm', elf_file], stdout=PIPE) # Popen.communicate returns a tuple of (stdout, stderr) nm_output = nm_process.communicate()[0] if not nm_output: raise InvalidBinaryError() cached_nm_output = nm_output else: nm_output = cached_nm_output for sym in nm_output.split('\n'): if symbol in sym: return int(sym.split()[0], 16) raise InvalidBinaryError() def get_relocate_entries(elf_file): """ returns a list of all the locations requiring an offset""" # TODO: insert link to the wiki page I'm about to write about PIC and relocatable values entries = [] # get the .data locations readelf_relocs_process = Popen(['arm-none-eabi-readelf', '-r', elf_file], stdout=PIPE) readelf_relocs_output = readelf_relocs_process.communicate()[0] lines = readelf_relocs_output.split('\n') i = 0 reading_section = False while i < len(lines): if not reading_section: # look for the next section if lines[i].find("Relocation section '.rel.data") == 0: reading_section = True i += 1 # skip the column title section else: if len(lines[i]) == 0: # end of the section reading_section = False else: entries.append(int(lines[i].split(' ')[0], 16)) i += 1 # get any Global Offset Table (.got) entries readelf_relocs_process = Popen(['arm-none-eabi-readelf', '--sections', elf_file], stdout=PIPE) readelf_relocs_output = readelf_relocs_process.communicate()[0] lines = readelf_relocs_output.split('\n') for line in lines: # We shouldn't need to do anything with the Procedure Linkage Table since we don't actually export functions if '.got' in line and '.got.plt' not in line: words = line.split(' ') while '' in words: words.remove('') section_label_idx = words.index('.got') addr = int(words[section_label_idx + 2], 16) length = int(words[section_label_idx + 4], 16) for i in range(addr, addr + length, 4): entries.append(i) break return entries target_elf = '.'.join([os.path.splitext(target_binary)[0], 'elf']) app_entry_address = get_symbol_addr(target_elf, ENTRY_PT_SYMBOL) jump_table_address = get_symbol_addr(target_elf, JUMP_TABLE_ADDR_SYMBOL) reloc_entries = get_relocate_entries(target_elf) statinfo = os.stat(target_binary) app_size = statinfo.st_size if DEBUG: copy2(target_binary, target_binary + ".orig") with open(target_binary, 'r+b') as f: app_bin = f.read() compiled_bin_size = len(app_bin) if compiled_bin_size + len(reloc_entries)*4 > MAX_APP_BINARY_SIZE: raise "Appending the reloc table will make this app too large" app_crc = stm32_crc.crc32(app_bin[STRUCT_SIZE_BYTES:]) struct_changes = { 'size' : app_size, 'entry_point' : "0x%08x" % app_entry_address, 'symbol_table' : "0x%08x" % jump_table_address, 'crc' : "0x%08x" % app_crc, 'reloc_list_start': "0x%08x" % compiled_bin_size, 'num_reloc_entries': "0x%08x" % len(reloc_entries) } f.seek(SIZE_ADDR) f.write(pack('<HLL', app_size, app_entry_address, app_crc)) f.seek(JUMP_TABLE_ADDR) f.write(pack('<L', jump_table_address)) f.seek(RELOC_LIST_START_ADDR) f.write(pack('<LL', compiled_bin_size, len(reloc_entries))) f.seek(compiled_bin_size) for entry in reloc_entries: f.write(pack('<L', entry)) f.flush() return struct_changes
def make_entry(file_id, offset, length, content): crc = 0 if content is None else stm32_crc.crc32(content) fmt = self.TABLE_ENTRY_FMT return struct.pack(fmt, file_id, offset, length, crc)
def cmd_table(args): with open(args.table_file, 'wb') as table_file: cur_file_id = 1 next_free_byte = 0 for filename in args.pack_file_list: with open(filename, 'rb') as data_file: content = data_file.read() length = len(content) table_file.write(struct.pack('<IIII', cur_file_id, next_free_byte, length, stm32_crc.crc32(content))) cur_file_id += 1 next_free_byte += length # pad the rest of the file for i in range(len(args.pack_file_list), MAX_NUM_FILES): table_file.write(struct.pack('<IIII', 0, 0, 0, 0))
def cmd_manifest(args): with open(args.manifest_file, 'wb') as manifest_file: with open(args.data_chunk_file, 'rb') as data_file: crc = stm32_crc.crc32(data_file.read()) manifest_file.write(struct.pack('<III', int(args.num_files), crc, int(args.timestamp)))
def stm32crc(path): with open(path, 'r+b') as f: binfile = f.read() return stm32_crc.crc32(binfile) & 0xFFFFFFFF