Skip to content

Commit cd0f178

Browse files
committed
added new example for command/response pair
1 parent 24c5418 commit cd0f178

2 files changed

Lines changed: 159 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import construct as cs
2+
import construct_typed as cst
3+
import dataclasses
4+
import typing as t
5+
from . import GalleryItem
6+
import copy
7+
8+
9+
class DefaultSizedError(cs.ConstructError):
10+
pass
11+
12+
13+
class DefaultSized(cs.Subconstruct):
14+
r"""
15+
Returns a size when calling sizeof of GreedyBytes. Parsing and building is not changed.
16+
17+
:param subcon: Construct instance
18+
:param default_size: size that should be returned
19+
20+
:raises DefaultSizedError: anouter GreedyBytes than GreedyBytes is passed
21+
22+
Example::
23+
24+
>>> d = DefaultSized(GreedyBytes)
25+
>>> d.sizeof()
26+
0
27+
"""
28+
29+
def __init__(self, subcon, default_size=0):
30+
if subcon is not cs.GreedyBytes:
31+
raise DefaultSizedError("DefaultSized only works with cs.GreedyBytes")
32+
super().__init__(subcon) # type: ignore
33+
self.default_size = default_size
34+
35+
def _sizeof(self, context, path):
36+
return 0
37+
38+
39+
class CmdCode(cst.EnumBase):
40+
Command1 = 0
41+
Command2 = 1
42+
43+
44+
@dataclasses.dataclass
45+
class RespData_Command1(cst.DataclassMixin):
46+
d1: int = cst.csfield(cs.Int8ul)
47+
d2: int = cst.csfield(cs.Int8ul)
48+
49+
50+
@dataclasses.dataclass
51+
class RespData_Command2(cst.DataclassMixin):
52+
c1: int = cst.csfield(cs.Int16ul)
53+
c2: int = cst.csfield(cs.Int16ul)
54+
55+
56+
@dataclasses.dataclass
57+
class RespData_Error(cst.DataclassMixin):
58+
e1: int = cst.csfield(cs.Int32ul)
59+
e2: int = cst.csfield(cs.Int32ul)
60+
61+
62+
resp_data_formats: t.Dict[CmdCode, cst.Construct[t.Any, t.Any]] = {
63+
CmdCode.Command1: cst.DataclassStruct(RespData_Command1),
64+
CmdCode.Command2: cst.DataclassStruct(RespData_Command2),
65+
}
66+
67+
RespDataType = t.Union[
68+
bytes,
69+
RespData_Command1,
70+
RespData_Command2,
71+
]
72+
73+
74+
class StatusCode(cst.EnumBase):
75+
OK = 0
76+
Error1 = 1
77+
Error2 = 2
78+
79+
80+
status_code_format = cst.TEnum(cs.Int32ul, StatusCode)
81+
82+
83+
def _get_cmd_code(ctx: cst.Context):
84+
contextkw = ctx._root._
85+
if "cmd_code" not in contextkw:
86+
# if no cmd_code is passed, fallback to GreedyBytes
87+
return None
88+
89+
return contextkw.cmd_code
90+
91+
92+
def _is_status_code_ok(ctx: cst.Context):
93+
if ctx._sizing is True:
94+
# while sizing pretend an ok status_code
95+
return True
96+
97+
return ctx.status_code == StatusCode.OK
98+
99+
100+
@dataclasses.dataclass
101+
class Response(cst.DataclassMixin):
102+
status_code: StatusCode = cst.csfield(status_code_format)
103+
data: RespDataType = cst.csfield(
104+
cs.Select(
105+
cs.IfThenElse(
106+
condfunc=_is_status_code_ok,
107+
thensubcon=cs.Switch(
108+
keyfunc=_get_cmd_code, # select the response format based on the cmd_code
109+
cases=resp_data_formats,
110+
default=cs.StopIf(True),
111+
),
112+
elsesubcon=cst.DataclassStruct(RespData_Error),
113+
),
114+
DefaultSized(cs.GreedyBytes),
115+
)
116+
)
117+
118+
119+
constr = cst.DataclassStruct(Response)
120+
121+
gallery_item = GalleryItem(
122+
construct=constr,
123+
example_binarys={
124+
"Zeros": bytes(20),
125+
"1": bytes([1, 1, 2, 1, 2]),
126+
},
127+
contextkw={"cmd_code": CmdCode.Command1},
128+
)
129+
130+
# ######################################################################################
131+
# ################## Adding new constructs to construct-editor #########################
132+
# ######################################################################################
133+
import construct_editor.helper.preprocessor as cse_preprocessor # type: ignore
134+
import construct_editor.helper.wrapper as cse_wrapper # type: ignore
135+
136+
cse_wrapper.construct_entry_mapping.update(
137+
{
138+
DefaultSized: cse_wrapper.EntryTransparentSubcon,
139+
}
140+
)
141+
142+
_original_include_metadata = cse_preprocessor.include_metadata
143+
144+
145+
def include_metadata(
146+
constr: "cs.Construct[t.Any, t.Any]", bitwise: bool = False
147+
) -> "cs.Construct[t.Any, t.Any]":
148+
if isinstance(constr, DefaultSized):
149+
constr = copy.copy(constr) # constr is modified, so we have to make a copy
150+
constr.subcon = cse_preprocessor.include_metadata(constr.subcon, bitwise)
151+
return cse_preprocessor.IncludeGuiMetaData(constr, bitwise)
152+
else:
153+
return _original_include_metadata(constr, bitwise)
154+
155+
156+
# monkey patch construct-editor
157+
cse_preprocessor.include_metadata = include_metadata

construct_editor/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import construct_editor.gallery.test_checksum
4545
import construct_editor.gallery.test_compressed
4646
import construct_editor.gallery.test_stringencodded
47+
import construct_editor.gallery.example_cmd_resp
4748
from construct_editor.widgets.construct_hex_editor import ConstructHexEditor
4849

4950

@@ -70,6 +71,7 @@ def __init__(self, parent: ConstructGalleryFrame):
7071
"################ EXAMPLES ################": None,
7172
"Example: pe32coff": construct_editor.gallery.example_pe32coff.gallery_item,
7273
"Example: ipstack": construct_editor.gallery.example_ipstack.gallery_item,
74+
"Example: Cmd/Resp": construct_editor.gallery.example_cmd_resp.gallery_item,
7375
"################ TESTS ####################": None,
7476
# "## bytes and bits ################": None,
7577
"Test: Bytes/GreedyBytes": construct_editor.gallery.test_bytes_greedybytes.gallery_item,

0 commit comments

Comments
 (0)