rule2sor 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rule2sor/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .gen_rulemanager import RuleManager
2
+
3
+ __all__ = ["RuleManager"]
rule2sor/cli.py ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ rule2sor: convert an OpenSCHC JSON rule file into a .sor file
4
+ (CORECONF/CBOR representation of the Set of Rules).
5
+
6
+ Usage:
7
+ rule2sor <rule-file.json> [-s <sid-file>] [-o <output.sor>] [-q]
8
+
9
+ The rules are validated and completed by RuleManager.Add(), then
10
+ serialized with RuleManager.to_coreconf().
11
+ """
12
+
13
+ import argparse
14
+ import binascii
15
+ import json
16
+ import os
17
+ import sys
18
+
19
+ from cbor_diag import cbor2diag
20
+ from pycoreconf import CORECONFModel
21
+
22
+ from .gen_rulemanager import RuleManager
23
+
24
+ DEFAULT_SID_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
25
+ "ietf-schc@2026-05-07.sid")
26
+
27
+ def main():
28
+ parser = argparse.ArgumentParser(
29
+ description="Convert OpenSCHC JSON rules to a CORECONF/CBOR .sor file, or display a .sor file")
30
+ parser.add_argument("rule_file", help="JSON file describing the set of rules, or .sor file to display")
31
+ parser.add_argument("-s", "--sid", default=DEFAULT_SID_FILE,
32
+ help="SID file (default: %(default)s)")
33
+ parser.add_argument("-o", "--output",
34
+ help="output .sor file (default: rule file with .sor extension)")
35
+ parser.add_argument("-q", "--quiet", action="store_true",
36
+ help="do not print the rules nor the CBOR dump")
37
+ args = parser.parse_args()
38
+
39
+ if args.rule_file.endswith(".sor"):
40
+ if not os.path.exists(args.rule_file):
41
+ print(f"Error: file not found: {args.rule_file}", file=sys.stderr)
42
+ sys.exit(1)
43
+ with open(args.rule_file, "rb") as f:
44
+ ycbor = f.read()
45
+ print("CBOR (hex):", binascii.hexlify(ycbor).decode())
46
+ print("\nCBOR diagnostic notation:")
47
+ print(cbor2diag(ycbor))
48
+ print("\nRESTCONF JSON:")
49
+ model = CORECONFModel([args.sid])
50
+ print(json.dumps(model.decode(ycbor, as_rfc7951=True), indent=2))
51
+ return
52
+
53
+ output = args.output
54
+ if output is None:
55
+ base, _ = os.path.splitext(args.rule_file)
56
+ output = base + ".sor"
57
+
58
+ rm = RuleManager()
59
+ rm.Add(file=args.rule_file, device="test:device1")
60
+ rm.add_sid_file(args.sid)
61
+
62
+ if not args.quiet:
63
+ rm.Print()
64
+
65
+ ycbor = rm.to_coreconf()
66
+ if not args.quiet:
67
+ print(binascii.hexlify(ycbor))
68
+ print("\nCBOR diagnostic notation:")
69
+ print(cbor2diag(ycbor))
70
+ print("\nRESTCONF JSON:")
71
+ model = CORECONFModel([args.sid])
72
+ print(json.dumps(model.decode(ycbor, as_rfc7951=True), indent=2))
73
+
74
+ with open(output, "wb") as f:
75
+ f.write(ycbor)
76
+
77
+ print("{} written ({} bytes)".format(output, len(ycbor)))
78
+
79
+ if __name__ == "__main__":
80
+ sys.exit(main())
@@ -0,0 +1,318 @@
1
+ T_RULEID = "RuleID"
2
+ T_RULEIDVALUE = "RuleIDValue"
3
+ T_RULEIDLENGTH = "RuleIDLength"
4
+ T_DEVICEID = "DeviceID"
5
+
6
+ T_FID = "FID"
7
+ T_FL = "FL"
8
+ T_FP = "FP"
9
+ T_DI = "DI"
10
+ T_TV = "TV"
11
+ T_TV_IND = "TV.IND"
12
+ T_MO = "MO"
13
+ T_MO_VAL = "MO.VAL"
14
+ T_CDA = "CDA"
15
+ T_SB = "SB"
16
+
17
+ T_PROTO_IPV6 = "IPV6"
18
+ # IPv6 header fields
19
+ T_IPV6_VER = "IPV6.VER"
20
+ T_IPV6_TC = "IPV6.TC"
21
+ T_IPV6_FL = "IPV6.FL"
22
+ T_IPV6_LEN = "IPV6.LEN"
23
+ T_IPV6_NXT = "IPV6.NXT"
24
+ T_IPV6_HOP_LMT = "IPV6.HOP_LMT"
25
+ T_IPV6_DEV_PREFIX = "IPV6.DEV_PREFIX"
26
+ T_IPV6_DEV_IID = "IPV6.DEV_IID"
27
+ T_IPV6_APP_PREFIX = "IPV6.APP_PREFIX"
28
+ T_IPV6_APP_IID = "IPV6.APP_IID"
29
+
30
+ T_IPV4_VER = "IPV4.VER"
31
+
32
+ T_PROTO_ICMPV6 = "ICMPV6"
33
+ # ICMPv6 header fields
34
+ T_ICMPV6_TYPE = "ICMPV6.TYPE"
35
+ T_ICMPV6_CODE = "ICMPV6.CODE"
36
+ T_ICMPV6_CKSUM = "ICMPV6.CKSUM"
37
+ T_ICMPV6_IDENT = "ICMPV6.IDENT"
38
+ T_ICMPV6_SEQNO = "ICMPV6.SEQNO"
39
+ T_ICMPV6_UNUSED = "ICMPV6.UNUSED"
40
+ T_ICMPV6_PAYLOAD = "ICMPV6.PAYLOAD"
41
+ # ICMPv6 types
42
+ T_ICMPV6_TYPE_ECHO_REQUEST = "ICMPV6.TYPE.ECHO.REQUEST"
43
+ T_ICMPV6_TYPE_ECHO_REPLY = "ICMPV6.TYPE.ECHO.REPLY"
44
+
45
+ T_PROTO_UDP = "UDP"
46
+ # UDP fields
47
+ T_UDP_DEV_PORT = "UDP.DEV_PORT"
48
+ T_UDP_APP_PORT = "UDP.APP_PORT"
49
+ T_UDP_LEN = "UDP.LEN"
50
+ T_UDP_CKSUM = "UDP.CKSUM"
51
+
52
+
53
+
54
+ T_PROTO_COAP = "COAP"
55
+ # CoAP fields
56
+ T_COAP_VERSION = "COAP.VER"
57
+ T_COAP_TYPE = "COAP.TYPE"
58
+ T_COAP_TKL = "COAP.TKL"
59
+ T_COAP_CODE = "COAP.CODE"
60
+ T_COAP_MID = "COAP.MID"
61
+ T_COAP_TOKEN = "COAP.TOKEN"
62
+ # CoAP options string (written exactly as in standards)
63
+ T_COAP_OPT_IF_MATCH = "COAP.IF-MATCH"
64
+ T_COAP_OPT_URI_HOST = "COAP.URI-HOST"
65
+ T_COAP_OPT_ETAG = "COAP.ETAG"
66
+ T_COAP_OPT_IF_NONE_MATCH = "COAP.IF-NONE-MATCH"
67
+ T_COAP_OPT_OBS = "COAP.OBSERVE"
68
+ T_COAP_OPT_URI_PORT = "COAP.URI-PORT"
69
+ T_COAP_OPT_LOC_PATH = "COAP.LOCATION-PATH"
70
+ T_COAP_OPT_URI_PATH = "COAP.URI-PATH"
71
+ T_COAP_OPT_CONT_FORMAT = "COAP.CONTENT-FORMAT"
72
+ T_COAP_OPT_MAX_AGE = "COAP.MAX-AGE"
73
+ T_COAP_OPT_URI_QUERY = "COAP.URI-QUERY"
74
+ T_COAP_OPT_ACCEPT = "COAP.ACCEPT"
75
+ T_COAP_OPT_LOC_QUERY = "COAP.LOCATION-QUERY"
76
+ T_COAP_OPT_BLOCK2 = "COAP.BLOCK2"
77
+ T_COAP_OPT_BLOCK1 = "COAP.BLOCK1"
78
+ T_COAP_OPT_SIZE2 = "COAP.SIZE2"
79
+ T_COAP_OPT_PROXY_URI = "COAP.PROXY-URI"
80
+ T_COAP_OPT_PROXY_SCHEME = "COAP.PROXY-SCHEME"
81
+ T_COAP_OPT_SIZE1 = "COAP.SIZE1"
82
+ T_COAP_OPT_NO_RESP = "COAP.NO-RESPONSE"
83
+
84
+ T_OSCORE_FLAGS = "COAP.OSCORE_FLAGS"
85
+ T_OSCORE_FLAGS_FLAGS = "COAP.OSCORE_FLAGS_FLAGS"
86
+ T_OSCORE_FLAGS_N = "COAP.OSCORE_FLAGS_N"
87
+ T_OSCORE_KID = "COAP.OSCORE_KID"
88
+ T_OSCORE_KIDCTX = "COAP.OSCORE_KID_CTX"
89
+ T_OSCORE_PIV = "COAP.OSCORE_PIV"
90
+ T_COAP_OPT_END = "COAP.End"
91
+
92
+ T_UNUSED = "UNUSED"
93
+ T_PAYLOAD = "PAYLOAD"
94
+
95
+ T_FUNCTION_VAR = "var"
96
+ T_FUNCTION_TKL = "tkl"
97
+
98
+ T_FUNCTION_VAR = "var"
99
+ T_FUNCTION_VARBIT = "var_bit"
100
+ T_FUNCTION_TKL = "tkl"
101
+
102
+
103
+ T_DIR_UP = "UP"
104
+ T_DIR_DW = "DW"
105
+ T_DIR_BI = "BI"
106
+
107
+ T_MO_EQUAL = "EQUAL"
108
+ T_MO_IGNORE = "IGNORE"
109
+ T_MO_MSB = "MSB"
110
+ T_MO_MMAP = "MATCH-MAPPING"
111
+ T_MO_MATCH_REV_RULE = "REV-RULE-MATCH"
112
+
113
+ T_CDA_NOT_SENT = "NOT-SENT"
114
+ T_CDA_VAL_SENT = "VALUE-SENT"
115
+ T_CDA_MAP_SENT = "MAPPING-SENT"
116
+ T_CDA_LSB = "LSB"
117
+ T_CDA_COMP_LEN = "COMPUTE-LENGTH"
118
+ T_CDA_COMP_CKSUM = "COMPUTE-CHECKSUM"
119
+ T_CDA_DEVIID = "DEVIID"
120
+ T_CDA_APPIID = "APPIID"
121
+ T_CDA_LORA_DEVIID = "COMPUTE-DEVIID"
122
+ T_CDA_REV_COMPRESS = "REV-COMPRESSED-SENT"
123
+
124
+ T_ALGO = "ALGO"
125
+ T_ALGO_DIRECT = "DIRECT"
126
+ T_ALGO_COAP_OPT = "COAP_OPTION"
127
+
128
+ T_META = "META"
129
+ T_UP_RULES = "UP_RULES"
130
+ T_DW_RULES = "DW_RULES"
131
+ T_LAST_USED = "LAST_USED"
132
+
133
+ T_ACTION = "Action"
134
+
135
+ T_COMP = "Compression"
136
+ T_NO_COMP = "NoCompression"
137
+ T_FRAG = "Fragmentation"
138
+ T_FRAG_MODE = "FRMode"
139
+ T_FRAG_NO_ACK = "NoAck" #YANG no-ack
140
+ T_FRAG_ACK_ALWAYS = "AckAlways"
141
+ T_FRAG_ACK_ON_ERROR = "AckOnError"
142
+ T_FRAG_DIRECTION = "FRDirection" #YANG direction
143
+ T_FRAG_PROF = "FRModeProfile"
144
+ T_FRAG_DTAG_SIZE = "dtagSize" #YANG dtag-size
145
+ T_FRAG_W_SIZE = "WSize" #YANG w-size
146
+ T_FRAG_FCN = "FCNSize" #YANG fcn-size
147
+ T_FRAG_WINDOW_SIZE = "windowSize" #YANG window-size
148
+ T_MAX_INTER_FRAME = "MaxInterFrame" #YANG max-interleaved-frames
149
+ T_FRAG_ACK_BEHAVIOR = "ackBehavior" #YANG ack-behavior
150
+ T_FRAG_AFTER_ALL1 = "afterAll1"
151
+ T_FRAG_AFTER_ALL0 = "afterAll0"
152
+ T_FRAG_AFTER_ANY = "afterAny"
153
+ T_FRAG_TILE = "tileSize" #YANG tile-size
154
+ T_FRAG_MIC = "MICALgorithm" #YANG rcs-algorithm
155
+ T_MAX_PACKET_SIZE = "MaxPcktSize" #YANG maximum-packet-size
156
+ T_MAX_INTER_FRAME = "MaxInterFrame" #YANG max-interleaved-frames
157
+ T_FRAG_MAX_RETRY = "maxRetry" #YANG max-ack-requests
158
+ T_FRAG_TIMEOUT = "timeout" #YANG retransmission-timer
159
+ T_FRAG_L2WORDSIZE = "L2WordSize" #YANG l2-word-size
160
+ T_FRAG_LAST_TILE_IN_ALL1 = "lastTileInAll1" #YANG tile-in-all-1
161
+ T_FRAG_RFC8724 = "RCS_CRC32"
162
+
163
+ T_POSITION_CORE = "core"
164
+ T_POSITION_DEVICE = "device"
165
+
166
+ T_INDEXES = "Indexes"
167
+ T_CMD_INDIRECT = "INDIRECT"
168
+
169
+ T_O_DELTA = "COAP.O-DELTA"
170
+ T_O_LENGTH = "COAP.O-LENGTH"
171
+ T_O_VALUE = "COAP.O-VALUE"
172
+
173
+
174
+ YANG_ID = {
175
+ "module": "ietf-schc",
176
+ T_CDA_APPIID: "cda-appiid",
177
+ T_CDA_COMP_CKSUM: "cda-compute",
178
+ T_CDA_COMP_LEN: "cda-compute",
179
+ T_CDA_DEVIID: "cda-deviid",
180
+ T_CDA_LSB: "cda-lsb",
181
+ T_CDA_MAP_SENT: "cda-mapping-sent",
182
+ T_CDA_NOT_SENT: "cda-not-sent",
183
+ T_CDA_VAL_SENT: "cda-value-sent",
184
+ T_DIR_BI: "di-bidirectional",
185
+ T_DIR_DW: "di-down",
186
+ T_DIR_UP: "di-up",
187
+ T_COAP_CODE: "fid-coap-code",
188
+ T_COAP_MID: "fid-coap-mid",
189
+ T_OSCORE_FLAGS: "fid-coap-option-oscore-flags",
190
+ T_OSCORE_FLAGS_FLAGS: "fid-coap-option-oscore-flags-flags",
191
+ T_OSCORE_FLAGS_N: "fid-coap-option-oscore-flags-n",
192
+ T_OSCORE_KID: "fid-coap-option-oscore-kid",
193
+ T_OSCORE_KIDCTX: "fid-coap-option-oscore-kidctx",
194
+ T_OSCORE_PIV: "fid-coap-option-oscore-piv",
195
+ T_COAP_TKL: "fid-coap-tkl",
196
+ T_COAP_TOKEN: "fid-coap-token",
197
+ T_COAP_TYPE: "fid-coap-type",
198
+ T_COAP_VERSION: "fid-coap-version",
199
+ T_IPV6_APP_IID: "fid-ipv6-appiid",
200
+ T_IPV6_APP_PREFIX: "fid-ipv6-appprefix",
201
+ T_IPV6_DEV_IID: "fid-ipv6-deviid",
202
+ T_IPV6_DEV_PREFIX: "fid-ipv6-devprefix",
203
+ T_IPV6_FL: "fid-ipv6-flowlabel",
204
+ T_IPV6_HOP_LMT: "fid-ipv6-hoplimit",
205
+ T_IPV6_NXT: "fid-ipv6-nextheader",
206
+ T_IPV6_LEN: "fid-ipv6-payload-length",
207
+ T_IPV6_TC: "fid-ipv6-trafficclass",
208
+ T_IPV6_VER: "fid-ipv6-version",
209
+ T_IPV4_VER: "fid-ipv6-version",
210
+ T_UDP_APP_PORT: "fid-udp-app-port",
211
+ T_UDP_CKSUM: "fid-udp-checksum",
212
+ T_UDP_DEV_PORT: "fid-udp-dev-port",
213
+ T_UDP_LEN: "fid-udp-length",
214
+ T_FUNCTION_TKL: "fl-token-length",
215
+ T_FUNCTION_VAR: "fl-variable",
216
+ T_FUNCTION_VARBIT: "fl-variable-bit",
217
+ T_FRAG_ACK_ALWAYS: "fragmentation-mode-ack-always",
218
+ T_FRAG_ACK_ON_ERROR: "fragmentation-mode-ack-on-error",
219
+ T_FRAG_NO_ACK: "fragmentation-mode-no-ack",
220
+ T_MO_EQUAL: "mo-equal",
221
+ T_MO_IGNORE: "mo-ignore",
222
+ T_MO_MMAP: "mo-match-mapping",
223
+ T_MO_MSB: "mo-msb",
224
+ T_FRAG_RFC8724: "rcs-crc32",
225
+ # from OAM
226
+ T_ICMPV6_CODE: "fid-icmpv6-code",
227
+ T_ICMPV6_TYPE: "fid-icmpv6-type",
228
+ T_ICMPV6_IDENT: "fid-icmpv6-identifier",
229
+ T_ICMPV6_SEQNO: "fid-icmpv6-sequence",
230
+ T_ICMPV6_CKSUM: "fid-icmpv6-checksum",
231
+ T_ICMPV6_PAYLOAD: "fid-icmpv6-payload",
232
+ # for quentin extension
233
+ T_O_DELTA: "fid-coap-delta-types",
234
+ T_O_LENGTH: "fid-coap-length",
235
+ T_O_VALUE: "fid-coap-value",
236
+
237
+ T_UNUSED: "fid-unused",
238
+ T_PAYLOAD: "fid-payload",
239
+ }
240
+
241
+ COAP_OPTION_NUMBERS = {
242
+ T_COAP_OPT_IF_MATCH: 1,
243
+ T_COAP_OPT_URI_HOST: 3,
244
+ T_COAP_OPT_ETAG: 4,
245
+ T_COAP_OPT_IF_NONE_MATCH: 5,
246
+ T_COAP_OPT_OBS: 6,
247
+ T_COAP_OPT_URI_PORT: 7,
248
+ T_COAP_OPT_LOC_PATH: 8,
249
+ T_COAP_OPT_URI_PATH: 11,
250
+ T_COAP_OPT_CONT_FORMAT: 12,
251
+ T_COAP_OPT_MAX_AGE: 14,
252
+ T_COAP_OPT_URI_QUERY: 15,
253
+ T_COAP_OPT_ACCEPT: 17,
254
+ T_COAP_OPT_LOC_QUERY: 20,
255
+ T_COAP_OPT_BLOCK2: 23,
256
+ T_COAP_OPT_BLOCK1: 27,
257
+ T_COAP_OPT_SIZE2: 28,
258
+ T_COAP_OPT_PROXY_URI: 35,
259
+ T_COAP_OPT_PROXY_SCHEME: 39,
260
+ T_COAP_OPT_SIZE1: 60,
261
+ T_COAP_OPT_NO_RESP: 258,
262
+ }
263
+
264
+ import ipaddress
265
+
266
+ def adapt_value(value, length=None, FID=None):
267
+ """transform any value of any type in the smallest bytearray.
268
+ FID allows to convert properly the string to IPv6 address."""
269
+
270
+ if type(value) is list:
271
+ result = []
272
+ for e in value:
273
+ result.append(adapt_value(e, length, FID))
274
+
275
+ return value
276
+
277
+ if type(value) is int:
278
+
279
+ if FID in [T_IPV6_APP_IID, T_IPV6_APP_PREFIX, T_IPV6_DEV_IID, T_IPV6_DEV_PREFIX] and length != None:
280
+ return value.to_bytes(length//8, byteorder='big')
281
+
282
+ size = 0
283
+ v = value
284
+ while v != 0:
285
+ v = v >> 8
286
+ size += 1
287
+
288
+ if size == 0: # when value == 0 store 0
289
+ size=1
290
+
291
+ return value.to_bytes (size, byteorder='big')
292
+ if type(value) is str:
293
+ if FID in [T_IPV6_APP_IID, T_IPV6_APP_PREFIX, T_IPV6_DEV_IID, T_IPV6_DEV_PREFIX]:
294
+ # string has to be converted as IPv6 addresses
295
+
296
+ slash_pos = value.find("/")
297
+ if slash_pos != -1:
298
+ # a prefix is given, remove / to be compatible with ip_address
299
+ value = value[:slash_pos]
300
+
301
+ addr = ipaddress.ip_address(value)
302
+ if addr.version != 6: # expect an IPv6 address
303
+ raise ValueError ("only IPv6 is supported, can not support {}".format(addr.version))
304
+
305
+ if FID in [T_IPV6_DEV_PREFIX, T_IPV6_APP_PREFIX]: #prefix top 8
306
+ return addr.packed[:8]
307
+ elif FID in [T_IPV6_DEV_IID, T_IPV6_APP_IID]: #IID bottom 8
308
+ return addr.packed[8:]
309
+ else:
310
+ raise ValueError ("{} Fid not found".format(FID))
311
+ else: # a regular string
312
+ return value.encode()
313
+ elif type(value) is bytes:
314
+ return value
315
+ elif value == None:
316
+ return None
317
+ else:
318
+ raise ValueError("Unknown type", type(value))