class GiveFile: def __init__(self, conduit, file_path): self.muxer = MX2() self.network = conduit.get_interface(False, 0.0, 0.5, 0.0) self.network.bring_up() self.muxer.register_network(self.network) self.instance = self.muxer.create_instance("GiveFile") self.transport = STP(self.muxer, self.instance) self.path = file_path self.peers = set() self.instance.incoming_greeting.subscribe(self.rx_greeting) self.network.incoming_advertisment.subscribe(self.rx_advertisement) self.transport.incoming_stream.subscribe(self.incoming) self.network.advertise(self.instance.reference) def rx_advertisement(self, adv: Advertisement): if (adv.instance_reference not in self.peers): self.muxer.inquire(self.instance, adv.instance_reference, [adv.peer_info]) def rx_greeting(self, origin): self.peers.add(origin) self.transport.initialise_stream(origin).subscribe(self.make_request) def make_request(self, stream): stream.reply.subscribe(self.reply) print("Asking peer to gib file...") stream.write(b"Gib file") def reply(self, stream): print("Peer gibs file...") # Get file size size = struct.unpack("!L", stream.read(4))[0] # Save file file = open(str(uuid.uuid4()), 'wb') file.write(stream.read(size)) print("Done") def incoming(self, stream): print("I have a new stream") if (stream.read(8) != b"Gib file"): print("Peer did not ask me to gib file") return self.transport.initialise_stream( stream.origin, in_reply_to=stream.id).subscribe(self.send_file) def send_file(self, stream): print("Sending my file") stream.write(struct.pack("!L", os.path.getsize(self.path))) f = open(self.path, 'rb') stream.write(f.read()) f.close()
class AipExample: def __init__(self, network: Network): print("init") self.network = network self.muxer = MX2() self.muxer.register_network(network) self.discoverer = AIP(self.muxer) self.discoverer.add_network(self.network) self.discoverer.ready.subscribe(self.aip_ready) self.instance = self.muxer.create_instance("AipExample") self.info = ApplicationInformation.from_instance(self.instance) self.transport = STP(self.muxer, self.instance) self.start = time.time() self.instance.incoming_greeting.subscribe(self.on_greet) self.transport.incoming_stream.subscribe(self.on_stream) def aip_ready(self, state): print("aip ready") self.discoverer.add_application(self.info).subscribe(self.new_aip_peer) def new_aip_peer(self, instance): print("new aip peer") self.discoverer.find_application_instance(self.info).subscribe( self.new_app_peer) def new_app_peer(self, info: InstanceInformation): print("new app peer") self.muxer.inquire(self.instance, info.instance_reference, info.connection_methods) def on_greet(self, instance): print("on greet") def on_stream_established(stream): print("on stream established") stream.write(struct.pack("!H", int(time.time() - self.start))) stream.close() self.transport.initialise_stream(instance).subscribe( on_stream_established) def on_stream(self, stream): print("on stream") value = struct.unpack("!H", stream.read(2))[0] print("I was discovered in {} second(s)".format(value))
def __init__(self, conduit, file_path): self.muxer = MX2() self.network = conduit.get_interface(False, 0.0, 0.5, 0.0) self.network.bring_up() self.muxer.register_network(self.network) self.instance = self.muxer.create_instance("GiveFile") self.transport = STP(self.muxer, self.instance) self.path = file_path self.peers = set() self.instance.incoming_greeting.subscribe(self.rx_greeting) self.network.incoming_advertisment.subscribe(self.rx_advertisement) self.transport.incoming_stream.subscribe(self.incoming) self.network.advertise(self.instance.reference)
def __init__(self, network: Network): print("init") self.network = network self.muxer = MX2() self.muxer.register_network(network) self.discoverer = AIP(self.muxer) self.discoverer.add_network(self.network) self.discoverer.ready.subscribe(self.aip_ready) self.instance = self.muxer.create_instance("AipExample") self.info = ApplicationInformation.from_instance(self.instance) self.transport = STP(self.muxer, self.instance) self.start = time.time() self.instance.incoming_greeting.subscribe(self.on_greet) self.transport.incoming_stream.subscribe(self.on_stream)
def __init__(self, namespace: str, use_repeaters=True, networks=[]): super().__init__(namespace) self.__networks = networks self.__reachable_peers: Set[InstanceReference] = set() self.__resource_subjects: Dict[bytes, ReplaySubject] = {} self.__peer_subjects: Dict[InstanceReference, ReplaySubject] = {} self.__has_aip_group_peers = ReplaySubject() if (len(networks) == 0): port = 5156 while True: try: network = IPv4("0.0.0.0", port) self.__networks.append(network) break except Exception as e: if (port >= 9000): raise e port += 1 self.__muxer = MX2() for network in self.__networks: network.bring_up() self.__muxer.register_network(network) self.__discoverer = AIP(self.__muxer) self.__instance = self.__muxer.create_instance(self.namespace) self.__transport = STP(self.__muxer, self.__instance) self.__path_finder = RPP(self.__muxer, self.__discoverer) self.__path_finder.add_instance(self.__instance.reference) self.__instance.incoming_greeting.subscribe(self.__received_greeting) self.__transport.incoming_stream.subscribe(self.__new_stream) for network in self.__networks: self.__discoverer.add_network(network) self.__info = ApplicationInformation.from_instance(self.__instance) # Add the application to the discoverer self.__discoverer.add_application(self.__info).subscribe( self.__new_aip_app_peer)
def __init__(self, muxer: MX2, discoverer: AIP, is_repeater: bool = False): self.__muxer = muxer self.__discoverer = discoverer self.is_repeater = is_repeater self.instance_info: Dict[InstanceReference, PeerInfo] = {} self.instance_network: Dict[InstanceReference, Network] = {} self.__announced_instances: Set[InstanceReference] = set() # Create instance self.__instance = self.__muxer.create_instance(RPP_NAMESPACE) # Create transport self.__transport = STP(self.__muxer, self.__instance) # Create application information self.__info = ApplicationInformation.from_instance(self.__instance) # Subscribe to muxer and discoverer events # self.__discoverer.ready.subscribe(self.__aip_ready) self.__instance.incoming_greeting.subscribe(self.__received_greeting) self.__transport.incoming_stream.subscribe(self.__new_stream) self.__ready = False self.ready = Subject() # Are we a repeater? if(self.is_repeater): # Yes, tag ourself with that resource self.__info.resources.add(REPEATER_RESOURCE) # Add the application to the discoverer self.__discoverer.add_application(self.__info).subscribe(self.__new_aip_app_peer) # Keep a set of reachable RPP repeater peers self.__repeaters: Set[InstanceReference] = set() self._ready = False self.ready = Subject()
def __init__(self, muxer: MX2, *, capabilities=set( (CAPABILITY_ADDRESS_INFO, CAPABILITY_FIND_PEERS, CAPABILITY_QUERY_ANSWER)), join_all=False): self.__application_information: List[ApplicationInformation] = [] self.__capabilities = capabilities self.__join_all_groups = join_all self.__muxer = muxer self.__instance = muxer.create_instance("AIP") self.__transport = STP(self.__muxer, self.__instance) self.__discovered_peers: Set[InstanceReference] = set() self.__peer_connection_methods: Dict[InstanceReference, Set[PeerInfo]] = {} self.__instance_capabilities: Dict[InstanceReference, Set[int]] = {} self.__default_group = QueryGroup(20) self.__query_groups: Dict[bytes, QueryGroup] = {} self.__reachable_peers: Set[InstanceReference] = set() self.__instance.incoming_greeting.subscribe(self.__rx_greeting) self.__transport.incoming_stream.subscribe(self.__rx_stream) self.__queries = TTLCache(64, 120) self.__query_response_count = TTLCache(65536, 120) self.__handled_query_ids: Set[bytes] = set() self.__peer_info: Set[PeerInfo] = set() self.__new_peer_info = rx.subject.Subject() self.__new_group_peer: Dict[bytes, rx.subject.Subject] = {} self.__ready = False self.__on_peer_greet: Dict[InstanceReference, rx.subject.Subject] = {} self.ready = rx.subject.Subject()
class RPP: def __init__(self, muxer: MX2, discoverer: AIP, is_repeater: bool = False): self.__muxer = muxer self.__discoverer = discoverer self.is_repeater = is_repeater self.instance_info: Dict[InstanceReference, PeerInfo] = {} self.instance_network: Dict[InstanceReference, Network] = {} self.__announced_instances: Set[InstanceReference] = set() # Create instance self.__instance = self.__muxer.create_instance(RPP_NAMESPACE) # Create transport self.__transport = STP(self.__muxer, self.__instance) # Create application information self.__info = ApplicationInformation.from_instance(self.__instance) # Subscribe to muxer and discoverer events # self.__discoverer.ready.subscribe(self.__aip_ready) self.__instance.incoming_greeting.subscribe(self.__received_greeting) self.__transport.incoming_stream.subscribe(self.__new_stream) self.__ready = False self.ready = Subject() # Are we a repeater? if(self.is_repeater): # Yes, tag ourself with that resource self.__info.resources.add(REPEATER_RESOURCE) # Add the application to the discoverer self.__discoverer.add_application(self.__info).subscribe(self.__new_aip_app_peer) # Keep a set of reachable RPP repeater peers self.__repeaters: Set[InstanceReference] = set() self._ready = False self.ready = Subject() @property def instance_reference(self): return self.__instance.reference def add_instance(self, instance: InstanceReference): # Add to our set self.__announced_instances.add(instance) # Announce to connected repeaters self.__send_command(COMMAND_ANNOUNCE, Announcement([instance]).serialise().read()) def find_path(self, instance: InstanceReference): # Construct a query query = PathQuery(instance, 15, []) # Create a Path Info from a stream def read_response(stream: IngressStream): response = PathResponse.deserialise(stream) stream.close() # Prepend hop to respondant repeater # TODO these flags may need to be looked at some more or the # PathNode object redesigned # HACK we should probably just get some sort of full representation # from the RPP repeater we ask the path from, but can't at the moment as we require peer info response.nodes.insert(0, PathNode.PathNode(stream.origin, PathNode.FLAGS_NONE, self.__muxer.get_peer_info(stream.origin))) # TODO generate many possible strategies return [PathStrategy(PathInfo([x.instance for x in response.nodes]), response.nodes[0].peer_info),] # Send the query and map reply to PathResponse return self.__send_command(COMMAND_FIND_PATH, query.serialise().read(), True).pipe(operators.take(1), operators.map(read_response)) def __new_aip_app_peer(self, instance): # Query for RPP repeater instances self.__discoverer.find_application_resource(self.__info, REPEATER_RESOURCE).answer.subscribe(self.__found_instance) def __found_instance(self, instance_info: InstanceInformation): # Is this peer already reachable? if(instance_info.instance_reference in self.__repeaters): # Don't harras it return # Inquire about the peer self.__muxer.inquire(self.__instance, instance_info.instance_reference, instance_info.connection_methods) def __received_greeting(self, instance: InstanceReference): # Do we already know about this peer? if(instance in self.__repeaters): # Nothing to do return # No, announce our instances self.__send_command_to(COMMAND_ANNOUNCE, Announcement(self.__announced_instances).serialise().read(), instance).subscribe(on_completed=self.__set_ready) # Save as repeater self.__repeaters.add(instance) # Are we a repeater? if(self.is_repeater): # Send join (empty) self.__send_command_to(COMMAND_JOIN, b"", instance) # Save instance info for flag lookups self.instance_info[instance] = self.__muxer.get_peer_info(instance) self.instance_network[instance] = self.__muxer.get_peer_network(instance) def __set_ready(self): if(self._ready): return self.ready.on_completed() def __send_command(self, command_type: bytes, data: bytes, expect_reply: bool = False, blacklist: Set[InstanceReference] = set()) -> Subject: # If we expect replies to this command, create a subject reply_subject = None if(expect_reply): reply_subject = Subject() count = 0 # Loop over each repeater for repeater in self.__repeaters: # Is this a blacklisted repeater? if(repeater in blacklist): # Yes, skip continue # Send command subject = self.__send_command_to(command_type, data, repeater, expect_reply) count += 1 # Do we expect a reply? if(expect_reply): # Yes, connect to subject subject.subscribe(reply_subject.on_next) Log.debug("Broadcasted a command to {} repeater/s".format(count)) # Return reply subject return reply_subject def __send_command_to(self, command_type: bytes, data: bytes, instance: InstanceReference, expect_reply: bool = False) -> Subject: # Returns when the command has been sent, or with the reply if expctant subject = Subject() # Handler for eventual opening of stream def on_connected(stream: EgressStream): # Do we expect a reply? if(expect_reply): # Subscribe to reply stream.reply.subscribe(subject.on_next) # Send command type and command stream.write(command_type + data) # Close the stream stream.close() # Do we expect a reply? if(not expect_reply): # No, let caller know we are done subject.on_completed() # Open stream with the peer self.__transport.initialise_stream(instance).subscribe(on_connected) # Return the subject return subject def __new_stream(self, stream: IngressStream): # New command, what is it? command_type = stream.read(1) # Announce if(command_type == COMMAND_ANNOUNCE): # Read announcement announcement = Announcement.deserialise(stream) # Get peer info and network info = self.__muxer.get_peer_info(stream.origin) network = self.__muxer.get_peer_network(stream.origin) Log.debug("Peer announced itself with {} attached instance/s".format(len(announcement.instances))) # Update records for instance in announcement.instances: self.instance_info[instance] = info self.instance_network[instance] = network # Also add info for the RPP peer self.instance_info[stream.origin] = info self.instance_network[stream.origin] = network # Join elif(command_type == COMMAND_JOIN): Log.debug("Repeater peer joined") # Add as repeater peer self.__repeaters.add(stream.origin) # Save instance info for flag lookups self.instance_info[stream.origin] = self.__muxer.get_peer_info(stream.origin) self.instance_network[stream.origin] = self.__muxer.get_peer_network(stream.origin) # Find path elif(command_type == COMMAND_FIND_PATH): # Read the query query = PathQuery.deserialise(stream) # Is it an instance directly connected to us? if(query.target in self.instance_info): # Yes, get the flags flags = self.__get_instance_flags(stream.origin, query.target) Log.debug("Servicing query for path to an instance that is currently connected") self.__query_respond(stream.origin, stream.id, PathResponse([])) elif(query.ttl > 0): Log.debug("Forwarding query for path to instance") # Instance is not directly connected, forward query if still alive self.__forward_query(stream.origin, stream.id, query) def __forward_query(self, instance: InstanceReference, reply_to, query: PathQuery): # Add out own instance reference to the blacklist so it can't route through us again query.blacklist.append(self.__instance.reference) # Handle responses def on_responded(stream: IngressStream): # Read response response = PathResponse.deserialise(stream) # TODO: Save PeerInfo of other repeaters we may be able to communicate with # as the client asking for this path will try combinations # Get flags for respondant repeater flags = self.__get_instance_flags(instance, stream.origin) # Prepend hop to respondant repeater response.nodes.insert(0, PathNode.PathNode(stream.origin, flags, self.__muxer.get_peer_info(stream.origin))) # Forward the query to all my repeater friends self.__send_command(COMMAND_FIND_PATH, query.serialise().read(), True, set(query.blacklist)).pipe(operators.take(1)).subscribe(on_responded) def __get_instance_flags(self, origin: InstanceReference, target: InstanceReference): #set up flags flags = PathNode.FLAGS_NONE # Is it on the same network as the caller? # TODO this may need to use self.instance_network instead of the muxer lookup if(self.instance_network[origin] != self.instance_network[target]): # No, add bridge flag flags = flags | PathNode.FLAGS_BRIDGE return flags def __query_respond(self, instance: InstanceReference, reply_to, response: PathResponse): # Handler for stream setup def on_stream(stream: EgressStream): # Send the response stream.write(response.serialise().read()) # Close stream.close() # Set up connection with instance self.__transport.initialise_stream(instance, in_reply_to=reply_to).subscribe(on_stream)
class DefaultInstanceManager(InstanceManager): def __init__(self, namespace: str, use_repeaters=True, networks=[]): super().__init__(namespace) self.__networks = networks self.__reachable_peers: Set[InstanceReference] = set() self.__resource_subjects: Dict[bytes, ReplaySubject] = {} self.__peer_subjects: Dict[InstanceReference, ReplaySubject] = {} self.__has_aip_group_peers = ReplaySubject() if (len(networks) == 0): port = 5156 while True: try: network = IPv4("0.0.0.0", port) self.__networks.append(network) break except Exception as e: if (port >= 9000): raise e port += 1 self.__muxer = MX2() for network in self.__networks: network.bring_up() self.__muxer.register_network(network) self.__discoverer = AIP(self.__muxer) self.__instance = self.__muxer.create_instance(self.namespace) self.__transport = STP(self.__muxer, self.__instance) self.__path_finder = RPP(self.__muxer, self.__discoverer) self.__path_finder.add_instance(self.__instance.reference) self.__instance.incoming_greeting.subscribe(self.__received_greeting) self.__transport.incoming_stream.subscribe(self.__new_stream) for network in self.__networks: self.__discoverer.add_network(network) self.__info = ApplicationInformation.from_instance(self.__instance) # Add the application to the discoverer self.__discoverer.add_application(self.__info).subscribe( self.__new_aip_app_peer) def establish_stream(self, peer: InstanceReference, *, in_reply_to=None) -> Subject: # Settle on a reply reply = in_reply_to or b"\x00" * 16 # Ask the transport to establish a stream return self.__transport.initialise_stream(peer, in_reply_to=reply) def find_resource_peers(self, resource: bytes) -> Subject: # Do we already have a subject for this query? if (resource not in self.__resource_subjects): # Create one self.__resource_subjects[resource] = ReplaySubject() # Prepeare function to make the resource request def find_peers(has_group_peers): # Create a query for the resource query = self.__discoverer.find_application_resource( self.__info, resource) # Subscribe to the queries answer query.answer.subscribe( lambda x: self.__found_resource_instance(x, resource)) # When we are in a position to ask group peers, do it self.__has_aip_group_peers.pipe(take(1)).subscribe(find_peers) # Return the resource subject return self.__resource_subjects[resource] @property def resources(self) -> Set[bytes]: return self.__info.resources def __new_aip_app_peer(self, instance): # Query for application instances self.__discoverer.find_application_instance(self.__info).subscribe( self.__found_instance) # We now have an AIP group peer self.__has_aip_group_peers.on_next(True) def __found_instance(self, instance_info: InstanceInformation): # Is this peer already reachable? if (instance_info.instance_reference in self.__reachable_peers): # Don't harras it return # Inquire about the peer subject = self.__muxer.inquire(self.__instance, instance_info.instance_reference, instance_info.connection_methods) # Handle timeouts subject.subscribe(on_error=lambda x: self.__greeting_timeout( instance_info.instance_reference)) def __found_resource_instance(self, instance_info: InstanceInformation, resource: bytes): # Get the resource subject resource_subject = self.__resource_subjects[resource] # Get the instance subject instance_subject = self.__get_instance_subject( instance_info.instance_reference) # Notify resource subject when instance subject is reachable instance_subject.subscribe(resource_subject.on_next) # Handle new instance self.__found_instance(instance_info) def __received_greeting(self, instance: InstanceReference): # Have we already marked this instance as reachable if (instance in self.__reachable_peers): # Don't notify app again return # Add to reachable peers self.__reachable_peers.add(instance) # Notify instance subject self.__get_instance_subject(instance).on_next(instance) # Notify the app self.new_peer.on_next(instance) def __new_stream(self, stream): # Notify app of new stream self.new_stream.on_next(stream) def __get_instance_subject(self, ref: InstanceReference) -> Subject: # Do we have it? if (ref in self.__peer_subjects): # Yes return self.__peer_subjects[ref] # No, create it subject = ReplaySubject() self.__peer_subjects[ref] = subject return subject def __greeting_timeout(self, target: InstanceReference): # Have we already found this peer? if (target in self.__instance.reachable_peers or not self.use_repeaters): return # Did not receive greeting from instance, ask for paths via repeaters query = self.__path_finder.find_path(target) def handle_route(paths): # Have we already found this peer? if (target in self.__instance.reachable_peers): return # We have a path, inquire self.__muxer.inquire_via_paths(self.__instance, target, paths) # Subscribe to answers query.subscribe(handle_route)
class AIP: def __init__(self, muxer: MX2, *, capabilities=set( (CAPABILITY_ADDRESS_INFO, CAPABILITY_FIND_PEERS, CAPABILITY_QUERY_ANSWER)), join_all=False): self.__application_information: List[ApplicationInformation] = [] self.__capabilities = capabilities self.__join_all_groups = join_all self.__muxer = muxer self.__instance = muxer.create_instance("AIP") self.__transport = STP(self.__muxer, self.__instance) self.__discovered_peers: Set[InstanceReference] = set() self.__peer_connection_methods: Dict[InstanceReference, Set[PeerInfo]] = {} self.__instance_capabilities: Dict[InstanceReference, Set[int]] = {} self.__default_group = QueryGroup(20) self.__query_groups: Dict[bytes, QueryGroup] = {} self.__reachable_peers: Set[InstanceReference] = set() self.__instance.incoming_greeting.subscribe(self.__rx_greeting) self.__transport.incoming_stream.subscribe(self.__rx_stream) self.__queries = TTLCache(64, 120) self.__query_response_count = TTLCache(65536, 120) self.__handled_query_ids: Set[bytes] = set() self.__peer_info: Set[PeerInfo] = set() self.__new_peer_info = rx.subject.Subject() self.__new_group_peer: Dict[bytes, rx.subject.Subject] = {} self.__ready = False self.__on_peer_greet: Dict[InstanceReference, rx.subject.Subject] = {} self.ready = rx.subject.Subject() def add_network(self, network: Network): network.incoming_advertisment.subscribe(self.__rx_advertisement) self.__muxer.register_network(network) network.advertise(self.__instance.reference) def add_application(self, application_information: ApplicationInformation): # Save reference to the application self.__application_information.append(application_information) # Join group for this application self.__join_query_group(application_information.namespace_bytes) # Return the observable for this group return self.__new_group_peer[application_information.namespace_bytes] def find_application_instance(self, app: ApplicationInformation): # Are we in a query group for this application yet? if (app.namespace_bytes not in self.__query_groups): raise Exception( "Not in query group for specified application namespace") # Create the query query = Query(QUERY_APPLICATION + app.namespace_bytes) # Send the query self.__initiate_query(query, self.__query_groups[app.namespace_bytes]) # Return the answer subject return query.answer def find_application_resource(self, app: ApplicationInformation, resource_identifier: bytes): # Are we in a query group for this application yet? if (app.namespace_bytes not in self.__query_groups): raise Exception( "Not in query group for specified application namespace") # Is the resource identifier valid? if (len(resource_identifier) != 32): raise Exception("Resource identifier not 32 bytes.") # Create the query query = Query(QUERY_APPLICATION_RESOURCE + resource_identifier + app.namespace_bytes) # Send the query self.__initiate_query(query, self.__query_groups[app.namespace_bytes]) # Return the query return query def __initiate_query(self, query: Query, group: QueryGroup): # Save a reference to the query self.__queries[query.identifier] = query self.__handled_query_ids.add(query.identifier) # Send the query self.__send_query(query, group) def __send_query(self, query: Query, group: QueryGroup): # Does the query have any hops left? if (query.hops > MAX_QUERY_HOPS): return # Function to handle new streams for this query def on_stream_open(stream: EgressStream): # Tell the instance that the data that follows is a query stream.write(DATA_FOLLOWING_QUERY) # Write the query query.serialise(stream) # Close the stream stream.close() # Loop over each instance in the query group for instance in group.instances.copy(): # Is this instance reachable? if (instance in self.__reachable_peers): # Open a stream with the instance self.__transport.initialise_stream(instance).subscribe( on_stream_open) def __send_answer(self, answer: Answer): # Get (and remove) the last item from the path list send_to = answer.path.pop() # Don't send answers to queries we havent received if (answer.in_reply_to not in self.__query_response_count): return # Don't send answers to queries that have exceed there maximum replies if (self.__query_response_count[answer.in_reply_to] <= 0): return # Decrement response counter (stops at 0) self.__query_response_count[answer.in_reply_to] -= 1 # Function to handle new stream for this answer def on_stream_open(stream: EgressStream): # Tell the instance that the data that follows is an answer stream.write(DATA_FOLLOWING_ANSWER) # Write the query answer.serialise(stream) # Close the stream stream.close() # Open a stream with the instance self.__transport.initialise_stream(send_to).subscribe(on_stream_open) def __join_query_group(self, group: bytes): # Create the query group self.__query_groups[group] = QueryGroup() self.__new_group_peer[group] = rx.subject.Subject() # Create function to send quesy def send_group_query(): # Construct a query asking for peers in the group query = Query(QUERY_GROUP + group) Log.debug("Joining group '{}'".format(group.decode("utf-8"))) # Create handler for query answers def on_query_answer(answer: InstanceInformation): Log.debug("Found AIP peer in group '{}'".format( group.decode("utf-8"))) # Create a subject so we know when this peer has been greeted self.__on_peer_greet[ answer.instance_reference] = rx.subject.Subject() # Add to group self.__query_groups[group].add_peer(answer.instance_reference) # Are we already connected to this peer? if (answer.instance_reference in self.__reachable_peers): # No need to greet, already connected self.__new_group_peer[group].on_next( answer.instance_reference) return # When is has been greeted, notify the group subject self.__on_peer_greet[answer.instance_reference].subscribe( self.__new_group_peer[group].on_next) # Inquire self.__muxer.inquire(self.__instance, answer.instance_reference, answer.connection_methods) # Subscribe to the answer query.answer.subscribe(on_query_answer) # Send the query self.__initiate_query(query, self.__default_group) # Are we ready? if (self.__ready): # Yes, Send the query send_group_query() else: # No, do it when we are ready self.ready.subscribe(on_completed=send_group_query) def __rx_advertisement(self, advertisement: Advertisement): # Send an inquiry self.__muxer.inquire(self.__instance, advertisement.instance_reference, [advertisement.peer_info]) def __rx_greeting(self, greeting: InstanceReference): # Add to known peers self.__discovered_peers.add(greeting) # Request capabilities from the instance self.__request_capabilities(greeting).subscribe( lambda x: self.__rx_capabilities(x, greeting)) def __rx_capabilities(self, capabilities: List[bytes], instance: InstanceReference): # Save the capabilities self.__instance_capabilities[instance] = capabilities # Can we ask the peer for our address? if (CAPABILITY_ADDRESS_INFO in capabilities): # Yes, do it self.__request_address(instance).subscribe(self.__rx_address) # Can we ask the peer for other peers? if (CAPABILITY_FIND_PEERS in capabilities): # Yes, do it self.__request_peers(instance).subscribe(self.__rx_peers) # Can we send queries and answers to this peer? if (CAPABILITY_QUERY_ANSWER in capabilities): # Yes, add to default group self.__default_group.add_peer(instance) # Peer is now reachable for queries self.__reachable_peers.add(instance) # We now have a queryable peer if (not self.__ready): self.__ready = True self.ready.on_next(True) self.ready.on_completed() # Does this peer have a subject? if (instance in self.__on_peer_greet): # Notfy self.__on_peer_greet[instance].on_next(instance) def __rx_address(self, info: PeerInfo): # We received peer info, add to our set self.__peer_info.add(info) self.__new_peer_info.on_next(info) def __rx_peers(self, peers: List[InstanceInformation]): # We received a list of peers running AIP, do we want more peers? if (not self.__default_group.actively_connect): # Don't worry bout it return # Send out inquries to the peers for peer in peers: self.__muxer.inquire(self.__instance, peer.instance_reference, peer.connection_methods) def __rx_stream(self, stream: IngressStream): # Figure out what data follows following = stream.read(1) if (following == DATA_FOLLOWING_ANSWER and CAPABILITY_QUERY_ANSWER in self.__capabilities): self.__handle_answer(stream) elif (following == DATA_FOLLOWING_QUERY and CAPABILITY_QUERY_ANSWER in self.__capabilities): self.__handle_query(stream) elif (following == DATA_FOLLOWING_REQUEST): self.__handle_request(stream) else: stream.close() def __handle_answer(self, stream: IngressStream): # Deserialise the answer answer = Answer.deserialise(stream) # Is this an answer to one of our queries? if (answer.in_reply_to in self.__queries): # Yes, get the query query = self.__queries[answer.in_reply_to] # Get instance information from the answer info = InstanceInformation.deserialise(answer.data) # Notify the query's subject listeners query.answer.on_next(info) # Complete! return # Does this have somwhere to forward to? if (len(answer.path) > 0): # Put it back on its path self.__send_answer(answer) def __handle_query(self, stream: IngressStream): # Deserialise the query query = Query.deserialise(stream) # Have we come across this query before? if (query.identifier in self.__handled_query_ids): # Don't forward return # Mark as handled self.__handled_query_ids.add(query.identifier) # Create a replies counter self.__query_response_count[query.identifier] = query.max_replies # Append the originator of the stream to the query reply path query.return_path.append(stream.origin) # Increment the query hops query.hops += 1 # Find query type query_type = query.data[:1] if (query_type == QUERY_GROUP): # Get the group identifier group = query.data[1:] Log.debug("Received group query for group '{}'".format( group.decode("utf-8"))) # Are we not in this group, but joining all? if (self.__join_all_groups) and (group not in self.__query_groups): Log.debug("Automatically joining new group") # Join group self.__join_query_group(group) # Are we in this group? if (group in self.__query_groups): # Yes create a function for sending the answer def send_reply(isDefered=True): # Create some instance information instance = InstanceInformation(self.__instance.reference, self.__peer_info) # Send the instance information in the answer answer = Answer(instance.serialise(), query.return_path.copy(), query.identifier) # Send the answer self.__send_answer(answer) if (isDefered): Log.debug("Sent defered group reply") # Do we have peer info to send yet? if (len(self.__peer_info) > 0): # Yes, do it Log.debug("Responding to query for group '{}'".format( group.decode("utf-8"))) send_reply(False) else: # No, wait for peer info self.__new_peer_info.pipe(rx.operators.take(1)).subscribe( on_completed=send_reply) Log.debug( "Responding to query for group '{}' but defering response until we have peer info to send" .format(group.decode("utf-8"))) # This is a query for a group, forward on to default group self.__send_query(query, self.__default_group) elif (query_type == QUERY_APPLICATION): # Get the application namespace namespace = query.data[1:] Log.debug("Received application query for '{}'".format( namespace.decode("utf-8"))) # Are we in the group for this namespace? if (namespace in self.__query_groups): # Yes, find relevent ApplicationInformation for app in self.__application_information: # Is this app relevent? TODO: Use a dictionary if (app.namespace_bytes == namespace): # Yes, create instance information instance = InstanceInformation(app.instance, self.__peer_info) # Send the instance information in the answer self.__send_answer( Answer(instance.serialise(), query.return_path.copy(), query.identifier)) # Forward on to the group self.__send_query(query, self.__query_groups[namespace]) elif (query_type == QUERY_APPLICATION_RESOURCE): # Read the label label = query.data[1:33] # Read the application namespace namespace = query.data[33:] Log.debug( "Received application resource query for application '{}'". format(namespace.decode("utf-8"))) # Are we in the group for this namespace? if (namespace in self.__query_groups): # Yes, find relevent ApplicationInformation for app in self.__application_information: # Is this app relevent? TODO: Use a dictionary if (app.namespace_bytes == namespace and label in app.resources): # Yes, create instance information instance = InstanceInformation(app.instance, self.__peer_info) Log.debug( "Responded to peer requesting resource associated with this peer" ) # Send the instance information in the answer self.__send_answer( Answer(instance.serialise(), query.return_path.copy(), query.identifier)) # Forward on to the group self.__send_query(query, self.__query_groups[namespace]) def __handle_request(self, stream: IngressStream): # Get the request type request_type = stream.read(1) # Is the request one of our capabilities? if (request_type != b"C" and request_type not in self.__capabilities): # Ignore return # Handler to reply to the request def handle_stream(es: EgressStream): if (request_type == REQUEST_CAPABILITIES): Log.debug("Received capabilities request") capabilities = struct.pack("!B", len(self.__capabilities)) capabilities += b"".join(self.__capabilities) es.write(capabilities) es.close() elif (request_type == REQUEST_ADDRESS): Log.debug("Received address request") address = self.__muxer.get_peer_info(es.target) es.write(address.serialise()) es.close() elif (request_type == REQUEST_PEERS): Log.debug("Received peers request") # Select up to 5 peers to reply with peers = [ ] #[x for x in random.sample(self.__default_group.instances, min(5, len(self.__reachable_peers))) if x in self.__peer_connection_methods] # Send the count es.write(struct.pack("!B", len(peers))) for peer in peers: # Get the peer's connection methods methods = self.__peer_connection_methods[peer] # Create an instance information object info = InstanceInformation(peer, methods) # Write the object to the stream es.write(info.serialise()) # Get a reply stream self.__transport.initialise_stream( stream.origin, in_reply_to=stream.id).subscribe(handle_stream) # Have we encountered this peer before? if (stream.origin not in self.__discovered_peers): # No, add it self.__discovered_peers.add(stream.origin) # Ask for capabilities self.__request_capabilities(stream.origin).subscribe( lambda x: self.__rx_capabilities(x, stream.origin)) def __send_request(self, request, instance: InstanceReference): # Create the reply subject reply = rx.subject.Subject() # Create a handler def on_stream_open(stream: EgressStream): # Subscribe to stream reply stream.reply.subscribe(reply.on_next, reply.on_error, reply.on_completed) # Send the request stream.write(DATA_FOLLOWING_REQUEST + request) stream.close() # Open a stream with the peer self.__transport.initialise_stream(instance).subscribe(on_stream_open) return reply def __request_capabilities(self, instance: InstanceReference): # Create the subject reply = rx.subject.Subject() # Handler for the reply def on_reply(stream: IngressStream): # Read number of capabilities capability_count = struct.unpack("!B", stream.read(1))[0] # Read the capabilities capabilities = [ bytes([x]) for x in list(stream.read(capability_count)) ] # Notify subscriber reply.on_next(capabilities) reply.on_completed() # Make the request self.__send_request(REQUEST_CAPABILITIES, instance).subscribe(on_reply) return reply def __request_address(self, instance: InstanceReference): # Create the subject reply = rx.subject.Subject() # Handler for the reply def on_reply(stream: IngressStream): # Read the address (peer info) address = PeerInfo.deserialise(stream) # Notify subscriber reply.on_next(address) reply.on_completed() # Make the request self.__send_request(REQUEST_ADDRESS, instance).subscribe(on_reply) return reply def __request_peers(self, instance: InstanceReference): # Create the subject reply = rx.subject.Subject() # Handler for the reply def on_reply(stream: IngressStream): # Read number of peers peer_count = struct.unpack("!B", stream.read(1))[0] # List to hold info info = [] # Read the peers (instance info) for i in range(peer_count): info.append(InstanceInformation.deserialise(stream)) # Notify subscriber reply.on_next(info) reply.on_completed() # Make the request self.__send_request(REQUEST_PEERS, instance).subscribe(on_reply) return reply