A Python port of the Invisible Internet Project (I2P)
at main 126 lines 4.0 kB view raw
1"""GarlicCloveBuilder — construct individual garlic cloves. 2 3Ported from net.i2p.router.message components. 4 5Builds CloveConfig objects for data delivery, ACK requests, 6and LeaseSet delivery — the three clove types in a typical 7outbound garlic message. 8""" 9 10from __future__ import annotations 11 12import os 13import struct 14import time 15 16from i2p_data.garlic_config import CloveConfig, GarlicConfig 17from i2p_data.i2np import DeliveryStatusMessage, DataMessage, DatabaseStoreMessage 18 19 20class GarlicCloveBuilder: 21 """Build individual garlic cloves for different delivery purposes.""" 22 23 @staticmethod 24 def build_data_clove( 25 dest_hash: bytes, 26 payload: bytes, 27 expiration: int | None = None, 28 ) -> CloveConfig: 29 """Build a DESTINATION-delivery clove wrapping payload in a DataMessage.""" 30 if expiration is None: 31 expiration = int(time.time() * 1000) + 60_000 32 33 # Wrap payload in I2NP DataMessage body format 34 data_msg = DataMessage(payload) 35 msg_bytes = data_msg.to_bytes() 36 37 return CloveConfig.for_destination( 38 dest_hash=dest_hash, 39 message_data=msg_bytes, 40 clove_id=int.from_bytes(os.urandom(4), "big"), 41 expiration=expiration, 42 ) 43 44 @staticmethod 45 def build_ack_clove( 46 reply_token: int, 47 our_ib_gateway: bytes, 48 our_ib_tunnel_id: int, 49 expiration: int | None = None, 50 ) -> CloveConfig: 51 """Build a TUNNEL-delivery ACK clove (DeliveryStatusMessage). 52 53 The ACK is delivered via our inbound tunnel so the reply 54 confirms the message reached the destination. 55 """ 56 if expiration is None: 57 expiration = int(time.time() * 1000) + 60_000 58 59 # DeliveryStatusMessage with the reply token as msg_id 60 ack_msg = DeliveryStatusMessage(reply_token, int(time.time() * 1000)) 61 msg_bytes = ack_msg.to_bytes() 62 63 return CloveConfig.for_tunnel( 64 router_hash=our_ib_gateway, 65 tunnel_id=our_ib_tunnel_id, 66 message_data=msg_bytes, 67 clove_id=int.from_bytes(os.urandom(4), "big"), 68 expiration=expiration, 69 ) 70 71 @staticmethod 72 def build_leaseset_clove( 73 our_lease_set_bytes: bytes, 74 expiration: int | None = None, 75 ) -> CloveConfig: 76 """Build a LOCAL-delivery clove carrying our LeaseSet. 77 78 The receiver stores our LeaseSet locally so they can 79 route replies back to us. 80 """ 81 if expiration is None: 82 expiration = int(time.time() * 1000) + 60_000 83 84 # Wrap in DatabaseStoreMessage (type 1) 85 key = b"\x00" * 32 # placeholder — receiver extracts from LS 86 db_store = DatabaseStoreMessage(key, 1, 0, our_lease_set_bytes) 87 msg_bytes = db_store.to_bytes() 88 89 return CloveConfig.for_local( 90 message_data=msg_bytes, 91 clove_id=int.from_bytes(os.urandom(4), "big"), 92 expiration=expiration, 93 ) 94 95 @staticmethod 96 def create_garlic_config( 97 dest_pub_key: bytes, 98 data_clove: CloveConfig, 99 ack_clove: CloveConfig | None = None, 100 ls_clove: CloveConfig | None = None, 101 msg_id: int | None = None, 102 expiration: int | None = None, 103 ) -> GarlicConfig: 104 """Assemble cloves into a GarlicConfig in standard order. 105 106 Order: ACK (if present), LeaseSet (if present), Data. 107 """ 108 if msg_id is None: 109 msg_id = int.from_bytes(os.urandom(4), "big") 110 if expiration is None: 111 expiration = int(time.time() * 1000) + 60_000 112 113 config = GarlicConfig( 114 recipient_public_key=dest_pub_key, 115 message_id=msg_id, 116 expiration=expiration, 117 ) 118 119 # Standard order: ACK first, then LS, then data 120 if ack_clove is not None: 121 config.add_clove(ack_clove) 122 if ls_clove is not None: 123 config.add_clove(ls_clove) 124 config.add_clove(data_clove) 125 126 return config