mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33:00 +02:00
219 lines
6.1 KiB
Python
Executable File
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)))
|