A Python port of the Invisible Internet Project (I2P)
at main 219 lines 6.6 kB view raw
1"""Tests for TunnelId, HopConfig, and TunnelInfo. 2 3TDD: written before implementation. 4""" 5 6import io 7import os 8import time 9 10import pytest 11 12from i2p_data.tunnel import TunnelId, HopConfig, TunnelInfo 13 14 15# --------------------------------------------------------------------------- 16# TunnelId 17# --------------------------------------------------------------------------- 18 19class TestTunnelId: 20 """TunnelId: 4-byte unsigned integer identifier for tunnels.""" 21 22 def test_construction_valid(self): 23 tid = TunnelId(42) 24 assert int(tid) == 42 25 26 def test_construction_zero(self): 27 tid = TunnelId(0) 28 assert int(tid) == 0 29 30 def test_construction_max(self): 31 tid = TunnelId(2**32 - 1) 32 assert int(tid) == 2**32 - 1 33 34 def test_construction_negative_raises(self): 35 with pytest.raises(ValueError): 36 TunnelId(-1) 37 38 def test_construction_too_large_raises(self): 39 with pytest.raises(ValueError): 40 TunnelId(2**32) 41 42 def test_to_bytes(self): 43 tid = TunnelId(0x01020304) 44 assert tid.to_bytes() == b"\x01\x02\x03\x04" 45 46 def test_to_bytes_zero(self): 47 tid = TunnelId(0) 48 assert tid.to_bytes() == b"\x00\x00\x00\x00" 49 50 def test_from_bytes_roundtrip(self): 51 original = TunnelId(12345) 52 restored = TunnelId.from_bytes(original.to_bytes()) 53 assert original == restored 54 55 def test_from_bytes_specific(self): 56 tid = TunnelId.from_bytes(b"\x00\x00\x00\x2a") 57 assert int(tid) == 42 58 59 def test_from_stream(self): 60 stream = io.BytesIO(b"\x00\x00\x01\x00extra") 61 tid = TunnelId.from_stream(stream) 62 assert int(tid) == 256 63 # Stream should have advanced past the 4 bytes 64 assert stream.read() == b"extra" 65 66 def test_equality(self): 67 assert TunnelId(100) == TunnelId(100) 68 69 def test_inequality(self): 70 assert TunnelId(100) != TunnelId(200) 71 72 def test_equality_different_type(self): 73 assert TunnelId(100) != 100 74 75 def test_hash_same(self): 76 assert hash(TunnelId(100)) == hash(TunnelId(100)) 77 78 def test_hash_usable_in_set(self): 79 s = {TunnelId(1), TunnelId(2), TunnelId(1)} 80 assert len(s) == 2 81 82 def test_is_zero_true(self): 83 assert TunnelId(0).is_zero() is True 84 85 def test_is_zero_false(self): 86 assert TunnelId(1).is_zero() is False 87 88 def test_repr(self): 89 r = repr(TunnelId(42)) 90 assert "42" in r 91 92 def test_int(self): 93 tid = TunnelId(999) 94 assert int(tid) == 999 95 96 97# --------------------------------------------------------------------------- 98# HopConfig 99# --------------------------------------------------------------------------- 100 101class TestHopConfig: 102 """HopConfig: configuration for one hop in a tunnel.""" 103 104 def _make_hop_config(self): 105 return HopConfig( 106 receive_tunnel_id=TunnelId(1), 107 send_tunnel_id=TunnelId(2), 108 receive_key=os.urandom(32), 109 send_key=os.urandom(32), 110 iv_key=os.urandom(32), 111 reply_key=os.urandom(32), 112 reply_iv=os.urandom(16), 113 layer_key=os.urandom(32), 114 ) 115 116 def test_construction(self): 117 hc = self._make_hop_config() 118 assert isinstance(hc.receive_tunnel_id, TunnelId) 119 assert isinstance(hc.send_tunnel_id, TunnelId) 120 121 def test_field_access(self): 122 rk = os.urandom(32) 123 sk = os.urandom(32) 124 ivk = os.urandom(32) 125 rpk = os.urandom(32) 126 rpiv = os.urandom(16) 127 lk = os.urandom(32) 128 hc = HopConfig( 129 receive_tunnel_id=TunnelId(10), 130 send_tunnel_id=TunnelId(20), 131 receive_key=rk, 132 send_key=sk, 133 iv_key=ivk, 134 reply_key=rpk, 135 reply_iv=rpiv, 136 layer_key=lk, 137 ) 138 assert int(hc.receive_tunnel_id) == 10 139 assert int(hc.send_tunnel_id) == 20 140 assert hc.receive_key == rk 141 assert hc.send_key == sk 142 assert hc.iv_key == ivk 143 assert hc.reply_key == rpk 144 assert hc.reply_iv == rpiv 145 assert hc.layer_key == lk 146 147 148# --------------------------------------------------------------------------- 149# TunnelInfo 150# --------------------------------------------------------------------------- 151 152class TestTunnelInfo: 153 """TunnelInfo: metadata about a built tunnel.""" 154 155 def _make_tunnel_info(self, **overrides): 156 defaults = dict( 157 tunnel_id=TunnelId(500), 158 gateway=os.urandom(32), 159 length=3, 160 creation_time=1_000_000, 161 expiration=2_000_000, 162 ) 163 defaults.update(overrides) 164 return TunnelInfo(**defaults) 165 166 def test_construction(self): 167 ti = self._make_tunnel_info() 168 assert int(ti.tunnel_id) == 500 169 assert ti.length == 3 170 assert ti.creation_time == 1_000_000 171 assert ti.expiration == 2_000_000 172 173 def test_gateway_size(self): 174 gw = os.urandom(32) 175 ti = self._make_tunnel_info(gateway=gw) 176 assert ti.gateway == gw 177 assert len(ti.gateway) == 32 178 179 def test_gateway_wrong_size_raises(self): 180 with pytest.raises(ValueError): 181 self._make_tunnel_info(gateway=b"\x00" * 16) 182 183 def test_is_expired_past(self): 184 ti = self._make_tunnel_info(expiration=1_000) 185 assert ti.is_expired(now_ms=2_000) is True 186 187 def test_is_expired_future(self): 188 ti = self._make_tunnel_info(expiration=5_000) 189 assert ti.is_expired(now_ms=1_000) is False 190 191 def test_is_expired_exact(self): 192 ti = self._make_tunnel_info(expiration=1_000) 193 # At exact expiration time, should be expired (<=) 194 assert ti.is_expired(now_ms=1_000) is True 195 196 def test_is_expired_default_now(self): 197 # Far-future expiration should not be expired 198 ti = self._make_tunnel_info(expiration=int(time.time() * 1000) + 600_000) 199 assert ti.is_expired() is False 200 201 def test_equality(self): 202 gw = os.urandom(32) 203 a = self._make_tunnel_info(tunnel_id=TunnelId(1), gateway=gw) 204 b = self._make_tunnel_info(tunnel_id=TunnelId(1), gateway=gw) 205 assert a == b 206 207 def test_inequality(self): 208 gw = os.urandom(32) 209 a = self._make_tunnel_info(tunnel_id=TunnelId(1), gateway=gw) 210 b = self._make_tunnel_info(tunnel_id=TunnelId(2), gateway=gw) 211 assert a != b 212 213 def test_equality_different_type(self): 214 ti = self._make_tunnel_info() 215 assert ti != "not a tunnel" 216 217 def test_repr(self): 218 r = repr(self._make_tunnel_info()) 219 assert "500" in r