packbin 0.1.9__tar.gz
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.
- packbin-0.1.9/PKG-INFO +195 -0
- packbin-0.1.9/README.md +187 -0
- packbin-0.1.9/pyproject.toml +18 -0
- packbin-0.1.9/setup.cfg +4 -0
- packbin-0.1.9/src/packbin/__init__.py +65 -0
- packbin-0.1.9/src/packbin/_errors.py +49 -0
- packbin-0.1.9/src/packbin/_nodes.py +323 -0
- packbin-0.1.9/src/packbin/_pack.py +264 -0
- packbin-0.1.9/src/packbin/_scheme.py +120 -0
- packbin-0.1.9/src/packbin/_unpack.py +327 -0
- packbin-0.1.9/src/packbin.egg-info/PKG-INFO +195 -0
- packbin-0.1.9/src/packbin.egg-info/SOURCES.txt +16 -0
- packbin-0.1.9/src/packbin.egg-info/dependency_links.txt +1 -0
- packbin-0.1.9/src/packbin.egg-info/top_level.txt +1 -0
- packbin-0.1.9/tests/test_binding.py +130 -0
- packbin-0.1.9/tests/test_fields.py +300 -0
- packbin-0.1.9/tests/test_position.py +175 -0
- packbin-0.1.9/tests/test_scheme.py +199 -0
packbin-0.1.9/PKG-INFO
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: packbin
|
|
3
|
+
Version: 0.1.9
|
|
4
|
+
Summary: Pack and unpack a caller-owned field list
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# packbin
|
|
10
|
+
Binary packing and unpacking across languages, declarative mapping, and zero overhead in the binary data.
|
|
11
|
+
|
|
12
|
+
Both sides keep the same field list. The bytes are only the values.
|
|
13
|
+
|
|
14
|
+
Can be used for WebSocket, TCP, UDP, and other means of efficient communication
|
|
15
|
+
|
|
16
|
+
## Example
|
|
17
|
+
|
|
18
|
+
Python → binary → TypeScript
|
|
19
|
+
|
|
20
|
+
### Python
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from packbin import BinaryPacker, Scheme, flags, i16, i32, u8, u16
|
|
24
|
+
|
|
25
|
+
class Position:
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.sid = 1
|
|
28
|
+
self.lat = 500_000_000
|
|
29
|
+
self.lon = 300_000_000
|
|
30
|
+
self.profile = 1
|
|
31
|
+
self.heading = None
|
|
32
|
+
self.speed = None
|
|
33
|
+
self.altitude = None
|
|
34
|
+
|
|
35
|
+
def bind(name):
|
|
36
|
+
return (lambda row: getattr(row, name), lambda row, value: setattr(row, name, value))
|
|
37
|
+
|
|
38
|
+
target = Scheme(
|
|
39
|
+
0x40,
|
|
40
|
+
Position,
|
|
41
|
+
u16(0, *bind("sid")),
|
|
42
|
+
i32(1, *bind("lat")),
|
|
43
|
+
i32(2, *bind("lon")),
|
|
44
|
+
u8(3, *bind("profile")),
|
|
45
|
+
flags(
|
|
46
|
+
u16(4, *bind("heading")),
|
|
47
|
+
u8(5, *bind("speed")),
|
|
48
|
+
i16(6, *bind("altitude")),
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
raw = BinaryPacker.pack(target, Position())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
40 01 00 00 65 cd 1d 00 a3 e1 11 01 00
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### TypeScript
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { BinaryPacker, flags, i16, i32, scheme, u8, u16 } from "packbin"
|
|
63
|
+
|
|
64
|
+
class Target {
|
|
65
|
+
sid = 1
|
|
66
|
+
lat = 500_000_000
|
|
67
|
+
lon = 300_000_000
|
|
68
|
+
profile = 1
|
|
69
|
+
heading: number | null = null
|
|
70
|
+
speed: number | null = null
|
|
71
|
+
altitude: number | null = null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const target = scheme<Target>(
|
|
75
|
+
0x40,
|
|
76
|
+
u16(0, (x) => x.sid),
|
|
77
|
+
i32(1, (x) => x.lat),
|
|
78
|
+
i32(2, (x) => x.lon),
|
|
79
|
+
u8(3, (x) => x.profile),
|
|
80
|
+
flags([
|
|
81
|
+
u16(4, (x) => x.heading),
|
|
82
|
+
u8(5, (x) => x.speed),
|
|
83
|
+
i16(6, (x) => x.altitude),
|
|
84
|
+
]),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const got = BinaryPacker.unpack(target, raw)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`flags` is how an optional field takes no space when you have no value for it. `heading`, `speed`, and `altitude` are measurements, so `0` is still a value and has to be written. `None` means the field is not in the packet.
|
|
91
|
+
|
|
92
|
+
`motion` is always one byte in front of those fields. Bit 0 is `heading`, bit 1 is `speed`, bit 2 is `altitude`. A set bit writes that field next. A clear bit skips it.
|
|
93
|
+
|
|
94
|
+
| Field | Type | Bytes when present | Values |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `heading` | `u16` | 2 | 0 … 65535 |
|
|
97
|
+
| `speed` | `u8` | 1 | 0 … 255 |
|
|
98
|
+
| `altitude` | `i16` | 2 | −32768 … 32767 |
|
|
99
|
+
|
|
100
|
+
In this example all three are `None`, so `motion` is `00` and those 5 bytes are absent. The packet is 13 bytes: type number 1, `sid` 2, `lat` 4, `lon` 4, `profile` 1, `motion` 1.
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
40 type
|
|
104
|
+
01 00 sid
|
|
105
|
+
00 65 cd 1d lat
|
|
106
|
+
00 a3 e1 11 lon
|
|
107
|
+
01 profile
|
|
108
|
+
00 motion
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`heading = 90` sets bit 0, so `motion` is `01` and `5a 00` follows it. `heading = 0` sets the same bit and writes `00 00`. `speed = 10` sets bit 1 and writes one byte. The present fields are written in the order listed, and only those.
|
|
112
|
+
|
|
113
|
+
## Example
|
|
114
|
+
|
|
115
|
+
C# → binary → Rust
|
|
116
|
+
|
|
117
|
+
### C#
|
|
118
|
+
|
|
119
|
+
```csharp
|
|
120
|
+
using Packbin;
|
|
121
|
+
|
|
122
|
+
sealed class User
|
|
123
|
+
{
|
|
124
|
+
public string Username { get; set; } = "";
|
|
125
|
+
public List<Role> Roles { get; set; } = [];
|
|
126
|
+
public Dictionary<string, ActionList> Access { get; set; } = [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
sealed class Role { public string RoleName { get; set; } = ""; }
|
|
130
|
+
sealed class ActionName { public string Action { get; set; } = ""; }
|
|
131
|
+
sealed class ActionList { public List<ActionName> Actions { get; set; } = []; }
|
|
132
|
+
|
|
133
|
+
var userScheme = new Scheme<User>(1, f => [
|
|
134
|
+
f.Utf8(0, x => x.Username),
|
|
135
|
+
f.List(x => x.Roles, r => r.Utf8(0, role => role.RoleName)),
|
|
136
|
+
f.Dict(x => x.Access, e => e.List(a => a.Actions, n => n.Utf8(0, action => action.Action)))]);
|
|
137
|
+
|
|
138
|
+
var raw = BinaryPacker.Pack(userScheme, new User
|
|
139
|
+
{
|
|
140
|
+
Username = "ada",
|
|
141
|
+
Roles = [new Role { RoleName = "user" }, new Role { RoleName = "admin" }],
|
|
142
|
+
Access = new()
|
|
143
|
+
{
|
|
144
|
+
["map"] = new ActionList { Actions = [new ActionName { Action = "read" }, new ActionName { Action = "edit" }] },
|
|
145
|
+
["store"] = new ActionList { Actions = [new ActionName { Action = "write" }] },
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
01 03 00 61 64 61 02 00 04 00 75 73 65 72 05 00 61 64 6d 69 6e
|
|
152
|
+
02 00 03 00 6d 61 70 02 00 04 00 72 65 61 64 04 00 65 64 69 74
|
|
153
|
+
05 00 73 74 6f 72 65 01 00 05 00 77 72 69 74 65
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The first byte is the scheme type number.
|
|
157
|
+
|
|
158
|
+
### Rust
|
|
159
|
+
|
|
160
|
+
```rust
|
|
161
|
+
use packbin::{dict, list, unpack_map, utf8, MapScheme};
|
|
162
|
+
|
|
163
|
+
let user = MapScheme::new(
|
|
164
|
+
1,
|
|
165
|
+
vec![
|
|
166
|
+
utf8("username"),
|
|
167
|
+
list("roles", utf8("role")),
|
|
168
|
+
dict("access", list("actions", utf8("action"))),
|
|
169
|
+
],
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
let got = unpack_map(&user, &raw).unwrap();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Data types
|
|
176
|
+
|
|
177
|
+
| Helper | What it writes |
|
|
178
|
+
|--------|----------------|
|
|
179
|
+
| `u8` `u16` `u32` `u64` | unsigned integer, little-endian |
|
|
180
|
+
| `i8` `i16` `i32` `i64` | signed integer, little-endian |
|
|
181
|
+
| `f32` `f64` | IEEE 754 float |
|
|
182
|
+
| `bytes(n)` | exactly `n` raw bytes |
|
|
183
|
+
| `be(field)` | that number, big-endian |
|
|
184
|
+
| `utf8` | UTF-8 string, `u16` length |
|
|
185
|
+
| `list` | `u16` count, then that many elements |
|
|
186
|
+
| `dict` | `u16` pair count; keys in unsigned byte order |
|
|
187
|
+
| `flags(name, fields)` | one `u8`; bit 0 is the first field; a clear bit omits that field |
|
|
188
|
+
| `when(eq(field, value), fields)` | the group only when an earlier field equals `value` |
|
|
189
|
+
| `repeat(fields)` | the group until the buffer ends |
|
|
190
|
+
|
|
191
|
+
[`_docs/01_solution/schema.md`](_docs/01_solution/schema.md)
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT
|
packbin-0.1.9/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# packbin
|
|
2
|
+
Binary packing and unpacking across languages, declarative mapping, and zero overhead in the binary data.
|
|
3
|
+
|
|
4
|
+
Both sides keep the same field list. The bytes are only the values.
|
|
5
|
+
|
|
6
|
+
Can be used for WebSocket, TCP, UDP, and other means of efficient communication
|
|
7
|
+
|
|
8
|
+
## Example
|
|
9
|
+
|
|
10
|
+
Python → binary → TypeScript
|
|
11
|
+
|
|
12
|
+
### Python
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from packbin import BinaryPacker, Scheme, flags, i16, i32, u8, u16
|
|
16
|
+
|
|
17
|
+
class Position:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.sid = 1
|
|
20
|
+
self.lat = 500_000_000
|
|
21
|
+
self.lon = 300_000_000
|
|
22
|
+
self.profile = 1
|
|
23
|
+
self.heading = None
|
|
24
|
+
self.speed = None
|
|
25
|
+
self.altitude = None
|
|
26
|
+
|
|
27
|
+
def bind(name):
|
|
28
|
+
return (lambda row: getattr(row, name), lambda row, value: setattr(row, name, value))
|
|
29
|
+
|
|
30
|
+
target = Scheme(
|
|
31
|
+
0x40,
|
|
32
|
+
Position,
|
|
33
|
+
u16(0, *bind("sid")),
|
|
34
|
+
i32(1, *bind("lat")),
|
|
35
|
+
i32(2, *bind("lon")),
|
|
36
|
+
u8(3, *bind("profile")),
|
|
37
|
+
flags(
|
|
38
|
+
u16(4, *bind("heading")),
|
|
39
|
+
u8(5, *bind("speed")),
|
|
40
|
+
i16(6, *bind("altitude")),
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
raw = BinaryPacker.pack(target, Position())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
40 01 00 00 65 cd 1d 00 a3 e1 11 01 00
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### TypeScript
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { BinaryPacker, flags, i16, i32, scheme, u8, u16 } from "packbin"
|
|
55
|
+
|
|
56
|
+
class Target {
|
|
57
|
+
sid = 1
|
|
58
|
+
lat = 500_000_000
|
|
59
|
+
lon = 300_000_000
|
|
60
|
+
profile = 1
|
|
61
|
+
heading: number | null = null
|
|
62
|
+
speed: number | null = null
|
|
63
|
+
altitude: number | null = null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const target = scheme<Target>(
|
|
67
|
+
0x40,
|
|
68
|
+
u16(0, (x) => x.sid),
|
|
69
|
+
i32(1, (x) => x.lat),
|
|
70
|
+
i32(2, (x) => x.lon),
|
|
71
|
+
u8(3, (x) => x.profile),
|
|
72
|
+
flags([
|
|
73
|
+
u16(4, (x) => x.heading),
|
|
74
|
+
u8(5, (x) => x.speed),
|
|
75
|
+
i16(6, (x) => x.altitude),
|
|
76
|
+
]),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const got = BinaryPacker.unpack(target, raw)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`flags` is how an optional field takes no space when you have no value for it. `heading`, `speed`, and `altitude` are measurements, so `0` is still a value and has to be written. `None` means the field is not in the packet.
|
|
83
|
+
|
|
84
|
+
`motion` is always one byte in front of those fields. Bit 0 is `heading`, bit 1 is `speed`, bit 2 is `altitude`. A set bit writes that field next. A clear bit skips it.
|
|
85
|
+
|
|
86
|
+
| Field | Type | Bytes when present | Values |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `heading` | `u16` | 2 | 0 … 65535 |
|
|
89
|
+
| `speed` | `u8` | 1 | 0 … 255 |
|
|
90
|
+
| `altitude` | `i16` | 2 | −32768 … 32767 |
|
|
91
|
+
|
|
92
|
+
In this example all three are `None`, so `motion` is `00` and those 5 bytes are absent. The packet is 13 bytes: type number 1, `sid` 2, `lat` 4, `lon` 4, `profile` 1, `motion` 1.
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
40 type
|
|
96
|
+
01 00 sid
|
|
97
|
+
00 65 cd 1d lat
|
|
98
|
+
00 a3 e1 11 lon
|
|
99
|
+
01 profile
|
|
100
|
+
00 motion
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`heading = 90` sets bit 0, so `motion` is `01` and `5a 00` follows it. `heading = 0` sets the same bit and writes `00 00`. `speed = 10` sets bit 1 and writes one byte. The present fields are written in the order listed, and only those.
|
|
104
|
+
|
|
105
|
+
## Example
|
|
106
|
+
|
|
107
|
+
C# → binary → Rust
|
|
108
|
+
|
|
109
|
+
### C#
|
|
110
|
+
|
|
111
|
+
```csharp
|
|
112
|
+
using Packbin;
|
|
113
|
+
|
|
114
|
+
sealed class User
|
|
115
|
+
{
|
|
116
|
+
public string Username { get; set; } = "";
|
|
117
|
+
public List<Role> Roles { get; set; } = [];
|
|
118
|
+
public Dictionary<string, ActionList> Access { get; set; } = [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
sealed class Role { public string RoleName { get; set; } = ""; }
|
|
122
|
+
sealed class ActionName { public string Action { get; set; } = ""; }
|
|
123
|
+
sealed class ActionList { public List<ActionName> Actions { get; set; } = []; }
|
|
124
|
+
|
|
125
|
+
var userScheme = new Scheme<User>(1, f => [
|
|
126
|
+
f.Utf8(0, x => x.Username),
|
|
127
|
+
f.List(x => x.Roles, r => r.Utf8(0, role => role.RoleName)),
|
|
128
|
+
f.Dict(x => x.Access, e => e.List(a => a.Actions, n => n.Utf8(0, action => action.Action)))]);
|
|
129
|
+
|
|
130
|
+
var raw = BinaryPacker.Pack(userScheme, new User
|
|
131
|
+
{
|
|
132
|
+
Username = "ada",
|
|
133
|
+
Roles = [new Role { RoleName = "user" }, new Role { RoleName = "admin" }],
|
|
134
|
+
Access = new()
|
|
135
|
+
{
|
|
136
|
+
["map"] = new ActionList { Actions = [new ActionName { Action = "read" }, new ActionName { Action = "edit" }] },
|
|
137
|
+
["store"] = new ActionList { Actions = [new ActionName { Action = "write" }] },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
01 03 00 61 64 61 02 00 04 00 75 73 65 72 05 00 61 64 6d 69 6e
|
|
144
|
+
02 00 03 00 6d 61 70 02 00 04 00 72 65 61 64 04 00 65 64 69 74
|
|
145
|
+
05 00 73 74 6f 72 65 01 00 05 00 77 72 69 74 65
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The first byte is the scheme type number.
|
|
149
|
+
|
|
150
|
+
### Rust
|
|
151
|
+
|
|
152
|
+
```rust
|
|
153
|
+
use packbin::{dict, list, unpack_map, utf8, MapScheme};
|
|
154
|
+
|
|
155
|
+
let user = MapScheme::new(
|
|
156
|
+
1,
|
|
157
|
+
vec![
|
|
158
|
+
utf8("username"),
|
|
159
|
+
list("roles", utf8("role")),
|
|
160
|
+
dict("access", list("actions", utf8("action"))),
|
|
161
|
+
],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
let got = unpack_map(&user, &raw).unwrap();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Data types
|
|
168
|
+
|
|
169
|
+
| Helper | What it writes |
|
|
170
|
+
|--------|----------------|
|
|
171
|
+
| `u8` `u16` `u32` `u64` | unsigned integer, little-endian |
|
|
172
|
+
| `i8` `i16` `i32` `i64` | signed integer, little-endian |
|
|
173
|
+
| `f32` `f64` | IEEE 754 float |
|
|
174
|
+
| `bytes(n)` | exactly `n` raw bytes |
|
|
175
|
+
| `be(field)` | that number, big-endian |
|
|
176
|
+
| `utf8` | UTF-8 string, `u16` length |
|
|
177
|
+
| `list` | `u16` count, then that many elements |
|
|
178
|
+
| `dict` | `u16` pair count; keys in unsigned byte order |
|
|
179
|
+
| `flags(name, fields)` | one `u8`; bit 0 is the first field; a clear bit omits that field |
|
|
180
|
+
| `when(eq(field, value), fields)` | the group only when an earlier field equals `value` |
|
|
181
|
+
| `repeat(fields)` | the group until the buffer ends |
|
|
182
|
+
|
|
183
|
+
[`_docs/01_solution/schema.md`](_docs/01_solution/schema.md)
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "packbin"
|
|
3
|
+
version = "0.1.9"
|
|
4
|
+
description = "Pack and unpack a caller-owned field list"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = []
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["setuptools>=61"]
|
|
12
|
+
build-backend = "setuptools.build_meta"
|
|
13
|
+
|
|
14
|
+
[tool.setuptools.packages.find]
|
|
15
|
+
where = ["src"]
|
|
16
|
+
|
|
17
|
+
[tool.pytest.ini_options]
|
|
18
|
+
pythonpath = ["src"]
|
packbin-0.1.9/setup.cfg
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from packbin._errors import ShortPacket, TrailingBytes, TypeMismatch, UnpackResult
|
|
4
|
+
from packbin._nodes import (
|
|
5
|
+
be,
|
|
6
|
+
bits,
|
|
7
|
+
bool,
|
|
8
|
+
bytes,
|
|
9
|
+
dict,
|
|
10
|
+
eq,
|
|
11
|
+
flags,
|
|
12
|
+
flag_byte,
|
|
13
|
+
group,
|
|
14
|
+
i8,
|
|
15
|
+
i16,
|
|
16
|
+
i32,
|
|
17
|
+
i64,
|
|
18
|
+
f32,
|
|
19
|
+
f64,
|
|
20
|
+
list,
|
|
21
|
+
repeat,
|
|
22
|
+
sized,
|
|
23
|
+
u2,
|
|
24
|
+
u8,
|
|
25
|
+
u16,
|
|
26
|
+
u32,
|
|
27
|
+
u64,
|
|
28
|
+
utf8,
|
|
29
|
+
when,
|
|
30
|
+
)
|
|
31
|
+
from packbin._scheme import BinaryPacker, Scheme
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"BinaryPacker",
|
|
35
|
+
"Scheme",
|
|
36
|
+
"ShortPacket",
|
|
37
|
+
"TrailingBytes",
|
|
38
|
+
"TypeMismatch",
|
|
39
|
+
"UnpackResult",
|
|
40
|
+
"u8",
|
|
41
|
+
"u16",
|
|
42
|
+
"u32",
|
|
43
|
+
"u64",
|
|
44
|
+
"i8",
|
|
45
|
+
"i16",
|
|
46
|
+
"i32",
|
|
47
|
+
"i64",
|
|
48
|
+
"f32",
|
|
49
|
+
"f64",
|
|
50
|
+
"bool",
|
|
51
|
+
"bytes",
|
|
52
|
+
"be",
|
|
53
|
+
"flags",
|
|
54
|
+
"flag_byte",
|
|
55
|
+
"eq",
|
|
56
|
+
"when",
|
|
57
|
+
"repeat",
|
|
58
|
+
"group",
|
|
59
|
+
"sized",
|
|
60
|
+
"u2",
|
|
61
|
+
"bits",
|
|
62
|
+
"utf8",
|
|
63
|
+
"list",
|
|
64
|
+
"dict",
|
|
65
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class ShortPacket:
|
|
11
|
+
field: str
|
|
12
|
+
needed: int
|
|
13
|
+
left: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class TrailingBytes:
|
|
18
|
+
left: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class TypeMismatch:
|
|
23
|
+
expected: int
|
|
24
|
+
actual: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class UnpackResult(Generic[T]):
|
|
29
|
+
ok: bool
|
|
30
|
+
value: T | None = None
|
|
31
|
+
error: ShortPacket | TrailingBytes | TypeMismatch | None = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def field(self) -> str | None:
|
|
35
|
+
if isinstance(self.error, ShortPacket):
|
|
36
|
+
return self.error.field
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def needed(self) -> int | None:
|
|
41
|
+
if isinstance(self.error, ShortPacket):
|
|
42
|
+
return self.error.needed
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def left(self) -> int | None:
|
|
47
|
+
if isinstance(self.error, (ShortPacket, TrailingBytes)):
|
|
48
|
+
return self.error.left
|
|
49
|
+
return None
|