Files
infix/board/common/rootfs/usr/bin/onieprom
T
Mattias Walström 2194f7bebe Merge /usr/* directories into /
/usr/bin  -> /bin
/usr/sbin -> /sbin
/usr/lib  -> /lib
2024-08-06 11:15:38 +02:00

219 lines
6.1 KiB
Python
Executable File

#!/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)))