From c0e0dbb4b50bd4e2cc6defebc21162eae9c21085 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Fri, 16 Jan 2026 06:13:06 -0700 Subject: [PATCH 1/4] wip --- synapse/models/inet.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/synapse/models/inet.py b/synapse/models/inet.py index 4e6af36b836..86f46c67389 100644 --- a/synapse/models/inet.py +++ b/synapse/models/inet.py @@ -537,6 +537,24 @@ async def _normPyTuple(self, valu, view=None): return f'{proto}://{ip_repr}', {'subs': subs, 'virts': virts} + async def _normPyDict(self, valu, view=None): + ipaddr, ipnorminfo = await self.iptype.norm(valu.get('ip')) + ip_repr = self.iptype.repr(ipaddr) + + if 'proto' in valu: + proto, protonorminfo = await self.prototype.norm(valu.get('proto')) + proto_repr = self.prototype.repr(proto) + else: + proto_repr = self.defproto + + # todo: similar thing for this + port = valu.get('port', self.defport) + + subs = {'ip': (self.iptype.typehash, ipaddr, norminfo)} + virts = {'ip': (ipaddr, self.iptype.stortype)} + + + class Email(s_types.Str): def postTypeInit(self): From d68b754703863ff59f637583008c24dcfe06e6af Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Wed, 21 Jan 2026 08:52:00 -0700 Subject: [PATCH 2/4] norm sockaddr dict --- synapse/models/inet.py | 128 +++++++++++++++---------------- synapse/tests/test_model_inet.py | 8 +- 2 files changed, 67 insertions(+), 69 deletions(-) diff --git a/synapse/models/inet.py b/synapse/models/inet.py index 21ccf0ed30f..86dca33a6d7 100644 --- a/synapse/models/inet.py +++ b/synapse/models/inet.py @@ -435,89 +435,51 @@ async def _normPort(self, valu): return valu, None, '' + def _reqProto(self, proto): + if proto not in self.protos: + protostr = ','.join(self.protos) + mesg = f'inet:sockaddr protocol must be one of: {protostr}' + raise s_exc.BadTypeValu(mesg=mesg, valu=orig, name=self.name) + async def _normPyStr(self, valu, view=None): + # todo: exceptions in _normPyDict wont have original str context orig = valu - subs = {} - virts = {} + ctor = {} # no protos use case sensitivity yet... valu = valu.lower() - proto = self.defproto parts = valu.split('://', 1) if len(parts) == 2: - proto, valu = parts - - if proto not in self.protos: - protostr = ','.join(self.protos) - mesg = f'inet:sockaddr protocol must be one of: {protostr}' - raise s_exc.BadTypeValu(mesg=mesg, valu=orig, name=self.name) - - subs['proto'] = (self.prototype.typehash, proto, {}) + ctor['proto'], valu = parts valu = valu.strip().strip('/') # Treat as IPv6 if starts with [ or contains multiple : if valu.startswith('['): - match = srv6re.match(valu) - if match: - ipv6, port = match.groups() - - ipv6, norminfo = await self.iptype.norm(ipv6) - host = self.iptype.repr(ipv6) - subs['ip'] = (self.iptype.typehash, ipv6, norminfo) - virts['ip'] = (ipv6, self.iptype.stortype) - - portstr = '' - if port is not None: - port, norminfo = await self.porttype.norm(port) - subs['port'] = (self.porttype.typehash, port, norminfo) - virts['port'] = (port, self.porttype.stortype) - portstr = f':{port}' - - elif self.defport: - subs['port'] = (self.porttype.typehash, self.defport, {}) - virts['port'] = (self.defport, self.porttype.stortype) - portstr = f':{self.defport}' - - if port and proto in self.noports: - mesg = f'Protocol {proto} does not allow specifying ports.' - raise s_exc.BadTypeValu(mesg=mesg, valu=orig) + if (match := srv6re.match(valu)) is None: + mesg = f'Invalid IPv6 w/port ({orig})' + raise s_exc.BadTypeValu(valu=orig, name=self.name, mesg=mesg) - return f'{proto}://[{host}]{portstr}', {'subs': subs, 'virts': virts} - - mesg = f'Invalid IPv6 w/port ({orig})' - raise s_exc.BadTypeValu(valu=orig, name=self.name, mesg=mesg) + ipv6, port = match.groups() + ctor['ip'] = ipv6 # todo: provide as tuple to force ipv6? + if port is not None: + ctor['port'] = port + return await self._normPyDict(ctor, view=view) elif valu.count(':') >= 2: - ipv6, norminfo = await self.iptype.norm(valu) - host = self.iptype.repr(ipv6) - subs['ip'] = (self.iptype.typehash, ipv6, norminfo) - virts['ip'] = (ipv6, self.iptype.stortype) - - if self.defport: - subs['port'] = (self.porttype.typehash, self.defport, {}) - virts['port'] = (self.defport, self.porttype.stortype) - return f'{proto}://[{host}]:{self.defport}', {'subs': subs, 'virts': virts} - - return f'{proto}://{host}', {'subs': subs, 'virts': virts} + ctor['ip'] = valu + return await self._normPyDict(ctor, view=view) # Otherwise treat as IPv4 - valu, port, pstr = await self._normPort(valu) - if port: - subs['port'] = (self.porttype.typehash, port, {}) - virts['port'] = (port, self.porttype.stortype) - - if port and proto in self.noports: - mesg = f'Protocol {proto} does not allow specifying ports.' - raise s_exc.BadTypeValu(mesg=mesg, valu=orig) + parts = valu.split(':', 1) + if len(parts) == 2: + valu, port = parts + ctor['port'] = port - ipv4, norminfo = await self.iptype.norm(valu) - ipv4_repr = self.iptype.repr(ipv4) - subs['ip'] = (self.iptype.typehash, ipv4, norminfo) - virts['ip'] = (ipv4, self.iptype.stortype) + ctor['ip'] = valu - return f'{proto}://{ipv4_repr}{pstr}', {'subs': subs, 'virts': virts} + return await self._normPyDict(ctor, view=view) async def _normPyTuple(self, valu, view=None): ipaddr, norminfo = await self.iptype.norm(valu) @@ -538,22 +500,52 @@ async def _normPyTuple(self, valu, view=None): return f'{proto}://{ip_repr}', {'subs': subs, 'virts': virts} async def _normPyDict(self, valu, view=None): + subs = {} + virts = {} + ipaddr, ipnorminfo = await self.iptype.norm(valu.get('ip')) ip_repr = self.iptype.repr(ipaddr) + subs['ip'] = (self.iptype.typehash, ipaddr, ipnorminfo) + virts['ip'] = (ipaddr, self.iptype.stortype) if 'proto' in valu: - proto, protonorminfo = await self.prototype.norm(valu.get('proto')) + proto, protonorminfo = await self.prototype.norm(valu['proto']) proto_repr = self.prototype.repr(proto) + if proto not in self.protos: + protostr = ','.join(self.protos) + mesg = f'inet:sockaddr protocol must be one of: {protostr}' + raise s_exc.BadTypeValu(mesg=mesg, valu=valu['proto'], name=self.name) else: + proto, protonorminfo = self.defproto, {} proto_repr = self.defproto - # todo: similar thing for this - port = valu.get('port', self.defport) + subs['proto'] = (self.prototype.typehash, proto, {}) - subs = {'ip': (self.iptype.typehash, ipaddr, norminfo)} - virts = {'ip': (ipaddr, self.iptype.stortype)} + noport = proto in self.noports + port_repr = None + + if 'port' in valu: + if noport: + mesg = f'Protocol {proto} does not allow specifying ports.' + raise s_exc.BadTypeValu(mesg=mesg) + port, portnorminfo = await self.porttype.norm(valu['port']) + port_repr = self.porttype.repr(port) + + elif not noport and self.defport is not None: + port, portnorminfo = self.defport, {} + port_repr = self.defport + if ipaddr[0] == 6 and port_repr is not None: + servstr = f'{proto}://[{ip_repr}]' + else: + servstr = f'{proto}://{ip_repr}' + + if port_repr is not None: + subs['port'] = (self.porttype.typehash, port, portnorminfo) + virts['port'] = (port, self.porttype.stortype) + servstr = f'{servstr}:{port_repr}' + return servstr, {'subs': subs, 'virts': virts} class Email(s_types.Str): diff --git a/synapse/tests/test_model_inet.py b/synapse/tests/test_model_inet.py index 1dc830cdf19..7890e8624cd 100644 --- a/synapse/tests/test_model_inet.py +++ b/synapse/tests/test_model_inet.py @@ -190,7 +190,9 @@ async def test_sockaddr(self): subs = {'ip': ipsub, 'proto': tcpsub} virts = {'ip': ((6, 1), 26)} - self.eq(await t.norm('tcp://[::1]'), ('tcp://[::1]', {'subs': subs, 'virts': virts})) + # todo: norm one way for ipv6 w/o port? + # self.eq(await t.norm('tcp://[::1]'), ('tcp://[::1]', {'subs': subs, 'virts': virts})) + self.eq(await t.norm('tcp://[::1]'), ('tcp://::1', {'subs': subs, 'virts': virts})) ipnorm, ipinfo = await t.iptype.norm('::fFfF:0102:0304') ipsub = (t.iptype.typehash, (6, 0xffff01020304), ipinfo) @@ -201,6 +203,8 @@ async def test_sockaddr(self): ('tcp://[::ffff:1.2.3.4]:2', {'subs': subs, 'virts': virts})) await self.asyncraises(s_exc.BadTypeValu, t.norm('tcp://[::1')) # bad ipv6 w/ port + # todo: norm dict test + async def test_asn_collection(self): async with self.getTestCore() as core: @@ -1516,6 +1520,8 @@ async def test_server(self): self.eq(ctx.exception.get('mesg'), 'inet:sockaddr protocol must be one of: tcp,udp,icmp,gre') + # todo: norm dict test + async def test_url(self): formname = 'inet:url' async with self.getTestCore() as core: From 958b7951530c8dd08db13390158de67bc39776eb Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Wed, 21 Jan 2026 08:54:20 -0700 Subject: [PATCH 3/4] remove helpers --- synapse/models/inet.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/synapse/models/inet.py b/synapse/models/inet.py index 86dca33a6d7..f9b0134cf8f 100644 --- a/synapse/models/inet.py +++ b/synapse/models/inet.py @@ -423,24 +423,6 @@ def _getPort(self, valu): return valu[0] - async def _normPort(self, valu): - parts = valu.split(':', 1) - if len(parts) == 2: - valu, port = parts - port = (await self.porttype.norm(port))[0] - return valu, port, f':{port}' - - if self.defport: - return valu, self.defport, f':{self.defport}' - - return valu, None, '' - - def _reqProto(self, proto): - if proto not in self.protos: - protostr = ','.join(self.protos) - mesg = f'inet:sockaddr protocol must be one of: {protostr}' - raise s_exc.BadTypeValu(mesg=mesg, valu=orig, name=self.name) - async def _normPyStr(self, valu, view=None): # todo: exceptions in _normPyDict wont have original str context orig = valu From 1c1d08d83ee8a6f1172d22f5fdac82ce535320b3 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Wed, 21 Jan 2026 09:06:53 -0700 Subject: [PATCH 4/4] tuple as well --- synapse/models/inet.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/synapse/models/inet.py b/synapse/models/inet.py index f9b0134cf8f..dc47a2a2137 100644 --- a/synapse/models/inet.py +++ b/synapse/models/inet.py @@ -464,22 +464,7 @@ async def _normPyStr(self, valu, view=None): return await self._normPyDict(ctor, view=view) async def _normPyTuple(self, valu, view=None): - ipaddr, norminfo = await self.iptype.norm(valu) - - ip_repr = self.iptype.repr(ipaddr) - subs = {'ip': (self.iptype.typehash, ipaddr, norminfo)} - virts = {'ip': (ipaddr, self.iptype.stortype)} - proto = self.defproto - - if self.defport: - subs['port'] = (self.porttype.typehash, self.defport, {}) - virts['port'] = (self.defport, self.porttype.stortype) - if ipaddr[0] == 6: - return f'{proto}://[{ip_repr}]:{self.defport}', {'subs': subs, 'virts': virts} - else: - return f'{proto}://{ip_repr}:{self.defport}', {'subs': subs, 'virts': virts} - - return f'{proto}://{ip_repr}', {'subs': subs, 'virts': virts} + return await self._normPyDict({'ip': valu}, view=view) async def _normPyDict(self, valu, view=None): subs = {}