|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +from contextlib import contextmanager |
| 4 | +import json |
| 5 | +import struct |
| 6 | + |
| 7 | +from itertools import batched |
| 8 | +import cbor2 |
| 9 | +import typer |
| 10 | +from typing_extensions import Annotated |
| 11 | + |
| 12 | + |
| 13 | +LOREM_IPSUM = """\ |
| 14 | +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris varius leo sed elit rutrum, sit amet imperdiet velit suscipit. Curabitur eget iaculis quam. Quisque porta risus orci, id sodales enim ullamcorper et. Donec eleifend orci velit, vel efficitur diam hendrerit eu. Donec et tempor sapien. Fusce ultrices varius fermentum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis maximus id elit vel rutrum. Vestibulum in turpis pharetra, venenatis dolor consequat, commodo ligula. Nunc ac mi viverra, accumsan sapien vitae, porta velit. Nam diam lacus, luctus in consectetur quis, congue eget sapien. Vivamus interdum, nisi quis viverra tempor, erat justo pulvinar urna, non condimentum tortor ligula molestie nisl. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam eu tempus quam. |
| 15 | +Donec ac interdum orci. Aenean gravida, massa sit amet efficitur condimentum, diam ipsum faucibus dolor, ut lacinia sapien neque sit amet tellus. Fusce pharetra vehicula erat et luctus. Ut euismod, neque vel mollis venenatis, ligula lorem convallis metus, ac dapibus nulla mauris eu ante. Aenean scelerisque dolor ipsum, non euismod lorem facilisis ac. Mauris rhoncus porta tortor at dapibus. Vestibulum eget justo sed velit finibus auctor facilisis eget arcu. Ut pulvinar consequat sapien, eu accumsan ipsum molestie et. In pharetra purus id porttitor vestibulum. Quisque ut rutrum dui, vitae egestas nulla. Aenean in ante nec elit faucibus iaculis. Curabitur vehicula ligula et leo semper bibendum. |
| 16 | +Maecenas pretium tortor neque, id iaculis risus efficitur in. Mauris egestas finibus odio. Maecenas enim arcu, sagittis ut aliquet in, mollis a mauris. Quisque velit erat, congue in orci at, ultrices interdum urna. Aliquam erat volutpat. Nunc vestibulum ligula arcu, sit amet suscipit erat consectetur sed. Phasellus dictum pulvinar risus, et finibus arcu luctus et. |
| 17 | +Phasellus id congue justo, vel dictum nulla. Maecenas sed mi libero. Integer efficitur velit turpis, laoreet volutpat dui rhoncus ac. Quisque aliquet dolor condimentum felis vulputate, a pulvinar quam rutrum. Quisque et neque eros. In id elit arcu. Aenean mattis eros ut elit feugiat, nec tincidunt justo dictum. Suspendisse tristique nisl et consequat suscipit. Nullam interdum eros vel nibh facilisis tristique. Duis purus turpis, mollis sed diam eget, maximus placerat massa. In tempus venenatis ultricies. Nunc ultricies purus eget auctor consectetur. |
| 18 | +Sed a tortor finibus, semper nunc vitae, dignissim urna. Nunc fermentum volutpat velit. Praesent sit amet quam in augue interdum feugiat in vel nulla. Etiam blandit laoreet cursus. Maecenas faucibus, orci sed fermentum consectetur, justo neque aliquet velit, sed varius massa sapien a sapien. Nulla consectetur enim vitae mauris posuere maximus. Morbi lobortis dolor mauris, sit amet vestibulum velit varius vitae. Etiam luctus eros dolor, et congue velit finibus sit amet. Fusce luctus bibendum rutrum. Mauris ultricies convallis diam, non elementum elit rutrum sit amet. Nam ut ipsum vitae risus commodo porta. |
| 19 | +Vestibulum ornare ex nec scelerisque sollicitudin. Nunc pulvinar risus tristique magna vestibulum, vel faucibus justo accumsan. Donec et nisi lacus. Nam fermentum eget erat et suscipit. Nunc pharetra, nunc nec accumsan bibendum, augue dolor vehicula ante, nec aliquam lorem tortor vel odio. Curabitur facilisis, sem ut sollicitudin gravida, elit leo rhoncus felis, non accumsan ipsum velit sit amet enim. Fusce viverra est quis lacus congue aliquam. Nulla ex leo, molestie sit amet lobortis sit amet, sagittis nec enim. Morbi nec mi ut elit luctus dictum. Nullam sed neque vitae nulla convallis facilisis. |
| 20 | +Etiam id lobortis nisl. Morbi tempus tempus mi, sed ornare ligula vulputate in. Cras neque erat, viverra pulvinar auctor in, blandit sit amet nisl. Phasellus gravida neque eu velit facilisis ultrices. Sed maximus, urna a gravida tristique, magna elit tincidunt mi, ut aliquet ipsum metus nec nulla. Proin molestie justo id felis dapibus, at imperdiet neque ornare. Curabitur condimentum ex quis pharetra aliquet. Nulla sem tortor, congue in arcu et, ultricies sollicitudin sapien. Aenean nec orci augue. Pellentesque sit amet velit et arcu facilisis lobortis. Duis eu interdum leo. Proin lectus dui, placerat et ante dictum, scelerisque sodales justo. Pellentesque auctor eros quis lectus convallis eleifend. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent vel ipsum eu justo egestas sagittis vitae quis arcu. |
| 21 | +""" |
| 22 | + |
| 23 | +BLOCK_SIZE = 512 |
| 24 | +BLOCK_HEADER_F_LEN_LEN = 2 |
| 25 | +BLOCK_HEADER_F_ID_LEN = 1 |
| 26 | +BLOCK_HEADER_LEN = BLOCK_HEADER_F_LEN_LEN + BLOCK_HEADER_F_ID_LEN |
| 27 | +BLOCK_FIRST = 0x40 |
| 28 | +BLOCK_LAST = 0x80 |
| 29 | + |
| 30 | +BLOCK_CAPACITY = BLOCK_SIZE - BLOCK_HEADER_LEN |
| 31 | + |
| 32 | +CONTENT_TYPE_MAP = { |
| 33 | + "text/octet-stream": 42, |
| 34 | + "application/json": 50, |
| 35 | + "application/cbor": 60, |
| 36 | +} |
| 37 | + |
| 38 | + |
| 39 | +app = typer.Typer() |
| 40 | + |
| 41 | + |
| 42 | +def entry_stream_chunks(entry: bytes): |
| 43 | + flags = BLOCK_FIRST |
| 44 | + |
| 45 | + while entry: |
| 46 | + chunk = entry[:BLOCK_SIZE - BLOCK_HEADER_LEN] |
| 47 | + entry = entry[BLOCK_SIZE - BLOCK_HEADER_LEN:] |
| 48 | + |
| 49 | + if len(entry) == 0: |
| 50 | + flags |= BLOCK_LAST |
| 51 | + |
| 52 | + yield chunk, flags |
| 53 | + |
| 54 | + flags = 0 |
| 55 | + |
| 56 | + |
| 57 | +def entry_pack(entry: dict, stream: bool) -> bytes: |
| 58 | + entry_bin = b"" |
| 59 | + if not stream: |
| 60 | + entry_bin += struct.pack(">H", len(entry["data"])) |
| 61 | + |
| 62 | + entry_bin += struct.pack(">HB", |
| 63 | + CONTENT_TYPE_MAP[entry["content_type"]], |
| 64 | + len(entry["path"])) |
| 65 | + entry_bin += entry["path"].encode("utf-8") |
| 66 | + entry_bin += (entry["data"].encode("utf-8") if isinstance(entry["data"], str) else entry["data"]) |
| 67 | + |
| 68 | + return entry_bin |
| 69 | + |
| 70 | + |
| 71 | +def block_pack(payload: bytes, stream_id: int, flags: int) -> bytes: |
| 72 | + return struct.pack(">HB", len(payload) + 1, stream_id | flags) + payload |
| 73 | + |
| 74 | + |
| 75 | +def payload_append(payload: bytearray, data: bytes, stream_id: int, flags: int): |
| 76 | + block = block_pack(data, stream_id, flags) |
| 77 | + payload += block |
| 78 | + |
| 79 | + |
| 80 | +class CInitializer: |
| 81 | + def __init__(self, out, init_indent_level: int = 0): |
| 82 | + self.out = out |
| 83 | + self.indent_level = init_indent_level |
| 84 | + |
| 85 | + def indentation(self): |
| 86 | + return "\t" * self.indent_level |
| 87 | + |
| 88 | + @contextmanager |
| 89 | + def indent(self): |
| 90 | + self.indent_level += 1 |
| 91 | + yield |
| 92 | + self.indent_level -= 1 |
| 93 | + |
| 94 | + @contextmanager |
| 95 | + def array_or_struct(self, name: str | None, end: str = ",", cast: str = ""): |
| 96 | + if name: |
| 97 | + self.out.write((self.indentation() + f".{name} = {cast}" + "{\n")) |
| 98 | + else: |
| 99 | + self.out.write((self.indentation() + "{\n")) |
| 100 | + |
| 101 | + with self.indent(): |
| 102 | + yield |
| 103 | + |
| 104 | + self.out.write((self.indentation() + "}" + end + "\n")) |
| 105 | + |
| 106 | + @contextmanager |
| 107 | + def array(self, name: str | None = None, end: str = ",", cast: str = ""): |
| 108 | + with self.array_or_struct(name, end, cast): |
| 109 | + yield |
| 110 | + |
| 111 | + @contextmanager |
| 112 | + def byte_array(self, name: str | None = None, end: str = ","): |
| 113 | + with self.array_or_struct(name, end, cast="(const uint8_t []) "): |
| 114 | + yield |
| 115 | + |
| 116 | + @contextmanager |
| 117 | + def struct(self, name: str | None = None, end: str = ","): |
| 118 | + with self.array_or_struct(name, end): |
| 119 | + yield |
| 120 | + |
| 121 | + def write(self, line: str): |
| 122 | + self.out.write((self.indentation() + line)) |
| 123 | + |
| 124 | + def writeln(self, line: str): |
| 125 | + self.out.write((self.indentation() + line + "\n")) |
| 126 | + |
| 127 | + def write_batched(self, payload: str | bytes, n: int = 16): |
| 128 | + if isinstance(payload, str): |
| 129 | + payload = payload.encode("utf-8") |
| 130 | + |
| 131 | + for batch in batched(payload, n): |
| 132 | + self.writeln(" ".join([f"0x{c:02x}," for c in batch])) |
| 133 | + |
| 134 | + |
| 135 | +@app.command() |
| 136 | +def main( |
| 137 | + name: Annotated[str, typer.Argument], |
| 138 | + out: Annotated[typer.FileTextWrite, typer.Argument()], |
| 139 | + device_name: Annotated[str, typer.Option()] = "id123", |
| 140 | + length: Annotated[int, typer.Option(min=0)] = len(LOREM_IPSUM), |
| 141 | + count: Annotated[int, typer.Option()] = 1, |
| 142 | +): |
| 143 | + obj = { |
| 144 | + "device_id": device_name, |
| 145 | + "entries": [ |
| 146 | + { |
| 147 | + "path": "/.s/lorem", |
| 148 | + "content_type": "application/json", |
| 149 | + "data": json.dumps({"lorem": (LOREM_IPSUM * 50)[:length]}), |
| 150 | + }, |
| 151 | + ] * count, |
| 152 | + } |
| 153 | + |
| 154 | + # Fixed header for plaintext encoding |
| 155 | + header = [1, [0, obj["device_id"]]] |
| 156 | + |
| 157 | + payload = bytearray() |
| 158 | + payload += cbor2.dumps(header) |
| 159 | + stream_id = 1 |
| 160 | + |
| 161 | + block_buf = bytearray() |
| 162 | + |
| 163 | + for entry in obj["entries"]: |
| 164 | + if len(entry["data"]) > BLOCK_CAPACITY: |
| 165 | + if block_buf: |
| 166 | + payload_append(payload, block_buf, 0, BLOCK_FIRST | BLOCK_LAST) |
| 167 | + block_buf = bytearray() |
| 168 | + |
| 169 | + entry_bin = entry_pack(entry, True) |
| 170 | + |
| 171 | + for chunk, flags in entry_stream_chunks(entry_bin): |
| 172 | + payload_append(payload, chunk, stream_id, flags) |
| 173 | + |
| 174 | + stream_id += 1 |
| 175 | + else: |
| 176 | + entry_bin = entry_pack(entry, False) |
| 177 | + |
| 178 | + if len(block_buf) + len(entry_bin) > BLOCK_CAPACITY: |
| 179 | + payload_append(payload, block_buf, 0, BLOCK_FIRST | BLOCK_LAST) |
| 180 | + block_buf = bytearray() |
| 181 | + |
| 182 | + block_buf += entry_bin |
| 183 | + |
| 184 | + if block_buf: |
| 185 | + payload_append(payload, block_buf, 0, BLOCK_FIRST | BLOCK_LAST) |
| 186 | + |
| 187 | + c = CInitializer(out) |
| 188 | + |
| 189 | + c.write(f"const struct pouch_test_item {name} = ") |
| 190 | + |
| 191 | + with c.struct(end=";"): |
| 192 | + # data |
| 193 | + with c.byte_array("data"): |
| 194 | + c.write_batched(payload) |
| 195 | + c.writeln(f".data_len = {len(payload)},") |
| 196 | + |
| 197 | + # entries |
| 198 | + with c.array("entries", cast="(const struct pouch_test_item_entry[]) "): |
| 199 | + for entry in obj["entries"]: |
| 200 | + with c.struct(""): |
| 201 | + c.writeln(f'.path = "{entry['path']}",') |
| 202 | + c.writeln(f'.content_type = {CONTENT_TYPE_MAP[entry['content_type']]},') |
| 203 | + |
| 204 | + with c.byte_array("data"): |
| 205 | + c.write_batched(entry["data"]) |
| 206 | + c.writeln(f".data_len = {len(entry['data'])},") |
| 207 | + c.writeln(f".num_entries = {len(obj['entries'])},") |
| 208 | + |
| 209 | + |
| 210 | +if __name__ == '__main__': |
| 211 | + app() |
0 commit comments