Skip to content

Commit 3271e54

Browse files
committed
msg_type/intercom
1 parent 6285742 commit 3271e54

File tree

3 files changed

+118
-67
lines changed

3 files changed

+118
-67
lines changed

hivemind_bus_client/client.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -446,11 +446,9 @@ def wait_for_payload_response(self, message: Union[MycroftMessage, HiveMessage],
446446
return waiter.wait(timeout)
447447

448448
# targeted messages for nodes, assymetric encryption
449-
def emit_encrypted(self, message: MycroftMessage, pubkey: Union[str, pgpy.PGPKey]):
450-
message = self.encrypt(message, pubkey)
451-
self.emit(message)
449+
def emit_intercom(self, message: Union[MycroftMessage, HiveMessage],
450+
pubkey: Union[str, pgpy.PGPKey]):
452451

453-
def encrypt(self, message: MycroftMessage, pubkey: Union[str, pgpy.PGPKey]):
454452
if isinstance(pubkey, str):
455453
pubkey, _ = pgpy.PGPKey.from_blob(pubkey)
456454
assert isinstance(pubkey, pgpy.PGPKey)
@@ -467,16 +465,4 @@ def encrypt(self, message: MycroftMessage, pubkey: Union[str, pgpy.PGPKey]):
467465
encrypted_message |= private_key.sign(encrypted_message,
468466
intended_recipients=[pubkey])
469467

470-
return MycroftMessage("hive.identity_encrypted",
471-
{"ciphertext": str(encrypted_message)})
472-
473-
def decrypt(self, message: MycroftMessage):
474-
assert message.msg_type == "hive.identity_encrypted"
475-
ciphertext = message.data["ciphertext"]
476-
message_from_blob = pgpy.PGPMessage.from_blob(ciphertext)
477-
478-
with open(self.identity.private_key, "r") as f:
479-
private_key = pgpy.PGPKey.from_blob(f.read())
480-
481-
decrypted: str = private_key.decrypt(message_from_blob)
482-
return MycroftMessage.deserialize(json.loads(decrypted))
468+
self.emit(HiveMessage(HiveMessageType.INTERCOM, payload={"ciphertext": str(encrypted_message)}))

hivemind_bus_client/message.py

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,58 @@
1-
from enum import Enum
21
import json
3-
from ovos_utils.json_helper import merge_dict
2+
from enum import Enum
3+
44
from ovos_bus_client import Message
5+
from ovos_utils.json_helper import merge_dict
6+
from typing import Union, List, Optional
57

68

79
class HiveMessageType(str, Enum):
810
HANDSHAKE = "shake" # negotiate initial connection
911
BUS = "bus" # request meant for internal mycroft-bus in master
1012
SHARED_BUS = "shared_bus" # passive sharing of message
11-
# from mycroft-bus in slave
13+
# from mycroft-bus in slave
14+
15+
INTERCOM = "intercom" # from satellite to satellite
16+
1217
BROADCAST = "broadcast" # forward message to all slaves
1318
PROPAGATE = "propagate" # forward message to all slaves and masters
1419
ESCALATE = "escalate" # forward message up the authority chain to all
15-
# masters
20+
# masters
1621
HELLO = "hello" # like escalate, used to announce the device
1722
QUERY = "query" # like escalate, but stops once one of the nodes can
18-
# send a response
23+
# send a response
1924
CASCADE = "cascade" # like propagate, but expects a response back from
20-
# all nodes in the hive (responses optional)
25+
# all nodes in the hive (responses optional)
2126
PING = "ping" # like cascade, but used to map the network
2227
RENDEZVOUS = "rendezvous" # reserved for rendezvous-nodes
2328
THIRDPRTY = "3rdparty" # user land message, do whatever you want
2429
BINARY = "bin" # binary data container, payload for something else
2530

2631

2732
class HiveMessage:
28-
def __init__(self, msg_type, payload=None, node=None, source_peer=None,
29-
route=None, target_peers=None, meta=None, target_site_id=None):
33+
def __init__(self, msg_type: Union[HiveMessageType, str],
34+
payload: Optional[Union[Message, 'HiveMessage', str, dict]] =None,
35+
node: Optional[str]=None,
36+
source_peer: Optional[str]=None,
37+
route: Optional[List[str]]=None,
38+
target_peers: Optional[List[str]]=None,
39+
target_site_id: Optional[str] =None,
40+
target_pubkey: Optional[str] =None):
3041
# except for the hivemind node classes receiving the message and
3142
# creating the object nothing should be able to change these values
3243
# node classes might change them a runtime by the private attribute
3344
# but end-users should consider them read_only
45+
46+
3447
if msg_type not in [m.value for m in HiveMessageType]:
3548
raise ValueError("Unknown HiveMessage.msg_type")
36-
3749
self._msg_type = msg_type
50+
3851
# the payload is more or less a free for all
3952
# the msg_type determines what happens to the message, but the
4053
# payload can simply be ignored by the receiving module
41-
42-
# some msg_types might return HiveMessage, others (mycroft) Message
43-
# we should support the dict/json format, json is used at the
44-
# transport layer before converting into any of these formats
54+
# we store things in dict/json format, json is always used at the
55+
# transport layer before converting into any of the other formats
4556
if isinstance(payload, Message):
4657
payload = {"type": payload.msg_type,
4758
"data": payload.data,
@@ -51,40 +62,44 @@ def __init__(self, msg_type, payload=None, node=None, source_peer=None,
5162
self._payload = payload or {}
5263

5364
self._site_id = target_site_id
65+
self._target_pubkey = target_pubkey
5466
self._node = node # node semi-unique identifier
5567
self._source_peer = source_peer # peer_id
5668
self._route = route or [] # where did this message come from
5769
self._targets = target_peers or [] # where will it be sent
58-
self._meta = meta or {}
5970

6071
@property
61-
def target_site_id(self):
72+
def target_site_id(self) -> str:
6273
return self._site_id
6374

6475
@property
65-
def msg_type(self):
76+
def target_public_key(self) -> str:
77+
return self._target_pubkey
78+
79+
@property
80+
def msg_type(self) -> str:
6681
return self._msg_type
6782

6883
@property
69-
def node_id(self):
84+
def node_id(self) -> str:
7085
return self._node
7186

7287
@property
73-
def source_peer(self):
88+
def source_peer(self) -> str:
7489
return self._source_peer
7590

7691
@property
77-
def target_peers(self):
92+
def target_peers(self) -> List[str]:
7893
if self.source_peer:
7994
return self._targets or [self._source_peer]
8095
return self._targets
8196

8297
@property
83-
def route(self):
98+
def route(self) -> List[str]:
8499
return [r for r in self._route if r.get("targets") and r.get("source")]
85100

86101
@property
87-
def payload(self):
102+
def payload(self) -> Union['HiveMessage', Message, dict]:
88103
if self.msg_type in [HiveMessageType.BUS, HiveMessageType.SHARED_BUS]:
89104
return Message(self._payload["type"],
90105
data=self._payload.get("data"),
@@ -97,37 +112,42 @@ def payload(self):
97112
return self._payload
98113

99114
@property
100-
def as_dict(self):
115+
def as_dict(self) -> dict:
101116
pload = self._payload
102117
if isinstance(pload, HiveMessage):
103-
pload = pload.as_json
118+
pload = pload.as_dict
104119
elif isinstance(pload, Message):
105120
pload = pload.serialize()
106121
if isinstance(pload, str):
107122
pload = json.loads(pload)
123+
124+
assert isinstance(pload, dict)
125+
108126
return {"msg_type": self.msg_type,
109127
"payload": pload,
110128
"route": self.route,
111129
"node": self.node_id,
112130
"target_site_id": self.target_site_id,
131+
"target_pubkey": self.target_public_key,
113132
"source_peer": self.source_peer}
114133

115134
@property
116-
def as_json(self):
135+
def as_json(self) -> str:
117136
return json.dumps(self.as_dict)
118137

119-
def serialize(self):
138+
def serialize(self) -> str:
120139
return self.as_json
121140

122141
@staticmethod
123-
def deserialize(payload):
142+
def deserialize(payload: Union[str, dict]) -> 'HiveMessage':
124143
if isinstance(payload, str):
125144
payload = json.loads(payload)
126145

127146
if "msg_type" in payload:
128147
try:
129148
return HiveMessage(payload["msg_type"], payload["payload"],
130-
target_site_id=payload.get("target_site_id"))
149+
target_site_id=payload.get("target_site_id"),
150+
target_pubkey=payload.get("target_pubkey"))
131151
except:
132152
pass # not a hivemind message
133153

@@ -136,12 +156,14 @@ def deserialize(payload):
136156
# NOTE: technically could also be SHARED_BUS or THIRDPRTY
137157
return HiveMessage(HiveMessageType.BUS,
138158
Message.deserialize(payload),
139-
target_site_id=payload.get("target_site_id"))
159+
target_site_id=payload.get("target_site_id"),
160+
target_pubkey=payload.get("target_pubkey"))
140161
except:
141162
pass # not a mycroft message
142163

143164
return HiveMessage(HiveMessageType.THIRDPRTY, payload,
144-
target_site_id=payload.get("target_site_id"))
165+
target_site_id=payload.get("target_site_id"),
166+
target_pubkey=payload.get("target_pubkey"))
145167

146168
def __getitem__(self, item):
147169
return self._payload.get(item)

hivemind_bus_client/protocol.py

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from dataclasses import dataclass
2-
from typing import Optional
32

43
from ovos_bus_client import Message as MycroftMessage
54
from ovos_bus_client import MessageBusClient
65
from ovos_bus_client.message import Message
76
from ovos_bus_client.session import Session, SessionManager
87
from ovos_utils.log import LOG
9-
from hivemind_bus_client.identity import NodeIdentity
8+
from poorman_handshake import HandShake, PasswordHandShake
9+
from typing import Optional
10+
import pgpy
1011
from hivemind_bus_client.client import HiveMessageBusClient
12+
from hivemind_bus_client.identity import NodeIdentity
1113
from hivemind_bus_client.message import HiveMessage, HiveMessageType
12-
from poorman_handshake import HandShake, PasswordHandShake
1314

1415

1516
@dataclass()
@@ -104,6 +105,7 @@ def bind(self, bus: Optional[MessageBusClient] = None):
104105
self.hm.on(HiveMessageType.HELLO, self.handle_hello)
105106
self.hm.on(HiveMessageType.BROADCAST, self.handle_broadcast)
106107
self.hm.on(HiveMessageType.PROPAGATE, self.handle_propagate)
108+
self.hm.on(HiveMessageType.INTERCOM, self.handle_intercom)
107109
self.hm.on(HiveMessageType.ESCALATE, self.handle_illegal_msg)
108110
self.hm.on(HiveMessageType.SHARED_BUS, self.handle_illegal_msg)
109111
self.hm.on(HiveMessageType.BUS, self.handle_bus)
@@ -136,7 +138,7 @@ def handle_hello(self, message: HiveMessage):
136138
self.internal_protocol.bus.session_id = message.payload["session_id"]
137139
LOG.debug("session_id updated to: " + message.payload["session_id"])
138140

139-
def start_handshake(self,):
141+
def start_handshake(self):
140142
if self.binarize:
141143
LOG.info("hivemind supports binarization protocol")
142144
else:
@@ -216,14 +218,15 @@ def handle_bus(self, message: HiveMessage):
216218
def handle_broadcast(self, message: HiveMessage):
217219
LOG.info(f"BROADCAST: {message.payload}")
218220

219-
# if the message targets our site_id, send it to internal bus
220-
site = message.target_site_id
221-
if site and site == self.site_id:
222-
pload = message.payload
223-
# broadcast messages always come from a trusted source
224-
# only masters can emit them
225-
if isinstance(pload, MycroftMessage):
226-
self.handle_bus(message)
221+
if message.payload.msg_type == HiveMessageType.INTERCOM:
222+
self.handle_intercom(message)
223+
return
224+
225+
if message.payload.msg_type == HiveMessageType.BUS:
226+
# if the message targets our site_id, send it to internal bus
227+
site = message.target_site_id
228+
if site and site == self.site_id:
229+
self.handle_bus(message.payload)
227230

228231
# if this device is also a hivemind server
229232
# forward to HiveMindListenerInternalProtocol
@@ -234,18 +237,58 @@ def handle_broadcast(self, message: HiveMessage):
234237
def handle_propagate(self, message: HiveMessage):
235238
LOG.info(f"PROPAGATE: {message.payload}")
236239

237-
# if the message targets our site_id, send it to internal bus
238-
site = message.target_site_id
239-
if site and site == self.site_id:
240-
# might originate from untrusted
241-
# satellite anywhere in the hive
242-
# do not inject by default
243-
pload = message.payload
244-
#if isinstance(pload, MycroftMessage):
245-
# self.handle_bus(message)
240+
if message.payload.msg_type == HiveMessageType.INTERCOM:
241+
self.handle_intercom(message)
242+
return
243+
244+
if message.payload.msg_type == HiveMessageType.BUS:
245+
# if the message targets our site_id, send it to internal bus
246+
site = message.target_site_id
247+
if site and site == self.site_id:
248+
# might originate from untrusted
249+
# satellite anywhere in the hive
250+
# do not inject by default
251+
pass # TODO - when to inject ? add list of trusted peers?
252+
# self.handle_bus(message.payload)
253+
246254

247255
# if this device is also a hivemind server
248256
# forward to HiveMindListenerInternalProtocol
249257
data = message.serialize()
250258
ctxt = {"source": self.node_id}
251259
self.internal_protocol.bus.emit(MycroftMessage('hive.send.downstream', data, ctxt))
260+
261+
262+
def handle_intercom(self, message: HiveMessage):
263+
LOG.info(f"INTERCOM: {message.payload}")
264+
265+
# if the message targets our site_id, send it to internal bus
266+
k = message.target_public_key
267+
if k and k != self.hm.identity.public_key:
268+
# not for us
269+
return
270+
271+
pload = message.payload
272+
if isinstance(pload, dict) and "ciphertext" in pload:
273+
try:
274+
message_from_blob = pgpy.PGPMessage.from_blob(pload["ciphertext"])
275+
276+
with open(self.identity.private_key, "r") as f:
277+
private_key = pgpy.PGPKey.from_blob(f.read())
278+
279+
decrypted: str = private_key.decrypt(message_from_blob)
280+
message._payload = HiveMessage.deserialize(decrypted)
281+
except:
282+
if k:
283+
LOG.error("failed to decrypt message!")
284+
raise
285+
LOG.debug("failed to decrypt message, not for us")
286+
return
287+
288+
if message.msg_type == HiveMessageType.BUS:
289+
self.handle_bus(message)
290+
elif message.msg_type == HiveMessageType.PROPAGATE:
291+
self.handle_propagate(message)
292+
elif message.msg_type == HiveMessageType.BROADCAST:
293+
self.handle_broadcast(message)
294+

0 commit comments

Comments
 (0)