11"""RoutingService interface."""
22
3- from typing import Tuple
3+ import json
4+ import uuid
5+
6+ from typing import Tuple , List , Dict , Any
47from pydid .service import DIDCommV2Service
58from didcomm_messaging .packaging import PackagingService
69from didcomm_messaging .resolver import DIDResolver
@@ -18,24 +21,45 @@ def __init__(self, packaging: PackagingService, resolver: DIDResolver):
1821 self .packaging = packaging
1922 self .resolver = resolver
2023
21- async def _resolve_service (self , to : str ) -> DIDCommV2Service :
22- """Resolve the service endpoint for a given DID."""
23- doc = await self .resolver .resolve_and_parse (to )
24- if not doc .service :
25- raise RoutingServiceError (f"No service endpoint found for { to } " )
26-
27- first_didcomm_service = next (
28- (
29- service
30- for service in doc .service
31- if isinstance (service , DIDCommV2Service )
32- ),
33- None ,
34- )
35- if not first_didcomm_service :
36- raise RoutingServiceError (f"No DIDCommV2 service endpoint found for { to } " )
24+ async def _resolve_services (self , to : str ) -> List [DIDCommV2Service ]:
25+ if not await self .resolver .is_resolvable (to ):
26+ return []
27+ did_doc = await self .resolver .resolve_and_parse (to )
28+ services = []
29+ if did_doc .service : # service is not guaranteed to exist
30+ for did_service in did_doc .service :
31+ if "didcomm/v2" in did_service .service_endpoint .accept :
32+ services .append (did_service )
33+ if not services :
34+ return []
35+ return services
36+
37+ async def is_forwardable_service (self , service : DIDCommV2Service ) -> bool :
38+ """Determine if the uri of a service is a service we should forward to."""
39+ endpoint = service .service_endpoint .uri
40+ found_forwardable_service = await self .resolver .is_resolvable (endpoint )
41+ return found_forwardable_service
3742
38- return first_didcomm_service
43+ def _create_forward_message (
44+ self , to : str , next_target : str , message : str
45+ ) -> Dict [Any , Any ]:
46+ return {
47+ "typ" : "application/didcomm-plain+json" ,
48+ "type" : "https://didcomm.org/routing/2.0/forward" ,
49+ "id" : str (uuid .uuid4 ()),
50+ "to" : [to ],
51+ # "expires_time": 123456, # time to expire the forward message, in epoch time
52+ "body" : {"next" : next_target },
53+ "attachments" : [
54+ {
55+ "id" : str (uuid .uuid4 ()),
56+ "media_type" : "application/didcomm-encrypted+json" ,
57+ "data" : {
58+ "json" : json .loads (message ),
59+ },
60+ },
61+ ],
62+ }
3963
4064 async def prepare_forward (
4165 self , to : str , encoded_message : bytes
@@ -47,8 +71,63 @@ async def prepare_forward(
4771 encoded_message (bytes): The encoded message.
4872
4973 Returns:
50- The encoded message, and the service endpoint to forward to.
74+ The encoded message, and the services to forward to.
5175 """
52- service = await self ._resolve_service (to )
53- # TODO Do the stuff
54- return encoded_message , service
76+
77+ # Get the initial service
78+ services = await self ._resolve_services (to )
79+ chain = [
80+ {
81+ "did" : to ,
82+ "service" : services ,
83+ }
84+ ]
85+
86+ # Loop through service DIDs until we run out of DIDs to forward to
87+ to_did = services [0 ].service_endpoint .uri
88+ found_forwardable_service = await self .is_forwardable_service (services [0 ])
89+ while found_forwardable_service :
90+ services = await self ._resolve_services (to_did )
91+ if services :
92+ chain .append (
93+ {
94+ "did" : to_did ,
95+ "service" : services ,
96+ }
97+ )
98+ to_did = services [0 ].service_endpoint .uri
99+ found_forwardable_service = (
100+ await self .is_forwardable_service (services [0 ]) if services else False
101+ )
102+
103+ if not chain [- 1 ]["service" ]:
104+ raise RoutingServiceError (f"No DIDCommV2 service endpoint found for { to } " )
105+
106+ # Grab our target to pack the initial message to, then pack the message
107+ # for the DID target
108+ next_target = chain .pop (0 )["did" ]
109+ packed_message = encoded_message
110+
111+ # Loop through the entire services chain and pack the message for each
112+ # layer of mediators
113+ for service in chain :
114+ # https://identity.foundation/didcomm-messaging/spec/#sender-process-to-enable-forwarding
115+ # Respect routing keys by adding the current DID to the front of
116+ # the list, then wrapping message following routing key order
117+ routing_keys = service ["service" ][0 ].service_endpoint .routing_keys
118+ routing_keys .insert (0 , service ["did" ]) # prepend did
119+
120+ # Pack for each key
121+ while routing_keys :
122+ key = routing_keys .pop () # pop from end of list (reverse order)
123+ packed_message = await self .packaging .pack (
124+ json .dumps (
125+ self ._create_forward_message (key , next_target , packed_message )
126+ ),
127+ [key ],
128+ )
129+ next_target = key
130+
131+ # Return the forward-packed message as well as the last service in the
132+ # chain, which is the destination of the top-level forward message.
133+ return (packed_message , chain [- 1 ]["service" ])
0 commit comments