Skip to content

Commit 3f1a071

Browse files
committed
Change capabilities sedes according to #6503
This changes the (de-)serialization of capabilities that are broadcast via synapse `avatar_url` to be `base64` wrapped `json`. Changes in detail: - hand rolled serialization is removed - truncation of falsy entries is dropped / all entries are explicit - unknown capabilities will be preserved - `parse_capabilities` is renamed to `deserialize_capabilities` - deserialization errors are treated as empty capabilities - adjusted unit tests
1 parent de447f0 commit 3f1a071

File tree

4 files changed

+38
-43
lines changed

4 files changed

+38
-43
lines changed

raiden/network/transport/matrix/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
)
5757
from raiden.network.utils import get_average_http_response_time
5858
from raiden.storage.serialization.serializer import MessageSerializer
59-
from raiden.utils.capabilities import parse_capabilities, serialize_capabilities
59+
from raiden.utils.capabilities import deserialize_capabilities, serialize_capabilities
6060
from raiden.utils.gevent import spawn_named
6161
from raiden.utils.signer import Signer, recover
6262
from raiden.utils.typing import Address, ChainID, MessageID, PeerCapabilities, Signature
@@ -346,7 +346,7 @@ def query_capabilities_for_user_id(self, user_id: str) -> PeerCapabilities:
346346
return PeerCapabilities({})
347347
avatar_url = user.get_avatar_url()
348348
if avatar_url is not None:
349-
return PeerCapabilities(parse_capabilities(avatar_url))
349+
return PeerCapabilities(deserialize_capabilities(avatar_url))
350350
else:
351351
return PeerCapabilities({})
352352

raiden/tests/integration/network/transport/test_matrix_transport.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
from raiden.transfer.identifiers import CANONICAL_IDENTIFIER_UNORDERED_QUEUE, QueueIdentifier
5757
from raiden.transfer.state import NetworkState
5858
from raiden.transfer.state_change import ActionChannelClose
59-
from raiden.utils.capabilities import capconfig_to_dict, parse_capabilities
59+
from raiden.utils.capabilities import capconfig_to_dict, deserialize_capabilities
6060
from raiden.utils.formatting import to_checksum_address
6161
from raiden.utils.typing import Address, Dict, List, PeerCapabilities, TokenNetworkAddress, cast
6262
from raiden.waiting import wait_for_network_state
@@ -1354,7 +1354,7 @@ def test_transport_capabilities(raiden_network: List[RaidenService], capabilitie
13541354
app1_avatar_url = app1_user.get_avatar_url()
13551355
assert "adhoc_capability" in app1_avatar_url, "avatar_url not set for app1"
13561356
msg = "capabilities could not be parsed"
1357-
assert parse_capabilities(app1_avatar_url) == expected_capabilities, msg
1357+
assert deserialize_capabilities(app1_avatar_url) == expected_capabilities, msg
13581358

13591359
msg = "capabilities were not collected in transport client"
13601360
collected_capabilities = app0.transport._address_mgr.get_address_capabilities(app1.address)

raiden/tests/unit/test_utils.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from raiden.utils.capabilities import (
1212
capconfig_to_dict,
1313
capdict_to_config,
14-
parse_capabilities,
14+
deserialize_capabilities,
1515
serialize_capabilities,
1616
)
1717
from raiden.utils.keys import privatekey_to_publickey
@@ -97,26 +97,23 @@ def test_get_http_rtt_ignore_failing(requests_responses):
9797
assert get_average_http_response_time(url="http://url3", method="get") is None
9898

9999

100-
def test_parse_capabilities():
101-
capstring = 'foo,toad,bar="max",form,agar'
102-
parsed = parse_capabilities(capstring)
100+
def test_deserialize_capabilities():
101+
capstring = "eyJmb28iOiB0cnVlLCAidG9hZCI6IHRydWUsICJiYXIiOiAibWF4IiwgImFnYXIiOiB0cnVlfQ==\n"
102+
parsed = deserialize_capabilities(capstring)
103103
assert parsed.get("foo") is True
104104
assert parsed.get("toad") is True
105105
assert parsed.get("bar") == "max"
106-
assert parsed.get("form") is True
107106
assert parsed.get("agar") is True
108107
assert not parsed.get("nothing")
109108

110109
assert serialize_capabilities(parsed) == f"mxc://{capstring}"
111110

112111
parsed["false"] = False
113112

114-
assert serialize_capabilities(parsed) == f"mxc://{capstring}"
115-
116-
parsed["nothing"] = None
117-
assert 'nothing="None"' in serialize_capabilities(parsed)
113+
# Explicit new value changes the serialization format
114+
assert serialize_capabilities(parsed) != f"mxc://{capstring}"
118115

119-
assert parse_capabilities("") == dict()
116+
assert deserialize_capabilities("") == dict()
120117

121118
assert serialize_capabilities({}) == "mxc://"
122119

raiden/utils/capabilities.py

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,42 @@
1+
import binascii
2+
import json
3+
from base64 import decodebytes, encodebytes
14
from typing import Any, Dict, Optional
25

6+
import structlog
7+
38
from raiden.constants import Capabilities
49
from raiden.settings import CapabilitiesConfig
510

11+
log = structlog.get_logger(__name__)
12+
13+
14+
def _serialize(capdict: Optional[Dict[str, Any]]) -> str:
15+
if capdict is None:
16+
capdict = {}
17+
return encodebytes(json.dumps(capdict).encode("utf-8")).decode("utf-8")
618

7-
def parse_capabilities(capstring: str) -> Dict[str, Any]:
8-
if capstring.startswith("mxc://"):
9-
capstring = capstring[6:]
10-
elif "/" in capstring:
11-
capstring = capstring[capstring.rindex("/") + 1 :]
12-
result: Dict[str, Any] = {}
13-
if len(capstring) == 0:
14-
return result
15-
for token in capstring.split(","):
16-
if "=" in token:
17-
key, value = token.split("=")
18-
value = value.strip('"')
19-
result[key] = value
20-
else:
21-
result[token] = True
22-
return result
19+
20+
def _deserialize(b64capabilites: bytes) -> Dict[str, Any]:
21+
try:
22+
return json.loads(decodebytes(b64capabilites))
23+
except (binascii.Error, json.decoder.JSONDecodeError, UnicodeDecodeError):
24+
log.error(f'Malformed capabilities "{b64capabilites!r}"')
25+
return {}
2326

2427

2528
def serialize_capabilities(capdict: Optional[Dict[str, Any]]) -> str:
2629
if not capdict:
2730
return "mxc://"
28-
for key in capdict.keys():
29-
if "/" in str(key):
30-
raise ValueError(f"Key {key} is malformed, '/' not allowed")
31+
return f"mxc://{_serialize(capdict)}"
32+
3133

32-
entries = []
33-
for key, value in capdict.items():
34-
if isinstance(value, bool):
35-
if value:
36-
entries.append(key)
37-
else:
38-
entries.append(f'{key}="{value}"')
39-
if len(entries):
40-
return f"mxc://{','.join(entries)}"
41-
return "mxc://"
34+
def deserialize_capabilities(capstring: str) -> Dict[str, Any]:
35+
if capstring.startswith("mxc://"):
36+
capstring = capstring[6:]
37+
if "/" in capstring:
38+
capstring = capstring[capstring.rindex("/") + 1 :]
39+
return _deserialize(capstring.encode("utf-8"))
4240

4341

4442
def capdict_to_config(capdict: Dict[str, Any]) -> CapabilitiesConfig:

0 commit comments

Comments
 (0)