Esempio n. 1
0
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()
Esempio n. 2
0
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))
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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)
Esempio n. 6
0
    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()
Esempio n. 7
0
    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()
Esempio n. 8
0
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)
Esempio n. 9
0
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)
Esempio n. 10
0
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