Skip to content

Commit 4fd645e

Browse files
committed
tests: pouch: downlink: add test cases
Use very long device name, so that pouch header does not fit into single transport MTU (19). Test also with high MTU (250). Signed-off-by: Marcin Niestroj <m.niestroj@emb.dev>
1 parent d1ef48d commit 4fd645e

7 files changed

Lines changed: 481 additions & 0 deletions

File tree

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
cbor2
2+
typer
13
zcbor==0.9.1
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# PDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(downlink_test)
7+
8+
target_sources(app PRIVATE
9+
src/downlink.c
10+
)
11+
12+
set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/)
13+
14+
function(pouch_gen_lorem length count)
15+
add_custom_command(
16+
OUTPUT ${gen_dir}/lorem-${length}-x${count}.c
17+
COMMAND
18+
${PYTHON_EXECUTABLE}
19+
${APPLICATION_SOURCE_DIR}/scripts/pouch-gen-lorem.py
20+
--device-name ${CONFIG_POUCH_DEVICE_NAME}
21+
--length ${length}
22+
--count ${count}
23+
lorem_${length}_x${count}
24+
${gen_dir}/lorem-${length}-x${count}.c
25+
DEPENDS ${APPLICATION_SOURCE_DIR}/scripts/pouch-gen-lorem.py
26+
WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR}/
27+
)
28+
add_custom_target(pouch-gen-lorem-${length}-x${count} DEPENDS ${gen_dir}/lorem-${length}-x${count}.c)
29+
add_dependencies(app pouch-gen-lorem-${length}-x${count})
30+
endfunction()
31+
32+
pouch_gen_lorem(10 1)
33+
pouch_gen_lorem(512 1)
34+
pouch_gen_lorem(1024 1)
35+
pouch_gen_lorem(102400 1)
36+
37+
pouch_gen_lorem(10 5)
38+
pouch_gen_lorem(100 5)
39+
pouch_gen_lorem(200 5)
40+
pouch_gen_lorem(500 5)
41+
pouch_gen_lorem(1024 5)

tests/pouch/downlink/Kconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
menu "Downlink test configuration"
2+
3+
config POUCH_DEVICE_NAME
4+
string "Device name used in encoded pouches"
5+
default "id123"
6+
7+
config POUCH_TRANSPORT_MTU
8+
int "Transport MTU"
9+
default 19
10+
11+
endmenu
12+
13+
source "Kconfig.zephyr"

tests/pouch/downlink/prj.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CONFIG_ZTEST=y
2+
CONFIG_POUCH=y
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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

Comments
 (0)