blerpc-protocol 0.8.0__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.
- blerpc_protocol-0.8.0/LICENSE +13 -0
- blerpc_protocol-0.8.0/PKG-INFO +102 -0
- blerpc_protocol-0.8.0/README.md +81 -0
- blerpc_protocol-0.8.0/pyproject.toml +41 -0
- blerpc_protocol-0.8.0/setup.cfg +4 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol/__init__.py +62 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol/command.py +76 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol/container.py +387 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol/crypto.py +598 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol.egg-info/PKG-INFO +102 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol.egg-info/SOURCES.txt +12 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol.egg-info/dependency_links.txt +1 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol.egg-info/requires.txt +1 -0
- blerpc_protocol-0.8.0/src/blerpc_protocol.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2026 tdaira
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blerpc-protocol
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Container and command protocol layers for BLE RPC
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://github.com/tdaira/blerpc-protocol
|
|
7
|
+
Project-URL: Repository, https://github.com/tdaira/blerpc-protocol
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Topic :: System :: Networking
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: cryptography>=41.0
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# blerpc-protocol
|
|
23
|
+
|
|
24
|
+
BLE RPC protocol library for Python and C.
|
|
25
|
+
|
|
26
|
+
Part of the [bleRPC](https://blerpc.net) project.
|
|
27
|
+
|
|
28
|
+
## Overview
|
|
29
|
+
|
|
30
|
+
Python and C implementation of the bleRPC binary protocol:
|
|
31
|
+
|
|
32
|
+
- Container fragmentation and reassembly with MTU-aware splitting
|
|
33
|
+
- Command packet encoding/decoding with protobuf payload support
|
|
34
|
+
- Control messages (timeout, stream end, capabilities, error)
|
|
35
|
+
- **Encryption layer** — E2E encryption with X25519 key exchange, Ed25519 signatures, and AES-128-GCM
|
|
36
|
+
|
|
37
|
+
The Python and C implementations are fully compatible and share the same wire format.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
pip install blerpc-protocol
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from blerpc_protocol import ContainerSplitter, ContainerAssembler, CommandPacket, CommandType
|
|
49
|
+
|
|
50
|
+
# Encode a command
|
|
51
|
+
packet = CommandPacket(CommandType.REQUEST, "Echo", protobuf_bytes)
|
|
52
|
+
payload = packet.serialize()
|
|
53
|
+
|
|
54
|
+
# Split into BLE-sized containers
|
|
55
|
+
splitter = ContainerSplitter(mtu=247)
|
|
56
|
+
containers = splitter.split(payload)
|
|
57
|
+
|
|
58
|
+
# Send containers over BLE, then reassemble on the other side
|
|
59
|
+
assembler = ContainerAssembler()
|
|
60
|
+
for container in received_containers:
|
|
61
|
+
result = assembler.feed(container)
|
|
62
|
+
if result is not None:
|
|
63
|
+
response = CommandPacket.deserialize(result)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Encryption
|
|
67
|
+
|
|
68
|
+
The library provides E2E encryption using a 4-step key exchange protocol (X25519 ECDH + Ed25519 signatures) and AES-128-GCM session encryption.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from blerpc_protocol.crypto import central_perform_key_exchange, BlerpcCryptoSession
|
|
72
|
+
|
|
73
|
+
# Perform key exchange (central side)
|
|
74
|
+
session = await central_perform_key_exchange(send=ble_send, receive=ble_receive)
|
|
75
|
+
|
|
76
|
+
# Encrypt outgoing commands
|
|
77
|
+
ciphertext = session.encrypt(plaintext)
|
|
78
|
+
|
|
79
|
+
# Decrypt incoming commands
|
|
80
|
+
plaintext = session.decrypt(ciphertext)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## C Library
|
|
84
|
+
|
|
85
|
+
The C implementation is a Zephyr module with zero external dependencies. Add it to your `west.yml` manifest:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
- name: blerpc-protocol
|
|
89
|
+
url: https://github.com/tdaira/blerpc-protocol
|
|
90
|
+
revision: main
|
|
91
|
+
path: modules/lib/blerpc-protocol
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Headers are in `c/include/blerpc_protocol/`. See [container.h](c/include/blerpc_protocol/container.h), [command.h](c/include/blerpc_protocol/command.h), and [crypto.h](c/include/blerpc_protocol/crypto.h) for the API.
|
|
95
|
+
|
|
96
|
+
## Requirements
|
|
97
|
+
|
|
98
|
+
- Python 3.11+
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
[Apache-2.0](LICENSE)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# blerpc-protocol
|
|
2
|
+
|
|
3
|
+
BLE RPC protocol library for Python and C.
|
|
4
|
+
|
|
5
|
+
Part of the [bleRPC](https://blerpc.net) project.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Python and C implementation of the bleRPC binary protocol:
|
|
10
|
+
|
|
11
|
+
- Container fragmentation and reassembly with MTU-aware splitting
|
|
12
|
+
- Command packet encoding/decoding with protobuf payload support
|
|
13
|
+
- Control messages (timeout, stream end, capabilities, error)
|
|
14
|
+
- **Encryption layer** — E2E encryption with X25519 key exchange, Ed25519 signatures, and AES-128-GCM
|
|
15
|
+
|
|
16
|
+
The Python and C implementations are fully compatible and share the same wire format.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
pip install blerpc-protocol
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from blerpc_protocol import ContainerSplitter, ContainerAssembler, CommandPacket, CommandType
|
|
28
|
+
|
|
29
|
+
# Encode a command
|
|
30
|
+
packet = CommandPacket(CommandType.REQUEST, "Echo", protobuf_bytes)
|
|
31
|
+
payload = packet.serialize()
|
|
32
|
+
|
|
33
|
+
# Split into BLE-sized containers
|
|
34
|
+
splitter = ContainerSplitter(mtu=247)
|
|
35
|
+
containers = splitter.split(payload)
|
|
36
|
+
|
|
37
|
+
# Send containers over BLE, then reassemble on the other side
|
|
38
|
+
assembler = ContainerAssembler()
|
|
39
|
+
for container in received_containers:
|
|
40
|
+
result = assembler.feed(container)
|
|
41
|
+
if result is not None:
|
|
42
|
+
response = CommandPacket.deserialize(result)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Encryption
|
|
46
|
+
|
|
47
|
+
The library provides E2E encryption using a 4-step key exchange protocol (X25519 ECDH + Ed25519 signatures) and AES-128-GCM session encryption.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from blerpc_protocol.crypto import central_perform_key_exchange, BlerpcCryptoSession
|
|
51
|
+
|
|
52
|
+
# Perform key exchange (central side)
|
|
53
|
+
session = await central_perform_key_exchange(send=ble_send, receive=ble_receive)
|
|
54
|
+
|
|
55
|
+
# Encrypt outgoing commands
|
|
56
|
+
ciphertext = session.encrypt(plaintext)
|
|
57
|
+
|
|
58
|
+
# Decrypt incoming commands
|
|
59
|
+
plaintext = session.decrypt(ciphertext)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## C Library
|
|
63
|
+
|
|
64
|
+
The C implementation is a Zephyr module with zero external dependencies. Add it to your `west.yml` manifest:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
- name: blerpc-protocol
|
|
68
|
+
url: https://github.com/tdaira/blerpc-protocol
|
|
69
|
+
revision: main
|
|
70
|
+
path: modules/lib/blerpc-protocol
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Headers are in `c/include/blerpc_protocol/`. See [container.h](c/include/blerpc_protocol/container.h), [command.h](c/include/blerpc_protocol/command.h), and [crypto.h](c/include/blerpc_protocol/crypto.h) for the API.
|
|
74
|
+
|
|
75
|
+
## Requirements
|
|
76
|
+
|
|
77
|
+
- Python 3.11+
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
[Apache-2.0](LICENSE)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "blerpc-protocol"
|
|
7
|
+
version = "0.8.0"
|
|
8
|
+
description = "Container and command protocol layers for BLE RPC"
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"cryptography>=41.0",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
"Topic :: System :: Networking",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/tdaira/blerpc-protocol"
|
|
28
|
+
Repository = "https://github.com/tdaira/blerpc-protocol"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 88
|
|
35
|
+
target-version = "py311"
|
|
36
|
+
|
|
37
|
+
[tool.ruff.lint]
|
|
38
|
+
select = ["E", "F", "W", "I"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests/python"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""blerpc-protocol: Container and command protocol layers for BLE RPC."""
|
|
2
|
+
|
|
3
|
+
from blerpc_protocol.command import CommandPacket, CommandType
|
|
4
|
+
from blerpc_protocol.container import (
|
|
5
|
+
ATT_OVERHEAD,
|
|
6
|
+
CAPABILITY_FLAG_ENCRYPTION_SUPPORTED,
|
|
7
|
+
CONTROL_HEADER_SIZE,
|
|
8
|
+
FIRST_HEADER_SIZE,
|
|
9
|
+
SUBSEQUENT_HEADER_SIZE,
|
|
10
|
+
Container,
|
|
11
|
+
ContainerAssembler,
|
|
12
|
+
ContainerSplitter,
|
|
13
|
+
ContainerType,
|
|
14
|
+
ControlCmd,
|
|
15
|
+
make_capabilities_request,
|
|
16
|
+
make_capabilities_response,
|
|
17
|
+
make_error_response,
|
|
18
|
+
make_key_exchange,
|
|
19
|
+
make_stream_end_c2p,
|
|
20
|
+
make_stream_end_p2c,
|
|
21
|
+
make_timeout_request,
|
|
22
|
+
make_timeout_response,
|
|
23
|
+
)
|
|
24
|
+
from blerpc_protocol.crypto import (
|
|
25
|
+
BlerpcCrypto,
|
|
26
|
+
BlerpcCryptoSession,
|
|
27
|
+
CentralKeyExchange,
|
|
28
|
+
KnownKeyStore,
|
|
29
|
+
PeripheralKeyExchange,
|
|
30
|
+
central_perform_key_exchange,
|
|
31
|
+
tofu_verify,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"ATT_OVERHEAD",
|
|
36
|
+
"BlerpcCrypto",
|
|
37
|
+
"BlerpcCryptoSession",
|
|
38
|
+
"CAPABILITY_FLAG_ENCRYPTION_SUPPORTED",
|
|
39
|
+
"CentralKeyExchange",
|
|
40
|
+
"CONTROL_HEADER_SIZE",
|
|
41
|
+
"FIRST_HEADER_SIZE",
|
|
42
|
+
"SUBSEQUENT_HEADER_SIZE",
|
|
43
|
+
"CommandPacket",
|
|
44
|
+
"CommandType",
|
|
45
|
+
"Container",
|
|
46
|
+
"ContainerAssembler",
|
|
47
|
+
"ContainerSplitter",
|
|
48
|
+
"ContainerType",
|
|
49
|
+
"ControlCmd",
|
|
50
|
+
"make_capabilities_request",
|
|
51
|
+
"make_capabilities_response",
|
|
52
|
+
"make_error_response",
|
|
53
|
+
"make_key_exchange",
|
|
54
|
+
"make_stream_end_c2p",
|
|
55
|
+
"make_stream_end_p2c",
|
|
56
|
+
"make_timeout_request",
|
|
57
|
+
"make_timeout_response",
|
|
58
|
+
"PeripheralKeyExchange",
|
|
59
|
+
"KnownKeyStore",
|
|
60
|
+
"central_perform_key_exchange",
|
|
61
|
+
"tofu_verify",
|
|
62
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Command encode/decode layer for blerpc.
|
|
2
|
+
|
|
3
|
+
Command format (bits):
|
|
4
|
+
| type(1) | reserved(7) | cmd_name_len(8) | cmd_name(N*8) |
|
|
5
|
+
| data_len(16) | data(data_len*8) |
|
|
6
|
+
|
|
7
|
+
- type: 0=request, 1=response
|
|
8
|
+
- cmd_name: ASCII command name
|
|
9
|
+
- data_len: little-endian uint16
|
|
10
|
+
- data: protobuf-encoded bytes
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import struct
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from enum import IntEnum
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CommandType(IntEnum):
|
|
21
|
+
REQUEST = 0
|
|
22
|
+
RESPONSE = 1
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class CommandPacket:
|
|
27
|
+
"""A single command packet."""
|
|
28
|
+
|
|
29
|
+
cmd_type: CommandType
|
|
30
|
+
cmd_name: str
|
|
31
|
+
data: bytes = b""
|
|
32
|
+
|
|
33
|
+
def serialize(self) -> bytes:
|
|
34
|
+
"""Serialize command to bytes."""
|
|
35
|
+
name_bytes = self.cmd_name.encode("ascii")
|
|
36
|
+
if len(name_bytes) > 255:
|
|
37
|
+
raise ValueError(f"cmd_name too long: {len(name_bytes)} > 255")
|
|
38
|
+
if len(self.data) > 65535:
|
|
39
|
+
raise ValueError(f"data too long: {len(self.data)} > 65535")
|
|
40
|
+
|
|
41
|
+
# Byte 0: type in MSB (bit 7), reserved bits 6-0 = 0
|
|
42
|
+
byte0 = (self.cmd_type & 0x01) << 7
|
|
43
|
+
return (
|
|
44
|
+
bytes([byte0])
|
|
45
|
+
+ struct.pack("<B", len(name_bytes))
|
|
46
|
+
+ name_bytes
|
|
47
|
+
+ struct.pack("<H", len(self.data))
|
|
48
|
+
+ self.data
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def deserialize(data: bytes) -> CommandPacket:
|
|
53
|
+
"""Deserialize bytes into a CommandPacket."""
|
|
54
|
+
if len(data) < 2:
|
|
55
|
+
raise ValueError(f"Command packet too short: {len(data)} bytes")
|
|
56
|
+
|
|
57
|
+
# Byte 0: type in MSB
|
|
58
|
+
cmd_type = CommandType((data[0] >> 7) & 0x01)
|
|
59
|
+
cmd_name_len = data[1]
|
|
60
|
+
|
|
61
|
+
offset = 2
|
|
62
|
+
if len(data) < offset + cmd_name_len + 2:
|
|
63
|
+
raise ValueError("Command packet truncated")
|
|
64
|
+
|
|
65
|
+
cmd_name = data[offset : offset + cmd_name_len].decode("ascii")
|
|
66
|
+
offset += cmd_name_len
|
|
67
|
+
|
|
68
|
+
data_len = struct.unpack_from("<H", data, offset)[0]
|
|
69
|
+
offset += 2
|
|
70
|
+
|
|
71
|
+
payload = data[offset : offset + data_len]
|
|
72
|
+
return CommandPacket(
|
|
73
|
+
cmd_type=cmd_type,
|
|
74
|
+
cmd_name=cmd_name,
|
|
75
|
+
data=payload,
|
|
76
|
+
)
|