ensemble-client 0.1.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.
@@ -0,0 +1,11 @@
1
+ # Build artefacts from `python -m build` / `uv build`. These land in
2
+ # clients/python/dist/ which the repo-root .gitignore does NOT cover (it
3
+ # only ignores /ui/dist/ and /internal/ui-service/dist/*).
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .eggs/
8
+
9
+ # Local virtualenvs from `python -m venv` smoke tests.
10
+ .venv/
11
+ venv/
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: ensemble-client
3
+ Version: 0.1.0
4
+ Summary: Python client for the Ensemble peer-to-peer messaging daemon.
5
+ Project-URL: Homepage, https://github.com/boxsie/ensemble
6
+ Project-URL: Repository, https://github.com/boxsie/ensemble.git
7
+ Project-URL: Issues, https://github.com/boxsie/ensemble/issues
8
+ Author: boxsie
9
+ License-Expression: MIT
10
+ Keywords: ensemble,grpc,messaging,p2p,tor
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Communications :: Chat
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: cryptography>=41.0
23
+ Requires-Dist: grpcio>=1.62
24
+ Requires-Dist: protobuf>=5.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: build>=1.0; extra == 'dev'
27
+ Requires-Dist: grpcio-tools>=1.62; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest>=7.4; extra == 'dev'
30
+ Requires-Dist: twine>=4.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # ensemble-client (Python)
34
+
35
+ Python client library for the [Ensemble](https://github.com/boxsie/ensemble)
36
+ decentralized P2P messaging daemon. Wraps the `RegisterService` bidi gRPC
37
+ stream so an external Python process can register a service against a local
38
+ daemon and exchange chat messages with peers.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install ensemble-client
44
+ ```
45
+
46
+ Requires Python 3.11+. Depends on `grpcio`, `protobuf`, `cryptography`.
47
+
48
+ For local development against an unpublished checkout:
49
+
50
+ ```bash
51
+ git clone https://github.com/boxsie/ensemble.git
52
+ pip install -e ensemble/clients/python
53
+ ```
54
+
55
+ ## Quick start
56
+
57
+ Connect to a daemon and inspect the service handle (`address`, `onion`) the
58
+ moment the registration completes:
59
+
60
+ ```python
61
+ import asyncio
62
+ from ensemble import ACL, Client
63
+
64
+ async def main():
65
+ async with Client(
66
+ socket_path="/run/ensemble/sock",
67
+ auth_seed="/etc/ensemble/admin.seed",
68
+ ) as client:
69
+ async with await client.register("echo", acl=ACL.CONTACTS) as svc:
70
+ print(f"Registered: {svc.address} (onion={svc.onion})")
71
+
72
+ asyncio.run(main())
73
+ ```
74
+
75
+ ## Service registration quickstart
76
+
77
+ Loop the events iterator and echo every inbound chat back to the sender:
78
+
79
+ ```python
80
+ import asyncio
81
+ from ensemble import ACL, ChatMessage, Client
82
+
83
+ async def main():
84
+ async with Client(
85
+ socket_path="/run/ensemble/sock",
86
+ auth_seed="/etc/ensemble/admin.seed",
87
+ ) as client:
88
+ async with await client.register("echo", acl=ACL.CONTACTS) as svc:
89
+ print(f"Registered: {svc.address} {svc.onion}")
90
+ async for ev in svc.events():
91
+ if isinstance(ev, ChatMessage):
92
+ await svc.send_message(ev.from_addr, f"echo: {ev.text}")
93
+
94
+ asyncio.run(main())
95
+ ```
96
+
97
+ See [`examples/echo.py`](examples/echo.py) for a complete runnable example
98
+ with argparse + connection-request handling.
99
+
100
+ ## Connecting
101
+
102
+ Pass exactly one of `socket_path` or `addr` to `Client`:
103
+
104
+ - `socket_path="/run/ensemble/sock"` — Unix socket (the typical k8s sidecar setup).
105
+ - `addr="localhost:9090"` — TCP, optionally with `tls=True`.
106
+
107
+ The `auth_seed` argument may be either raw bytes or a path to a file
108
+ containing the seed (32 raw bytes, or 64 ASCII hex characters). It must match
109
+ the daemon's configured admin key (see `ensemble keygen` in the main repo).
110
+
111
+ ### TLS
112
+
113
+ Pass `tls=True` to use TLS. The client uses the system trust store;
114
+ self-signed daemon certs (e.g. behind a LAN CA) need either the CA installed
115
+ in the trust store or `GRPC_DEFAULT_SSL_ROOTS_FILE_PATH` pointing at it.
116
+ There is no clean equivalent to the Go CLI's `--tls-insecure` flag in
117
+ grpc-python; the parameter exists for API parity but does not currently
118
+ disable verification.
119
+
120
+ ## Events
121
+
122
+ `ServiceHandle.events()` yields decoded dataclasses, not raw protobuf:
123
+
124
+ - `ChatMessage(type, from_addr, text, ts)` — inbound chat.
125
+ - `ConnectionRequest(type, request_id, from_addr)` — inbound connection
126
+ awaiting accept/reject. Respond with
127
+ `svc.accept_connection(request_id)` or
128
+ `svc.reject_connection(request_id, reason)`.
129
+ - `UnknownEvent(type, payload)` — forward-compat fallback for event types
130
+ the client version doesn't recognise.
131
+
132
+ The daemon enforces backpressure with a 256-deep per-stream queue and drops
133
+ oldest events under sustained load (no on-wire signal). Consume events
134
+ promptly.
135
+
136
+ ## Public services (RPC transport + introductions)
137
+
138
+ For services that accept callers beyond the contact list, three primitives
139
+ work together (see [`examples/matchmaker_stub.py`](examples/matchmaker_stub.py)):
140
+
141
+ - `transport=Transport.RPC` on `client.register(...)` opts the service
142
+ into raw protobuf bytes both directions. Reply via
143
+ `svc.send_bytes(to_addr, payload)`; receive via either an
144
+ `svc.on_rpc_message(handler)` callback or `RpcMessage` items from
145
+ `svc.events()`.
146
+ - `svc.introduce_peers(to_addr, other_addr, session_id, expires_at_ms,
147
+ role_hint="", payload=b"")` asks the daemon to introduce two peers to
148
+ each other. The receiving peer gets a `PeerIntroduction` event with a
149
+ daemon-attested `from_service_addr` — provenance comes free; replay
150
+ protection (`session_id` + `expires_at`) is consumer-side.
151
+ - `max_payload_bytes` and `rate_limit_per_minute` / `rate_limit_burst`
152
+ on the manifest cap inbound abuse. Oversize / throttled inbound
153
+ envelopes surface as `PayloadTooLargeError` / `RateLimitedError` from
154
+ `events()` — branch on the typed exception, do NOT string-match
155
+ `message`.
156
+
157
+ ## Caveats
158
+
159
+ - `keypair_seed` on the manifest is currently advisory: the daemon's
160
+ keystore is append-only and ignores externally-supplied seeds. Pin to the
161
+ server-issued `address` from `ServiceRegistered` for stability across
162
+ restarts. (T07 limitation; tracked for follow-up.)
163
+ - Outbound chat (`send_message`) currently travels under the daemon's
164
+ primary node identity, not the registered service's identity. Inbound
165
+ chat correctly carries the service address. (Also T07.)
166
+ - Async-only API. There's no synchronous wrapper; use `asyncio.run` or
167
+ embed in your existing event loop.
168
+
169
+ ## Regenerating the gRPC stubs
170
+
171
+ The `ensemble/_proto/*.py` files are checked in. Regenerate via the
172
+ top-level `Makefile` (canonical entrypoint — regenerates Go and Python
173
+ stubs together so they stay in lock-step):
174
+
175
+ ```bash
176
+ make proto
177
+ ```
178
+
179
+ ## Versioning
180
+
181
+ `ensemble-client` is versioned independently from the daemon and from the
182
+ .NET client.
183
+
184
+ - Tag pattern for PyPI releases: `client-python/v<MAJOR>.<MINOR>.<PATCH>`
185
+ (e.g. `client-python/v0.1.0`). The publish workflow at
186
+ `.gitea/workflows/python-publish.yml` is wired to trigger only on tags
187
+ matching that prefix, so daemon (`v*.*.*`) and .NET
188
+ (`client-dotnet/v*.*.*`) tags never accidentally cut a Python release.
189
+ - Pre-1.0: minor bumps may include breaking changes. Pin the major+minor
190
+ (e.g. `ensemble-client~=0.1.0`) if you depend on this in production.
191
+ - Post-1.0: semver. Breaking changes bump the major.
192
+
193
+ ## Links
194
+
195
+ - Daemon + source: <https://github.com/boxsie/ensemble>
196
+ - Issues: <https://github.com/boxsie/ensemble/issues>
197
+ - PyPI: <https://pypi.org/project/ensemble-client/>
@@ -0,0 +1,165 @@
1
+ # ensemble-client (Python)
2
+
3
+ Python client library for the [Ensemble](https://github.com/boxsie/ensemble)
4
+ decentralized P2P messaging daemon. Wraps the `RegisterService` bidi gRPC
5
+ stream so an external Python process can register a service against a local
6
+ daemon and exchange chat messages with peers.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install ensemble-client
12
+ ```
13
+
14
+ Requires Python 3.11+. Depends on `grpcio`, `protobuf`, `cryptography`.
15
+
16
+ For local development against an unpublished checkout:
17
+
18
+ ```bash
19
+ git clone https://github.com/boxsie/ensemble.git
20
+ pip install -e ensemble/clients/python
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ Connect to a daemon and inspect the service handle (`address`, `onion`) the
26
+ moment the registration completes:
27
+
28
+ ```python
29
+ import asyncio
30
+ from ensemble import ACL, Client
31
+
32
+ async def main():
33
+ async with Client(
34
+ socket_path="/run/ensemble/sock",
35
+ auth_seed="/etc/ensemble/admin.seed",
36
+ ) as client:
37
+ async with await client.register("echo", acl=ACL.CONTACTS) as svc:
38
+ print(f"Registered: {svc.address} (onion={svc.onion})")
39
+
40
+ asyncio.run(main())
41
+ ```
42
+
43
+ ## Service registration quickstart
44
+
45
+ Loop the events iterator and echo every inbound chat back to the sender:
46
+
47
+ ```python
48
+ import asyncio
49
+ from ensemble import ACL, ChatMessage, Client
50
+
51
+ async def main():
52
+ async with Client(
53
+ socket_path="/run/ensemble/sock",
54
+ auth_seed="/etc/ensemble/admin.seed",
55
+ ) as client:
56
+ async with await client.register("echo", acl=ACL.CONTACTS) as svc:
57
+ print(f"Registered: {svc.address} {svc.onion}")
58
+ async for ev in svc.events():
59
+ if isinstance(ev, ChatMessage):
60
+ await svc.send_message(ev.from_addr, f"echo: {ev.text}")
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ See [`examples/echo.py`](examples/echo.py) for a complete runnable example
66
+ with argparse + connection-request handling.
67
+
68
+ ## Connecting
69
+
70
+ Pass exactly one of `socket_path` or `addr` to `Client`:
71
+
72
+ - `socket_path="/run/ensemble/sock"` — Unix socket (the typical k8s sidecar setup).
73
+ - `addr="localhost:9090"` — TCP, optionally with `tls=True`.
74
+
75
+ The `auth_seed` argument may be either raw bytes or a path to a file
76
+ containing the seed (32 raw bytes, or 64 ASCII hex characters). It must match
77
+ the daemon's configured admin key (see `ensemble keygen` in the main repo).
78
+
79
+ ### TLS
80
+
81
+ Pass `tls=True` to use TLS. The client uses the system trust store;
82
+ self-signed daemon certs (e.g. behind a LAN CA) need either the CA installed
83
+ in the trust store or `GRPC_DEFAULT_SSL_ROOTS_FILE_PATH` pointing at it.
84
+ There is no clean equivalent to the Go CLI's `--tls-insecure` flag in
85
+ grpc-python; the parameter exists for API parity but does not currently
86
+ disable verification.
87
+
88
+ ## Events
89
+
90
+ `ServiceHandle.events()` yields decoded dataclasses, not raw protobuf:
91
+
92
+ - `ChatMessage(type, from_addr, text, ts)` — inbound chat.
93
+ - `ConnectionRequest(type, request_id, from_addr)` — inbound connection
94
+ awaiting accept/reject. Respond with
95
+ `svc.accept_connection(request_id)` or
96
+ `svc.reject_connection(request_id, reason)`.
97
+ - `UnknownEvent(type, payload)` — forward-compat fallback for event types
98
+ the client version doesn't recognise.
99
+
100
+ The daemon enforces backpressure with a 256-deep per-stream queue and drops
101
+ oldest events under sustained load (no on-wire signal). Consume events
102
+ promptly.
103
+
104
+ ## Public services (RPC transport + introductions)
105
+
106
+ For services that accept callers beyond the contact list, three primitives
107
+ work together (see [`examples/matchmaker_stub.py`](examples/matchmaker_stub.py)):
108
+
109
+ - `transport=Transport.RPC` on `client.register(...)` opts the service
110
+ into raw protobuf bytes both directions. Reply via
111
+ `svc.send_bytes(to_addr, payload)`; receive via either an
112
+ `svc.on_rpc_message(handler)` callback or `RpcMessage` items from
113
+ `svc.events()`.
114
+ - `svc.introduce_peers(to_addr, other_addr, session_id, expires_at_ms,
115
+ role_hint="", payload=b"")` asks the daemon to introduce two peers to
116
+ each other. The receiving peer gets a `PeerIntroduction` event with a
117
+ daemon-attested `from_service_addr` — provenance comes free; replay
118
+ protection (`session_id` + `expires_at`) is consumer-side.
119
+ - `max_payload_bytes` and `rate_limit_per_minute` / `rate_limit_burst`
120
+ on the manifest cap inbound abuse. Oversize / throttled inbound
121
+ envelopes surface as `PayloadTooLargeError` / `RateLimitedError` from
122
+ `events()` — branch on the typed exception, do NOT string-match
123
+ `message`.
124
+
125
+ ## Caveats
126
+
127
+ - `keypair_seed` on the manifest is currently advisory: the daemon's
128
+ keystore is append-only and ignores externally-supplied seeds. Pin to the
129
+ server-issued `address` from `ServiceRegistered` for stability across
130
+ restarts. (T07 limitation; tracked for follow-up.)
131
+ - Outbound chat (`send_message`) currently travels under the daemon's
132
+ primary node identity, not the registered service's identity. Inbound
133
+ chat correctly carries the service address. (Also T07.)
134
+ - Async-only API. There's no synchronous wrapper; use `asyncio.run` or
135
+ embed in your existing event loop.
136
+
137
+ ## Regenerating the gRPC stubs
138
+
139
+ The `ensemble/_proto/*.py` files are checked in. Regenerate via the
140
+ top-level `Makefile` (canonical entrypoint — regenerates Go and Python
141
+ stubs together so they stay in lock-step):
142
+
143
+ ```bash
144
+ make proto
145
+ ```
146
+
147
+ ## Versioning
148
+
149
+ `ensemble-client` is versioned independently from the daemon and from the
150
+ .NET client.
151
+
152
+ - Tag pattern for PyPI releases: `client-python/v<MAJOR>.<MINOR>.<PATCH>`
153
+ (e.g. `client-python/v0.1.0`). The publish workflow at
154
+ `.gitea/workflows/python-publish.yml` is wired to trigger only on tags
155
+ matching that prefix, so daemon (`v*.*.*`) and .NET
156
+ (`client-dotnet/v*.*.*`) tags never accidentally cut a Python release.
157
+ - Pre-1.0: minor bumps may include breaking changes. Pin the major+minor
158
+ (e.g. `ensemble-client~=0.1.0`) if you depend on this in production.
159
+ - Post-1.0: semver. Breaking changes bump the major.
160
+
161
+ ## Links
162
+
163
+ - Daemon + source: <https://github.com/boxsie/ensemble>
164
+ - Issues: <https://github.com/boxsie/ensemble/issues>
165
+ - PyPI: <https://pypi.org/project/ensemble-client/>
@@ -0,0 +1,61 @@
1
+ """Ensemble Python client library.
2
+
3
+ Public API surface — import these directly from `ensemble`. The `_proto/`
4
+ subpackage contains generated gRPC stubs and is internal; consumers should
5
+ not import from it.
6
+ """
7
+
8
+ from .auth import pubkey_hex, signing_interceptors
9
+ from .client import (
10
+ ACL,
11
+ Client,
12
+ PeerIntroductionHandler,
13
+ RpcMessageHandler,
14
+ ServiceHandle,
15
+ Transport,
16
+ )
17
+ from .errors import (
18
+ AuthError,
19
+ ConnectionError,
20
+ EnsembleError,
21
+ PayloadTooLargeError,
22
+ RateLimitedError,
23
+ RegistrationError,
24
+ ServiceProtocolError,
25
+ TransportMismatchError,
26
+ )
27
+ from .events import (
28
+ ChatMessage,
29
+ ConnectionRequest,
30
+ PeerIntroduction,
31
+ RpcMessage,
32
+ ServiceEvent,
33
+ UnknownEvent,
34
+ )
35
+
36
+ __all__ = [
37
+ "ACL",
38
+ "AuthError",
39
+ "ChatMessage",
40
+ "Client",
41
+ "ConnectionError",
42
+ "ConnectionRequest",
43
+ "EnsembleError",
44
+ "PayloadTooLargeError",
45
+ "PeerIntroduction",
46
+ "PeerIntroductionHandler",
47
+ "RateLimitedError",
48
+ "RegistrationError",
49
+ "RpcMessage",
50
+ "RpcMessageHandler",
51
+ "ServiceEvent",
52
+ "ServiceHandle",
53
+ "ServiceProtocolError",
54
+ "Transport",
55
+ "TransportMismatchError",
56
+ "UnknownEvent",
57
+ "pubkey_hex",
58
+ "signing_interceptors",
59
+ ]
60
+
61
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,149 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: ensemble.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'ensemble.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x65nsemble.proto\x12\x0c\x65nsemble.api\"\x14\n\x12GetIdentityRequest\":\n\x13GetIdentityResponse\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x12\n\npublic_key\x18\x02 \x01(\x0c\"\x12\n\x10GetStatusRequest\"r\n\x11GetStatusResponse\x12\x11\n\ttor_state\x18\x01 \x01(\t\x12\x12\n\npeer_count\x18\x02 \x01(\x05\x12\x12\n\nonion_addr\x18\x03 \x01(\t\x12\x11\n\tuptime_ms\x18\x04 \x01(\x03\x12\x0f\n\x07rt_size\x18\x05 \x01(\x05\"\x15\n\x13ListContactsRequest\"C\n\x14ListContactsResponse\x12+\n\x08\x63ontacts\x18\x01 \x03(\x0b\x32\x19.ensemble.api.ContactInfo\"\x8d\x01\n\x0b\x43ontactInfo\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\x12\x15\n\ronion_address\x18\x04 \x01(\t\x12\x10\n\x08\x61\x64\x64\x65\x64_at\x18\x05 \x01(\x03\x12\x11\n\tlast_seen\x18\x06 \x01(\x03\x12\x0e\n\x06online\x18\x07 \x01(\x08\"G\n\x11\x41\x64\x64\x43ontactRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\"\x14\n\x12\x41\x64\x64\x43ontactResponse\"\'\n\x14RemoveContactRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\"\x17\n\x15RemoveContactResponse\"!\n\x0e\x43onnectRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\"4\n\x0f\x43onnectResponse\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"*\n\x17\x41\x63\x63\x65ptConnectionRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\"\x1a\n\x18\x41\x63\x63\x65ptConnectionResponse\"*\n\x17RejectConnectionRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\"\x1a\n\x18RejectConnectionResponse\"3\n\x12SendMessageRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\")\n\x13SendMessageResponse\x12\x12\n\nmessage_id\x18\x01 \x01(\t\"7\n\x15GetChatHistoryRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\"\x87\x01\n\x0b\x43hatMessage\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tfrom_addr\x18\x02 \x01(\t\x12\x0f\n\x07to_addr\x18\x03 \x01(\t\x12\x0c\n\x04text\x18\x04 \x01(\t\x12\x12\n\nsent_at_ms\x18\x05 \x01(\x03\x12\x13\n\x0b\x61\x63ked_at_ms\x18\x06 \x01(\x03\x12\x11\n\tdirection\x18\x07 \x01(\t\"E\n\x16GetChatHistoryResponse\x12+\n\x08messages\x18\x01 \x03(\x0b\x32\x19.ensemble.api.ChatMessage\"5\n\x0fSendFileRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tfile_path\x18\x02 \x01(\t\"\x94\x01\n\x10TransferProgress\x12\x13\n\x0btransfer_id\x18\x01 \x01(\t\x12\x10\n\x08\x66ilename\x18\x02 \x01(\t\x12\x13\n\x0btotal_bytes\x18\x03 \x01(\x04\x12\x12\n\nsent_bytes\x18\x04 \x01(\x04\x12\x0f\n\x07percent\x18\x05 \x01(\x02\x12\x10\n\x08\x63omplete\x18\x06 \x01(\x08\x12\r\n\x05\x65rror\x18\x07 \x01(\t\";\n\x11\x41\x63\x63\x65ptFileRequest\x12\x13\n\x0btransfer_id\x18\x01 \x01(\t\x12\x11\n\tsave_path\x18\x02 \x01(\t\"\x14\n\x12\x41\x63\x63\x65ptFileResponse\"(\n\x11RejectFileRequest\x12\x13\n\x0btransfer_id\x18\x01 \x01(\t\"\x14\n\x12RejectFileResponse\"\'\n\x0e\x41\x64\x64NodeRequest\x12\x15\n\ronion_address\x18\x01 \x01(\t\"&\n\x0f\x41\x64\x64NodeResponse\x12\x13\n\x0bpeers_found\x18\x01 \x01(\x05\"\x15\n\x13GetDebugInfoRequest\"\xc5\x01\n\x14GetDebugInfoResponse\x12\x0f\n\x07rt_size\x18\x01 \x01(\x05\x12\'\n\x08rt_peers\x18\x02 \x03(\x0b\x32\x15.ensemble.api.DHTNode\x12,\n\x0b\x63onnections\x18\x03 \x03(\x0b\x32\x17.ensemble.api.PeerState\x12\x12\n\nonion_addr\x18\x04 \x01(\t\x12\x31\n\x08services\x18\x05 \x03(\x0b\x32\x1f.ensemble.api.RegisteredService\"F\n\x11RegisteredService\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\x12\x12\n\nonion_addr\x18\x03 \x01(\t\"$\n\x14\x44\x65leteServiceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x17\n\x15\x44\x65leteServiceResponse\"A\n\x07\x44HTNode\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x12\n\nonion_addr\x18\x02 \x01(\t\x12\x11\n\tlast_seen\x18\x03 \x01(\x03\":\n\tPeerState\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"\x12\n\x10SubscribeRequest\",\n\x0b\x44\x61\x65monEvent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\t\"\xba\x02\n\x14ServiceClientMessage\x12\x31\n\x08manifest\x18\x01 \x01(\x0b\x32\x1d.ensemble.api.ServiceManifestH\x00\x12\x38\n\x0csend_message\x18\x02 \x01(\x0b\x32 .ensemble.api.ServiceSendMessageH\x00\x12\x37\n\x06\x61\x63\x63\x65pt\x18\x03 \x01(\x0b\x32%.ensemble.api.ServiceAcceptConnectionH\x00\x12\x37\n\x06reject\x18\x04 \x01(\x0b\x32%.ensemble.api.ServiceRejectConnectionH\x00\x12<\n\x0eintroduce_peer\x18\x05 \x01(\x0b\x32\".ensemble.api.ServiceIntroducePeerH\x00\x42\x05\n\x03msg\"\xae\x01\n\x14ServiceServerMessage\x12\x35\n\nregistered\x18\x01 \x01(\x0b\x32\x1f.ensemble.api.ServiceRegisteredH\x00\x12+\n\x05\x65vent\x18\x02 \x01(\x0b\x32\x1a.ensemble.api.ServiceEventH\x00\x12+\n\x05\x65rror\x18\x03 \x01(\x0b\x32\x1a.ensemble.api.ServiceErrorH\x00\x42\x05\n\x03msg\"\xfe\x01\n\x0fServiceManifest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x1e\n\x03\x61\x63l\x18\x03 \x01(\x0e\x32\x11.ensemble.api.ACL\x12\x11\n\tallowlist\x18\x04 \x03(\t\x12\x14\n\x0ckeypair_seed\x18\x05 \x01(\x0c\x12\x31\n\ttransport\x18\x06 \x01(\x0e\x32\x1e.ensemble.api.ServiceTransport\x12\x19\n\x11max_payload_bytes\x18\x07 \x01(\x03\x12\x31\n\nrate_limit\x18\x08 \x01(\x0b\x32\x1d.ensemble.api.RateLimitPolicy\"=\n\x0fRateLimitPolicy\x12\x1b\n\x13requests_per_minute\x18\x01 \x01(\x05\x12\r\n\x05\x62urst\x18\x02 \x01(\x05\"f\n\x11ServiceRegistered\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\r\n\x05onion\x18\x02 \x01(\t\x12\x31\n\ttransport\x18\x03 \x01(\x0e\x32\x1e.ensemble.api.ServiceTransport\"-\n\x0cServiceEvent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"F\n\x17ServiceChatMessageEvent\x12\x11\n\tfrom_addr\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\n\n\x02ts\x18\x03 \x01(\x03\"F\n\x1dServiceConnectionRequestEvent\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x11\n\tfrom_addr\x18\x02 \x01(\t\"H\n\x16ServiceRpcMessageEvent\x12\x11\n\tfrom_addr\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12\n\n\x02ts\x18\x03 \x01(\x03\"D\n\x12ServiceSendMessage\x12\x0f\n\x07to_addr\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x0f\n\x07payload\x18\x03 \x01(\x0c\"-\n\x17ServiceAcceptConnection\x12\x12\n\nrequest_id\x18\x01 \x01(\t\"=\n\x17ServiceRejectConnection\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\"\x87\x01\n\x14ServiceIntroducePeer\x12\x0f\n\x07to_addr\x18\x01 \x01(\t\x12\x12\n\nother_addr\x18\x02 \x01(\t\x12\x12\n\nsession_id\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\x12\x11\n\trole_hint\x18\x05 \x01(\t\x12\x0f\n\x07payload\x18\x06 \x01(\x0c\"\x98\x01\n\x1cServicePeerIntroductionEvent\x12\x19\n\x11\x66rom_service_addr\x18\x01 \x01(\t\x12\x11\n\tpeer_addr\x18\x02 \x01(\t\x12\x12\n\nsession_id\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\x12\x11\n\trole_hint\x18\x05 \x01(\t\x12\x0f\n\x07payload\x18\x06 \x01(\x0c\"Z\n\x0cServiceError\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x13\n\x0blimit_bytes\x18\x02 \x01(\x03\x12\x0c\n\x04\x63ode\x18\x03 \x01(\t\x12\x16\n\x0eretry_after_ms\x18\x04 \x01(\x03*:\n\x03\x41\x43L\x12\x0e\n\nACL_PUBLIC\x10\x00\x12\x10\n\x0c\x41\x43L_CONTACTS\x10\x01\x12\x11\n\rACL_ALLOWLIST\x10\x02*\x88\x01\n\x10ServiceTransport\x12!\n\x1dSERVICE_TRANSPORT_UNSPECIFIED\x10\x00\x12\x1a\n\x16SERVICE_TRANSPORT_CHAT\x10\x01\x12\x1a\n\x16SERVICE_TRANSPORT_HTTP\x10\x02\x12\x19\n\x15SERVICE_TRANSPORT_RPC\x10\x03\x32\x85\x0c\n\x0f\x45nsembleService\x12R\n\x0bGetIdentity\x12 .ensemble.api.GetIdentityRequest\x1a!.ensemble.api.GetIdentityResponse\x12L\n\tGetStatus\x12\x1e.ensemble.api.GetStatusRequest\x1a\x1f.ensemble.api.GetStatusResponse\x12U\n\x0cListContacts\x12!.ensemble.api.ListContactsRequest\x1a\".ensemble.api.ListContactsResponse\x12O\n\nAddContact\x12\x1f.ensemble.api.AddContactRequest\x1a .ensemble.api.AddContactResponse\x12X\n\rRemoveContact\x12\".ensemble.api.RemoveContactRequest\x1a#.ensemble.api.RemoveContactResponse\x12\x46\n\x07\x43onnect\x12\x1c.ensemble.api.ConnectRequest\x1a\x1d.ensemble.api.ConnectResponse\x12\x61\n\x10\x41\x63\x63\x65ptConnection\x12%.ensemble.api.AcceptConnectionRequest\x1a&.ensemble.api.AcceptConnectionResponse\x12\x61\n\x10RejectConnection\x12%.ensemble.api.RejectConnectionRequest\x1a&.ensemble.api.RejectConnectionResponse\x12R\n\x0bSendMessage\x12 .ensemble.api.SendMessageRequest\x1a!.ensemble.api.SendMessageResponse\x12[\n\x0eGetChatHistory\x12#.ensemble.api.GetChatHistoryRequest\x1a$.ensemble.api.GetChatHistoryResponse\x12K\n\x08SendFile\x12\x1d.ensemble.api.SendFileRequest\x1a\x1e.ensemble.api.TransferProgress0\x01\x12O\n\nAcceptFile\x12\x1f.ensemble.api.AcceptFileRequest\x1a .ensemble.api.AcceptFileResponse\x12O\n\nRejectFile\x12\x1f.ensemble.api.RejectFileRequest\x1a .ensemble.api.RejectFileResponse\x12\x46\n\x07\x41\x64\x64Node\x12\x1c.ensemble.api.AddNodeRequest\x1a\x1d.ensemble.api.AddNodeResponse\x12U\n\x0cGetDebugInfo\x12!.ensemble.api.GetDebugInfoRequest\x1a\".ensemble.api.GetDebugInfoResponse\x12H\n\tSubscribe\x12\x1e.ensemble.api.SubscribeRequest\x1a\x19.ensemble.api.DaemonEvent0\x01\x12]\n\x0fRegisterService\x12\".ensemble.api.ServiceClientMessage\x1a\".ensemble.api.ServiceServerMessage(\x01\x30\x01\x12X\n\rDeleteService\x12\".ensemble.api.DeleteServiceRequest\x1a#.ensemble.api.DeleteServiceResponseB#Z!github.com/boxsie/ensemble/api/pbb\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ensemble_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'Z!github.com/boxsie/ensemble/api/pb'
35
+ _globals['_ACL']._serialized_start=3975
36
+ _globals['_ACL']._serialized_end=4033
37
+ _globals['_SERVICETRANSPORT']._serialized_start=4036
38
+ _globals['_SERVICETRANSPORT']._serialized_end=4172
39
+ _globals['_GETIDENTITYREQUEST']._serialized_start=32
40
+ _globals['_GETIDENTITYREQUEST']._serialized_end=52
41
+ _globals['_GETIDENTITYRESPONSE']._serialized_start=54
42
+ _globals['_GETIDENTITYRESPONSE']._serialized_end=112
43
+ _globals['_GETSTATUSREQUEST']._serialized_start=114
44
+ _globals['_GETSTATUSREQUEST']._serialized_end=132
45
+ _globals['_GETSTATUSRESPONSE']._serialized_start=134
46
+ _globals['_GETSTATUSRESPONSE']._serialized_end=248
47
+ _globals['_LISTCONTACTSREQUEST']._serialized_start=250
48
+ _globals['_LISTCONTACTSREQUEST']._serialized_end=271
49
+ _globals['_LISTCONTACTSRESPONSE']._serialized_start=273
50
+ _globals['_LISTCONTACTSRESPONSE']._serialized_end=340
51
+ _globals['_CONTACTINFO']._serialized_start=343
52
+ _globals['_CONTACTINFO']._serialized_end=484
53
+ _globals['_ADDCONTACTREQUEST']._serialized_start=486
54
+ _globals['_ADDCONTACTREQUEST']._serialized_end=557
55
+ _globals['_ADDCONTACTRESPONSE']._serialized_start=559
56
+ _globals['_ADDCONTACTRESPONSE']._serialized_end=579
57
+ _globals['_REMOVECONTACTREQUEST']._serialized_start=581
58
+ _globals['_REMOVECONTACTREQUEST']._serialized_end=620
59
+ _globals['_REMOVECONTACTRESPONSE']._serialized_start=622
60
+ _globals['_REMOVECONTACTRESPONSE']._serialized_end=645
61
+ _globals['_CONNECTREQUEST']._serialized_start=647
62
+ _globals['_CONNECTREQUEST']._serialized_end=680
63
+ _globals['_CONNECTRESPONSE']._serialized_start=682
64
+ _globals['_CONNECTRESPONSE']._serialized_end=734
65
+ _globals['_ACCEPTCONNECTIONREQUEST']._serialized_start=736
66
+ _globals['_ACCEPTCONNECTIONREQUEST']._serialized_end=778
67
+ _globals['_ACCEPTCONNECTIONRESPONSE']._serialized_start=780
68
+ _globals['_ACCEPTCONNECTIONRESPONSE']._serialized_end=806
69
+ _globals['_REJECTCONNECTIONREQUEST']._serialized_start=808
70
+ _globals['_REJECTCONNECTIONREQUEST']._serialized_end=850
71
+ _globals['_REJECTCONNECTIONRESPONSE']._serialized_start=852
72
+ _globals['_REJECTCONNECTIONRESPONSE']._serialized_end=878
73
+ _globals['_SENDMESSAGEREQUEST']._serialized_start=880
74
+ _globals['_SENDMESSAGEREQUEST']._serialized_end=931
75
+ _globals['_SENDMESSAGERESPONSE']._serialized_start=933
76
+ _globals['_SENDMESSAGERESPONSE']._serialized_end=974
77
+ _globals['_GETCHATHISTORYREQUEST']._serialized_start=976
78
+ _globals['_GETCHATHISTORYREQUEST']._serialized_end=1031
79
+ _globals['_CHATMESSAGE']._serialized_start=1034
80
+ _globals['_CHATMESSAGE']._serialized_end=1169
81
+ _globals['_GETCHATHISTORYRESPONSE']._serialized_start=1171
82
+ _globals['_GETCHATHISTORYRESPONSE']._serialized_end=1240
83
+ _globals['_SENDFILEREQUEST']._serialized_start=1242
84
+ _globals['_SENDFILEREQUEST']._serialized_end=1295
85
+ _globals['_TRANSFERPROGRESS']._serialized_start=1298
86
+ _globals['_TRANSFERPROGRESS']._serialized_end=1446
87
+ _globals['_ACCEPTFILEREQUEST']._serialized_start=1448
88
+ _globals['_ACCEPTFILEREQUEST']._serialized_end=1507
89
+ _globals['_ACCEPTFILERESPONSE']._serialized_start=1509
90
+ _globals['_ACCEPTFILERESPONSE']._serialized_end=1529
91
+ _globals['_REJECTFILEREQUEST']._serialized_start=1531
92
+ _globals['_REJECTFILEREQUEST']._serialized_end=1571
93
+ _globals['_REJECTFILERESPONSE']._serialized_start=1573
94
+ _globals['_REJECTFILERESPONSE']._serialized_end=1593
95
+ _globals['_ADDNODEREQUEST']._serialized_start=1595
96
+ _globals['_ADDNODEREQUEST']._serialized_end=1634
97
+ _globals['_ADDNODERESPONSE']._serialized_start=1636
98
+ _globals['_ADDNODERESPONSE']._serialized_end=1674
99
+ _globals['_GETDEBUGINFOREQUEST']._serialized_start=1676
100
+ _globals['_GETDEBUGINFOREQUEST']._serialized_end=1697
101
+ _globals['_GETDEBUGINFORESPONSE']._serialized_start=1700
102
+ _globals['_GETDEBUGINFORESPONSE']._serialized_end=1897
103
+ _globals['_REGISTEREDSERVICE']._serialized_start=1899
104
+ _globals['_REGISTEREDSERVICE']._serialized_end=1969
105
+ _globals['_DELETESERVICEREQUEST']._serialized_start=1971
106
+ _globals['_DELETESERVICEREQUEST']._serialized_end=2007
107
+ _globals['_DELETESERVICERESPONSE']._serialized_start=2009
108
+ _globals['_DELETESERVICERESPONSE']._serialized_end=2032
109
+ _globals['_DHTNODE']._serialized_start=2034
110
+ _globals['_DHTNODE']._serialized_end=2099
111
+ _globals['_PEERSTATE']._serialized_start=2101
112
+ _globals['_PEERSTATE']._serialized_end=2159
113
+ _globals['_SUBSCRIBEREQUEST']._serialized_start=2161
114
+ _globals['_SUBSCRIBEREQUEST']._serialized_end=2179
115
+ _globals['_DAEMONEVENT']._serialized_start=2181
116
+ _globals['_DAEMONEVENT']._serialized_end=2225
117
+ _globals['_SERVICECLIENTMESSAGE']._serialized_start=2228
118
+ _globals['_SERVICECLIENTMESSAGE']._serialized_end=2542
119
+ _globals['_SERVICESERVERMESSAGE']._serialized_start=2545
120
+ _globals['_SERVICESERVERMESSAGE']._serialized_end=2719
121
+ _globals['_SERVICEMANIFEST']._serialized_start=2722
122
+ _globals['_SERVICEMANIFEST']._serialized_end=2976
123
+ _globals['_RATELIMITPOLICY']._serialized_start=2978
124
+ _globals['_RATELIMITPOLICY']._serialized_end=3039
125
+ _globals['_SERVICEREGISTERED']._serialized_start=3041
126
+ _globals['_SERVICEREGISTERED']._serialized_end=3143
127
+ _globals['_SERVICEEVENT']._serialized_start=3145
128
+ _globals['_SERVICEEVENT']._serialized_end=3190
129
+ _globals['_SERVICECHATMESSAGEEVENT']._serialized_start=3192
130
+ _globals['_SERVICECHATMESSAGEEVENT']._serialized_end=3262
131
+ _globals['_SERVICECONNECTIONREQUESTEVENT']._serialized_start=3264
132
+ _globals['_SERVICECONNECTIONREQUESTEVENT']._serialized_end=3334
133
+ _globals['_SERVICERPCMESSAGEEVENT']._serialized_start=3336
134
+ _globals['_SERVICERPCMESSAGEEVENT']._serialized_end=3408
135
+ _globals['_SERVICESENDMESSAGE']._serialized_start=3410
136
+ _globals['_SERVICESENDMESSAGE']._serialized_end=3478
137
+ _globals['_SERVICEACCEPTCONNECTION']._serialized_start=3480
138
+ _globals['_SERVICEACCEPTCONNECTION']._serialized_end=3525
139
+ _globals['_SERVICEREJECTCONNECTION']._serialized_start=3527
140
+ _globals['_SERVICEREJECTCONNECTION']._serialized_end=3588
141
+ _globals['_SERVICEINTRODUCEPEER']._serialized_start=3591
142
+ _globals['_SERVICEINTRODUCEPEER']._serialized_end=3726
143
+ _globals['_SERVICEPEERINTRODUCTIONEVENT']._serialized_start=3729
144
+ _globals['_SERVICEPEERINTRODUCTIONEVENT']._serialized_end=3881
145
+ _globals['_SERVICEERROR']._serialized_start=3883
146
+ _globals['_SERVICEERROR']._serialized_end=3973
147
+ _globals['_ENSEMBLESERVICE']._serialized_start=4175
148
+ _globals['_ENSEMBLESERVICE']._serialized_end=5716
149
+ # @@protoc_insertion_point(module_scope)