#!/usr/bin/env python3 import binascii import struct HDRMGC = b"TlvInfo" HDRVER = 1 HDRFMT = ">7sxBH" HDRLEN = struct.calcsize(HDRFMT) CRCFMT = ">BBL" CRCLEN = struct.calcsize(CRCFMT) def pack_varstr(s, maxlen=0xff): b = bytes(s, "ascii") assert(len(b) <= maxlen) return b def unpack_varstr(b): return b.decode("ascii") def pack_u8(num): return struct.pack("B", num) def unpack_u8(b): return struct.unpack("B", b)[0] def pack_u16(num): return struct.pack(">H", num) def unpack_u16(b): return struct.unpack(">H", b)[0] def pack_u32(num): return struct.pack(">L", num) def unpack_u32(b): return struct.unpack(">L", b)[0] def pack_date(datestr): assert(len(datestr) == 19) return pack_varstr(datestr, 19) def pack_country(cstr): assert(len(cstr) == 2) return pack_varstr(cstr, 2) def pack_mac(macstr): return struct.pack("6B", *[int(o, 16) for o in macstr.split(":")]) def unpack_mac(b): o = struct.unpack("6B", b) return f"{o[0]:02x}:{o[1]:02x}:{o[2]:02x}:{o[3]:02x}:{o[4]:02x}:{o[5]:02x}" OPS = { "varstr": (pack_varstr, unpack_varstr), "u8": (pack_u8, unpack_u8), "u16": (pack_u16, unpack_u16), "u32": (pack_u32, unpack_u32), "date": (pack_date, unpack_varstr), "country": (pack_country, unpack_varstr), "mac": (pack_mac, unpack_mac), } TLV = ( { "type": 0x21, "name": "product-name", "ops": "varstr" }, { "type": 0x22, "name": "part-number", "ops": "varstr" }, { "type": 0x23, "name": "serial-number", "ops": "varstr" }, { "type": 0x24, "name": "mac-address", "ops": "mac" }, { "type": 0x25, "name": "manufacture-date", "ops": "date" }, { "type": 0x26, "name": "device-version", "ops": "u8" }, { "type": 0x27, "name": "label-revision", "ops": "varstr" }, { "type": 0x28, "name": "platform-name", "ops": "varstr" }, { "type": 0x29, "name": "onie-version", "ops": "varstr" }, { "type": 0x2a, "name": "num-macs", "ops": "u16" }, { "type": 0x2b, "name": "manufacturer", "ops": "varstr" }, { "type": 0x2c, "name": "country-code", "ops": "country" }, { "type": 0x2d, "name": "vendor", "ops": "varstr" }, { "type": 0x2e, "name": "diag-version", "ops": "varstr" }, { "type": 0x2f, "name": "service-tag", "ops": "varstr" }, ) TLV_VENDOR_EXTENSION = 0xfd TLV_CRC32 = 0xfe def _tlv_by_lambda(fn): info = next(filter(fn, TLV)) if "ops" in info: return info, OPS[info["ops"]] else: return info def tlv_by_name(name): try: return _tlv_by_lambda(lambda info: info["name"] == name) except StopIteration: raise ValueError(f"Unknown type name \"{name}\"") def tlv_by_type(t): try: return _tlv_by_lambda(lambda info: info["type"] == t) except StopIteration: raise ValueError(f"Unknown type id {t}") def into_tlv(d): def pack_vendor(exts): out = b"" for (iana_pen, val) in exts: b = bytes(val, "utf-8") l = len(b) + struct.calcsize(">L") assert(l <= 0xff) out += struct.pack(">BBL", TLV_VENDOR_EXTENSION, l, iana_pen) + b return out out = b"" # Generate all optional data for (k, v) in sorted(d.items()): if k == "vendor-extension": out += pack_vendor(v) else: info, (pack, _) = tlv_by_name(k) val = pack(v) out += struct.pack("BB", info["type"], len(val)) + val # Prepend the header now that we know the total length of the # optional fields - make sure sure to include the CRC TLV length, # which is appended in the last step out = struct.pack(HDRFMT, HDRMGC, HDRVER, len(out) + CRCLEN) + out out += struct.pack("BB", TLV_CRC32, struct.calcsize(">L")) out += struct.pack(">L", binascii.crc32(out)) return out def from_tlv(f): d = {} def unpack_vendor(ext): head, tail = ext[:4], ext[4:] iana_pen = struct.unpack(">L", head)[0] val = tail.decode("utf-8") if "vendor-extension" not in d: d["vendor-extension"] = [] d["vendor-extension"].append([iana_pen, val]) head = f.read(HDRLEN) magic, ver, l = struct.unpack(HDRFMT, head) assert(magic == HDRMGC) assert(ver == 1) tail = f.read(l) b = head + tail assert(len(b) >= HDRLEN + l) crcoffs = HDRLEN + l - CRCLEN t, l, v = struct.unpack(CRCFMT, b[crcoffs:crcoffs+CRCLEN]) assert(t == TLV_CRC32) assert(v == binascii.crc32(b[:crcoffs + struct.calcsize("BB")])) while len(tail) >= 2: t, l = struct.unpack("BB", tail[:2]) v = tail[2:l+2] tail = tail[l+2:] if t == TLV_VENDOR_EXTENSION: unpack_vendor(v) continue elif t == TLV_CRC32: break info, (_, unpack) = tlv_by_type(t) d[info["name"]] = unpack(v) return d if __name__ == "__main__": import argparse import json import os import sys parser = argparse.ArgumentParser(prog='onieprom') parser.add_argument("infile", nargs="?", default=sys.stdin, type=argparse.FileType("rb", 0)) parser.add_argument("outfile", nargs="?", default=sys.stdout, type=argparse.FileType("wb")) parser.add_argument("-e", "--encode", default=False, action="store_true", help="Encode JSON input to binary output") parser.add_argument("-d", "--decode", default=False, action="store_true", help="Decode binary input to JSON output") args = parser.parse_args() if (not args.encode) and (not args.decode): c = args.infile.read(1) args.infile.seek(0, 0) if c == b"{": args.encode = True elif c == b"T": args.decode = True else: sys.stderr.write("Neither encode nor decode specified, and could not infer operation from input") sys.exit(1) if args.encode: args.outfile.buffer.write(into_tlv(json.load(args.infile))) else: args.outfile.write(json.dumps(from_tlv(args.infile)))