Explorar o código

Added GDP to eventually replace AIP

Billy Barrow %!s(int64=3) %!d(string=hai) anos
pai
achega
25cf1b6092

+ 81 - 0
src/lib/Protocols/GDP/Answer.vala

@@ -0,0 +1,81 @@
+using LibPeer.Networks;
+using Sodium.Asymmetric;
+
+namespace LibPeer.Protocols.Gdp {
+
+    public class Answer {
+
+        protected uint8[] raw_data { get; set; }
+
+        public Mx2.InstanceReference instance_reference { get; set; }
+
+        public PeerInfo[] connection_methods { get; set; }
+
+        public QueryBase query { get; set; }
+
+        public QuerySummary query_summary { get; set; }
+
+        public Answer.from_stream(DataInputStream stream) throws Error {
+
+            var challenge_pk = new uint8[Signing.PUBLIC_KEY_BYTES];
+            stream.read(challenge_pk);
+
+            var signature_size = stream.read_uint16();
+            var raw_data = new uint8[signature_size];
+            stream.read(raw_data);
+
+            var data = Signing.verify(raw_data, challenge_pk);
+            if(data == null) {
+                throw new IOError.INVALID_DATA("Invalid answer signature");
+            }
+
+            var ds = new Util.ByteComposer().add_byte_array(data).to_stream();
+            
+            instance_reference = new Mx2.InstanceReference.from_stream(ds);
+
+            var connection_method_count = ds.read_byte();
+            connection_methods = new PeerInfo[connection_method_count];
+            for(var i = 0; i < connection_method_count; i++) {
+                connection_methods[i] = PeerInfo.deserialise(ds);
+            }
+
+            query = QueryBase.new_from_stream(ds);
+            query_summary = new QuerySummary(query);
+
+            if(!query_summary.challenge.check_key(challenge_pk)) {
+                throw new IOError.INVALID_DATA("The answer challenge public key does not match the challenge public key found in the original query");
+            }
+        }
+
+        public void serialise(DataOutputStream stream) throws Error requires (raw_data.length > 0) {
+            stream.write(query_summary.challenge.public_key);
+            stream.put_uint16((uint16)raw_data.length);
+            stream.write(raw_data);
+        }
+
+        protected void sign() throws Error requires (query_summary.challenge.solved) {
+
+            var data = new Util.ByteComposer()
+                .add_with_stream(s => {
+                    instance_reference.serialise(s);
+                    s.put_byte((uint8)connection_methods.length);
+                    foreach(var method in connection_methods) {
+                        method.serialise(s);
+                    }
+                    query.serialise(s);
+                })
+                .to_byte_array();
+
+            raw_data = query_summary.challenge.sign(data);
+        }
+
+        public Answer(QueryBase query, Mx2.InstanceReference instance, PeerInfo[] methods) throws Error {
+            this.query = query;
+            instance_reference = instance;
+            connection_methods = methods;
+            query_summary = new QuerySummary(query);
+            sign();
+        }
+    }
+
+}

+ 54 - 0
src/lib/Protocols/GDP/Application.vala

@@ -0,0 +1,54 @@
+using LibPeer.Protocols.Mx2;
+
+namespace LibPeer.Protocols.Gdp {
+
+    public class GdpApplication {
+        
+        public string app_namespace { get; private set; }
+
+        public InstanceReference instance_reference { get; private set; }
+
+        private uint8[] namespace_secret_hash;
+
+        public uint8[] namespace_hash { get; private set; }
+
+        public signal void challenged(Bytes resource_hash, Challenge challenge);
+
+        public signal void query_answered(Answer answer);
+
+        public bool solve_app_challenge(Challenge challenge) {
+            return challenge.complete(xor_with_secret_hash(challenge.challenge_blob));
+        }
+
+        public Challenge create_app_challenge() {
+            return new Challenge(xor_with_secret_hash);
+        }
+
+        private uint8[] xor_with_secret_hash(uint8[] data) {
+            var output = new uint8[data.length];
+            for(var i = 0; i < data.length; i++) {
+                output[i] = data[i] ^ namespace_secret_hash[i];
+            }
+            return output;
+        }
+
+        public GdpApplication(string name, InstanceReference instance) {
+            app_namespace = name;
+            instance_reference = instance;
+
+            var checksum = new Checksum(ChecksumType.SHA512);
+            checksum.update((uchar[])app_namespace, app_namespace.length);
+            namespace_secret_hash = new uint8[ChecksumType.SHA512.get_length()];
+            size_t size = namespace_secret_hash.length;
+            checksum.get_digest(namespace_secret_hash, ref size);
+
+            checksum = new Checksum(ChecksumType.SHA256);
+            checksum.update(namespace_secret_hash, namespace_secret_hash.length);
+            namespace_hash = new uint8[ChecksumType.SHA256.get_length()];
+            size = namespace_hash.length;
+            checksum.get_digest(namespace_hash, ref size);
+        }
+
+    }
+
+}

+ 53 - 0
src/lib/Protocols/GDP/Challenge.vala

@@ -0,0 +1,53 @@
+using Sodium.Asymmetric;
+
+
+namespace LibPeer.Protocols.Gdp {
+
+    public class Challenge {
+
+        public delegate uint8[] ChallengeGenerator(uint8[] secret);
+
+        public uint8[] public_key { get; set; }
+
+        public uint8[] challenge_blob { get; set; }
+
+        private uint8[]? secret { get; set; }
+
+        public bool solved {
+            get{
+                return secret != null;
+            }
+        }
+
+        public bool complete(uint8[] secret) {
+            var signed = Signing.sign(public_key, secret);
+            if(Signing.verify(signed, public_key) != null) {
+                this.secret = secret;
+                return true;
+            }
+            return false;
+        }
+
+        internal uint8[] sign(uint8[] message) requires (solved) {
+            return Signing.sign(message, secret);
+        }
+
+        public bool check_key(uint8[] compare) {
+            return new Bytes(compare).compare(new Bytes(public_key)) == 0;
+        }
+
+        public Challenge(ChallengeGenerator generator) {
+            public_key = new uint8[Signing.PUBLIC_KEY_BYTES];
+            var sk = new uint8[Signing.SECRET_KEY_BYTES];
+            Signing.generate_keypair(public_key, sk);
+            challenge_blob = generator(sk);
+        }
+
+        public Challenge.from_values(uint8[] key, uint8[] challenge) {
+            public_key = key;
+            challenge_blob = challenge;
+        }
+
+    }
+
+}

+ 308 - 0
src/lib/Protocols/GDP/GeneralDiscoveryProtocol.vala

@@ -0,0 +1,308 @@
+using LibPeer.Protocols.Mx2;
+using LibPeer.Protocols.Stp;
+using LibPeer.Protocols.Stp.Streams;
+using LibPeer.Networks;
+using LibPeer.Util;
+using Sodium.Asymmetric;
+using Gee;
+
+
+namespace LibPeer.Protocols.Gdp {
+
+    private enum Command {
+        ASSOCIATE = 0,
+        PEERS = 1,
+        QUERY = 2,
+        ANSWER = 3,
+        DISASSOCIATE = 255
+    }
+
+    public class GeneralDiscoveryProtocol {
+
+        private const int QUERY_MAX_HOPS = 10;
+        public const uint8[] EMPTY_RESOURCE = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+        protected uint8[] public_key;
+        protected uint8[] private_key;
+        protected ConcurrentHashMap<Bytes, InstanceReference> peers = new ConcurrentHashMap<Bytes, InstanceReference>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+        protected ConcurrentHashMap<Bytes, GdpApplication> applications = new ConcurrentHashMap<Bytes, GdpApplication>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+        protected HashSet<PeerInfo> peer_info = new HashSet<PeerInfo>((a) => a.hash(), (a, b) => a.equals(b));
+        protected AsyncQueue<QueryBase> query_queue = new AsyncQueue<QueryBase>();
+        protected Muxer muxer;
+        protected Instance instance;
+        protected StreamTransmissionProtocol transport;
+
+        public bool is_ready {
+            get {
+                return peers.size > 0 && peer_info.size > 0;
+            }    
+        }
+
+        public GdpApplication add_application(Instance instance) {
+            var app = new GdpApplication(instance.application_namespace, instance.reference);
+            applications.set(new Bytes(app.namespace_hash), app);
+            return app;
+        }
+
+        public void add_network(Network network) {
+            network.incoming_advertisment.connect(handle_advertisement);
+            muxer.register_network(network);
+            network.advertise(instance.reference);
+        }
+
+        public void query_general(GdpApplication app, bool allow_routing = true) throws Error {
+            var query = new Query() {
+                sender_id = public_key,
+                max_hops = QUERY_MAX_HOPS,
+                allow_routing = allow_routing,
+                namespace_hash = app.namespace_hash,
+                resource_hash = EMPTY_RESOURCE,
+                challenge = app.create_app_challenge()
+            };
+            query.sign(private_key);
+            send_query(query);
+        }
+
+        public void query_resource(GdpApplication app, uint8[] resource_identifier, Challenge challenge, bool allow_routing = true) throws Error requires (resource_identifier.length == ChecksumType.SHA512.get_length()) {
+            var query = new Query() {
+                sender_id = public_key,
+                max_hops = QUERY_MAX_HOPS,
+                allow_routing = allow_routing,
+                namespace_hash = app.namespace_hash,
+                resource_hash = resource_identifier,
+                challenge = challenge
+            };
+            query.sign(private_key);
+            send_query(query);
+        }
+
+        public GeneralDiscoveryProtocol(Muxer muxer) {
+            this.muxer = muxer;
+            instance = muxer.create_instance ("GDP");
+            transport = new StreamTransmissionProtocol(muxer, instance);
+
+            // Generate identity
+            public_key = new uint8[Signing.PUBLIC_KEY_BYTES];
+            private_key = new uint8[Signing.SECRET_KEY_BYTES];
+            Signing.generate_keypair(public_key, private_key);
+
+            // Attach signal handlers
+            instance.incoming_greeting.connect(handle_greeting);
+            transport.incoming_stream.connect(handle_stream);
+        }
+
+        private void handle_stream(StreamTransmissionProtocol stp, StpInputStream stream) {
+            var command = new uint8[1];
+            stream.read(command);
+
+            switch (command[0]) {
+                case Command.ASSOCIATE:
+                    handle_association(stream);
+                    break;
+                case Command.PEERS:
+                    handle_peers(stream);
+                    break;
+                case Command.QUERY:
+                    handle_query(stream);
+                    break;
+                case Command.ANSWER:
+                    handle_answer(stream);
+                    break;
+                case Command.DISASSOCIATE:
+                    handle_disassociation(stream);
+                    break;
+            }
+            stream.close();
+        }
+
+        private void handle_greeting(InstanceReference origin) {
+            send_command(origin, Command.ASSOCIATE, s => serialise_association_information(origin, s), handle_association_reply);
+        }
+
+        protected void handle_advertisement(Advertisement advertisement) {
+            // Send an inquiry
+            muxer.inquire(instance, advertisement.instance_reference, new PeerInfo[] { advertisement.peer_info });
+        }
+
+        private void handle_association_reply(StpInputStream stream) {
+            handle_association(stream, true);
+        }
+
+        private void handle_association(StpInputStream stream, bool is_final = false) throws Error {
+            var id = new ByteComposer().add_from_stream(stream, Signing.PUBLIC_KEY_BYTES).to_bytes();
+            var peer_info = PeerInfo.deserialise(stream);
+            if(is_final) {
+                add_peer(id, stream.origin, peer_info);
+            }
+            if(!is_final) {
+                var origin = stream.origin;
+                transport.initialise_stream(origin, stream.session_id).established.connect(s => {
+                    serialise_association_information(origin, s);
+                    stream.close();
+                    add_peer(id, stream.origin, peer_info);
+                });
+            }
+        }
+
+        private void serialise_association_information(InstanceReference origin, OutputStream stream) {
+            var peer_info = muxer.get_peer_info_for_instance(origin);
+            stream.write(public_key);
+            peer_info.serialise(stream);
+        }
+
+        private void handle_peers(StpInputStream stream) throws Error {
+            // todo
+        }
+
+        private void handle_query(StpInputStream stream) throws Error {
+            var dis = new DataInputStream(stream);
+            var query = QueryBase.new_from_stream(dis);
+            var summary = new QuerySummary(query);
+            
+            if(!summary.validate()) {
+                return;
+            }
+
+            // Do we have the specified app?
+            if(applications.has_key(summary.namespace_hash)) {
+                var app = applications.get(summary.namespace_hash);
+                if(summary.is_null_resource()) {
+                    if(app.solve_app_challenge(summary.challenge)) {
+                        answer(stream, query, app);
+                    }
+                }
+                else {
+                    app.challenged(new Bytes(summary.resource_hash), summary.challenge);
+                    if(summary.challenge.solved) {
+                        answer(stream, query, app);
+                    }
+                }
+            }
+
+            // Should we forward this?
+            if(summary.should_forward(new Bytes(public_key))) {
+                forward_query(query);
+            }
+        }
+
+        private void handle_answer(StpInputStream stream) {
+            var dis = new DataInputStream(stream);
+            var answer = new Answer.from_stream(dis);
+            
+            if(!answer.query_summary.has_visited(new Bytes(public_key))) {
+                // Drop any answer that we had no part in forwarding the query for
+                return;
+            }
+
+            if(!answer.query_summary.validate()) {
+                // Drop any answer that is based on a query that is invalid
+                return;
+            }
+
+            var query = answer.query;
+            var forward = false;
+            while(query is WrappedQuery) {
+                var wrapped = (WrappedQuery)query;
+                query = wrapped.query;
+                if(query.compare_sender(public_key)) {
+                    forward = true;
+                    break;
+                }
+            }
+
+            if(forward) {
+                var sender_id = new Bytes(query.sender_id);
+                if(peers.has_key(sender_id)) {
+                    send_command(peers.get(sender_id), Command.ANSWER, answer.serialise);
+                }
+            }
+            else if(query.compare_sender(public_key) && applications.has_key(answer.query_summary.namespace_hash)) {
+                var app = applications.get(answer.query_summary.namespace_hash);
+                app.query_answered(answer);
+            }
+        }
+
+        private void handle_disassociation(StpInputStream stream) throws Error {
+            var id = new ByteComposer().add_from_stream(stream, Signing.PUBLIC_KEY_BYTES).to_bytes();
+            if(peers.has_key(id)) {
+                var peer = peers.get(id);
+                if(peer.compare(stream.origin) == 0) {
+                    peers.remove(id);
+                }
+            }
+        }
+
+        private void answer(StpInputStream stream, QueryBase query, GdpApplication app) throws Error {
+            var answer_obj = new Answer(query, app.instance_reference, get_peer_info());
+            send_command(stream.origin, Command.ANSWER, answer_obj.serialise);
+        }
+
+        private void forward_query(QueryBase query) throws Error {
+            var wrapped = new WrappedQuery(public_key, query);
+            wrapped.sign(private_key);
+            send_query(query);
+        }
+
+        private void send_query(QueryBase query) throws Error {
+            query_queue.push(query);
+        }
+
+        private bool queue_running = false;
+        private void start_queue_worker() {
+            queue_running = true;
+            ThreadFunc<bool> queue_worker = () => {
+                while (queue_running) {
+                    var query = query_queue.pop();
+                    var summary = new QuerySummary(query);
+                    try {
+                        foreach(var peer in peers) {
+                            if(!summary.has_visited(peer.key)) {
+                                send_command(peer.value, Command.QUERY, query.serialise);
+                            }
+                            Posix.sleep(Random.int_range(5, 30));
+                        }
+                    }
+                    catch (Error e) {
+                        printerr(@"Exception on query sender queue: $(e.message)\n");
+                    }
+                }
+                return false;
+            };
+
+            new Thread<bool>(@"GDP-Query-Sender", queue_worker);
+        }
+
+        private PeerInfo[] get_peer_info() {
+            lock(peer_info) {
+                var output = new PeerInfo[peer_info.size];
+                int i = 0;
+                foreach(var info in peer_info) {
+                    output[i] = info;
+                    i++;
+                }
+                return output;
+            }
+        }
+
+        private void add_peer(Bytes id, InstanceReference ir, PeerInfo info) {
+            peer_info.add(info);
+            peers.set(id, ir);
+            if(!queue_running) {
+                start_queue_worker();
+            }
+        }
+
+        private delegate void ReplyHandler(StpInputStream stream);
+
+        private void send_command(InstanceReference peer, Command command, ByteComposer.StreamComposer serialiser, ReplyHandler? handler = null) throws Error {
+            var message = new ByteComposer().add_byte(command).add_with_stream(serialiser).to_byte_array();
+            transport.initialise_stream(peer).established.connect(s => {
+                if(handler != null) {
+                    s.reply.connect(s => handler(s));
+                }
+                s.write(message);
+                s.close();
+            });
+        }
+    }
+}

+ 64 - 0
src/lib/Protocols/GDP/Query.vala

@@ -0,0 +1,64 @@
+using LibPeer.Util;
+using Sodium.Asymmetric;
+
+namespace LibPeer.Protocols.Gdp {
+
+    public class Query : QueryBase {
+
+        public int max_hops { get; set; }
+
+        public bool allow_routing { get; set; }
+        
+        public uint8[] namespace_hash { get; set; }
+        
+        public uint8[] resource_hash { get; set; }
+        
+        public Challenge challenge { get; set; }
+
+        public void sign(uint8[] signing_key) throws Error {
+            var data = new ByteComposer()
+                .add_with_stream(s => {
+                    s.put_byte(5);
+                    s.put_byte((uint8) max_hops);
+                    s.put_byte(allow_routing ? 1 : 0);
+                    s.write(namespace_hash);
+                    s.write(resource_hash);
+                    s.write(challenge.public_key);
+                    s.put_uint16((uint16)challenge.challenge_blob.length);
+                    s.write(challenge.challenge_blob);
+                })
+                .to_byte_array();
+
+            raw_data = Signing.sign(data, signing_key);
+        }
+
+
+        internal Query.from_stream(uint8[] sender, uint8[] raw, DataInputStream stream) throws IOError {
+            raw_data = raw;
+            sender_id = sender;
+            
+            if(stream.read_byte() != 5) {
+                throw new IOError.INVALID_DATA("Invalid magic number");
+            }
+
+            max_hops = stream.read_byte();
+            allow_routing = stream.read_byte() == 1;
+
+            namespace_hash = new uint8[ChecksumType.SHA256.get_length()];
+            stream.read(namespace_hash);
+
+            resource_hash = new uint8[ChecksumType.SHA512.get_length()];
+            stream.read(resource_hash);
+
+            var challenge_pk = new uint8[Signing.PUBLIC_KEY_BYTES];
+            stream.read(challenge_pk);
+
+            var challenge_blob_size = stream.read_uint16();
+            var challenge_blob = new uint8[challenge_blob_size];
+            stream.read(challenge_blob);
+            
+            challenge = new Challenge.from_values(challenge_pk, challenge_blob);
+        }
+    }
+
+}

+ 56 - 0
src/lib/Protocols/GDP/QueryBase.vala

@@ -0,0 +1,56 @@
+using Sodium.Asymmetric;
+
+namespace LibPeer.Protocols.Gdp {
+
+    public abstract class QueryBase {
+
+        protected uint8[] raw_data { get; set; }
+        
+        public uint8[] sender_id { get; set; }
+
+        public static QueryBase new_from_stream(DataInputStream stream) throws IOError, Error {
+
+            var sender_id = new uint8[Signing.PUBLIC_KEY_BYTES];
+            stream.read(sender_id);
+
+            var signature_len = stream.read_uint32();
+            var signature = new Util.ByteComposer().add_from_stream(stream, signature_len).to_byte_array();
+
+            var query = Signing.verify(signature, sender_id);
+
+            if(query == null) {
+                throw new IOError.INVALID_DATA("Invalid query signature");
+            }
+
+            var query_stream = new DataInputStream(new MemoryInputStream.from_data(query));
+            if(query[0] == 5) {
+                return new Query.from_stream(sender_id, signature, query_stream);
+            }
+            else if(query[0] == 31) {
+                return new WrappedQuery.from_stream(sender_id, signature, query_stream);
+            }
+
+            throw new IOError.INVALID_DATA(@"Unrecognised query type $(query[0]).");
+        }
+
+
+        public virtual void serialise(DataOutputStream stream) throws IOError requires (raw_data.length > 0)  {
+            stream.write(sender_id);
+            stream.put_uint32((uint16)raw_data.length);
+            stream.write(raw_data);
+        }
+
+        public bool compare_sender(uint8[] sender) {
+            if(sender.length != sender_id.length) {
+                return false;
+            }
+            for(var i = 0; i < sender.length; i++) {
+                if(sender[i] != sender_id[i]){
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+    
+}

+ 77 - 0
src/lib/Protocols/GDP/QuerySummary.vala

@@ -0,0 +1,77 @@
+
+namespace LibPeer.Protocols.Gdp {
+
+    public class QuerySummary {
+
+        public int max_hops { get; set; }
+
+        public int actual_hops { get; set; }
+
+        public bool allow_routing { get; set; }
+
+        public Gee.LinkedList<RouterInfo> routing_path { get; set; }
+
+        private Gee.HashSet<Bytes> sender_ids = new Gee.HashSet<Bytes>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+
+        public Bytes namespace_hash { get; set; }
+        
+        public uint8[] resource_hash { get; set; }
+        
+        public Challenge challenge { get; set; }
+
+        public QuerySummary(QueryBase query) {
+
+            var depth = 0;
+            var q = query;
+
+            routing_path = new Gee.LinkedList<RouterInfo>();
+
+            while(q is WrappedQuery) {
+                depth ++;
+                var wrapped = (WrappedQuery)q;
+                q = wrapped.query;
+                if(wrapped.router_info != null) {
+                    routing_path.add(wrapped.router_info);
+                }
+                sender_ids.add(new Bytes(wrapped.sender_id));
+            }
+
+            var main_query = (Query)q;
+            sender_ids.add(new Bytes(main_query.sender_id));
+            actual_hops = depth;
+            max_hops = main_query.max_hops;
+            allow_routing = main_query.allow_routing;
+            namespace_hash = new Bytes(main_query.namespace_hash);
+            resource_hash = main_query.resource_hash;
+            challenge = main_query.challenge;
+        }
+
+        public bool validate() {
+            if(!allow_routing && routing_path.size > 0) {
+                return false;
+            }
+            if(actual_hops > max_hops) {
+                return false;
+            }
+            return true;
+        }
+
+        public bool should_forward(Bytes sender_id) {
+            return actual_hops < max_hops && validate() && !has_visited(sender_id);
+        }
+
+        public bool is_null_resource() {
+            for(var i = 0; i < resource_hash.length; i++) {
+                if(resource_hash[0] != 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public bool has_visited(Bytes sender_id) {
+            return sender_ids.contains(sender_id);
+        }
+    }
+
+}

+ 90 - 0
src/lib/Protocols/GDP/WrappedQuery.vala

@@ -0,0 +1,90 @@
+using LibPeer.Protocols;
+using LibPeer.Networks;
+using LibPeer.Util;
+using Sodium.Asymmetric;
+
+namespace LibPeer.Protocols.Gdp {
+
+    public class WrappedQuery : QueryBase {
+
+        public QueryBase query { get; set; }
+
+        public RouterInfo? router_info { get; set; }
+
+        internal WrappedQuery.from_stream(uint8[] sender, uint8[] raw, DataInputStream stream) throws IOError, Error {
+            raw_data = raw;
+            sender_id = sender;
+            
+            if(stream.read_byte() != 31) {
+                throw new IOError.INVALID_DATA("Invalid magic number");
+            }
+
+            if(stream.read_byte() % 2 == 1) {
+                router_info = new RouterInfo.from_stream (stream);
+            }
+
+            query = QueryBase.new_from_stream (stream);
+        }
+
+        public void sign(uint8[] signing_key) throws Error {
+            var data = new ByteComposer()
+                .add_with_stream(s => {
+                    s.put_byte(31);
+                    s.put_byte(router_info != null ? 1 : 0);
+                    if(router_info != null) {
+                        router_info.serialise(s);
+                    }
+                    query.serialise(s);
+                })
+                .to_byte_array();
+
+            var signed = Signing.sign(data, signing_key);
+            raw_data = signed;
+        }
+
+        public WrappedQuery(uint8[] sender_id, QueryBase query, RouterInfo? router_info = null) {
+            this.sender_id = sender_id;
+            this.query = query;
+            this.router_info = router_info;
+        }
+    }
+
+    public class RouterInfo {
+        
+        public Mx2.InstanceReference instance_reference { get; set; }
+
+        public PeerInfo[] queryer_connection_methods { get; set; }
+
+        public PeerInfo[] answerer_connection_methods { get; set; }
+
+        public RouterInfo.from_stream(DataInputStream stream) throws IOError, Error {
+            instance_reference = new Mx2.InstanceReference.from_stream (stream);
+
+            var qcms = stream.read_byte();
+            queryer_connection_methods = new PeerInfo[qcms];
+            for(var i = 0; i > qcms; i++) {
+                queryer_connection_methods[i] = PeerInfo.deserialise(stream);
+            }
+
+            var acms = stream.read_byte();
+            answerer_connection_methods = new PeerInfo[acms];
+            for(var i = 0; i > qcms; i++) {
+                answerer_connection_methods[i] = PeerInfo.deserialise(stream);
+            }
+        }
+
+        public void serialise(DataOutputStream stream) throws Error {
+
+            instance_reference.serialise(stream);
+            stream.put_byte((uint8)queryer_connection_methods.length);
+            foreach(var method in queryer_connection_methods) {
+                method.serialise(stream);
+            }
+            stream.put_byte((uint8)answerer_connection_methods.length);
+            foreach(var method in answerer_connection_methods) {
+                method.serialise(stream);
+            }
+        }
+    }
+
+}

+ 35 - 0
src/lib/Util/ByteComposer.vala

@@ -2,7 +2,9 @@ namespace LibPeer.Util {
 
     public class ByteComposer {
 
+        
         private List<Bytes> components = new List<Bytes>();
+        public delegate void StreamComposer(DataOutputStream stream) throws Error;
 
         public ByteComposer add_byte(uint8 byte) {
             components.append(new Bytes({byte}));
@@ -33,6 +35,35 @@ namespace LibPeer.Util {
             return this;
         }
 
+        public ByteComposer add_with_stream(StreamComposer composer) throws Error {
+
+            var stream = new MemoryOutputStream(null, GLib.realloc, GLib.free);
+            var dos = new DataOutputStream(stream);
+            composer(dos);
+            dos.close();
+            uint8[] buffer = stream.steal_data();
+            buffer.length = (int)stream.get_data_size();
+            if(buffer.length > 0) {
+                add_byte_array(buffer);
+            }
+
+            return this;
+        }
+
+        public ByteComposer add_from_stream(InputStream stream, uint64 count) throws Error {
+            add_with_stream(s => {
+                var buffer = new uint8[count];
+                size_t read_size = 0;
+                size_t last_read = 0;
+                while(read_size != count && 0 != (last_read = stream.read(buffer))) {
+                    read_size += last_read;
+                    s.write(buffer[0:last_read]);
+                }
+            });
+
+            return this;
+        }
+
         public uint8[] to_byte_array() {
             uint8[] data = {};
             foreach (Bytes bytes in components) {
@@ -64,6 +95,10 @@ namespace LibPeer.Util {
             }
             return builder.str;
         }
+
+        public DataInputStream to_stream() {
+            return new DataInputStream(new MemoryInputStream.from_data(to_byte_array()));
+        }
     }
 
 }

+ 1 - 1
src/lib/Util/Streams.vala

@@ -1,6 +1,6 @@
 namespace LibPeer.Util {
 
-    public class StreamUtil {
+    internal class StreamUtil {
 
         public static DataInputStream get_data_input_stream(InputStream stream) {
             if(stream is DataInputStream) {

+ 8 - 0
src/lib/meson.build

@@ -64,6 +64,14 @@ sources += files('Protocols/AIP/Query.vala')
 sources += files('Protocols/AIP/InstanceInformation.vala')
 sources += files('Protocols/AIP/Answer.vala')
 sources += files('Protocols/AIP/Request.vala')
+sources += files('Protocols/GDP/GeneralDiscoveryProtocol.vala')
+sources += files('Protocols/GDP/QueryBase.vala')
+sources += files('Protocols/GDP/Query.vala')
+sources += files('Protocols/GDP/WrappedQuery.vala')
+sources += files('Protocols/GDP/QuerySummary.vala')
+sources += files('Protocols/GDP/Challenge.vala')
+sources += files('Protocols/GDP/Answer.vala')
+sources += files('Protocols/GDP/Application.vala')
 sources += files('Util/ByteComposer.vala')
 sources += files('Util/QueueCommand.vala')
 sources += files('Util/ThreadTimer.vala')

+ 81 - 0
src/toys/discoverer_improved/Discoverer.vala

@@ -0,0 +1,81 @@
+using LibPeer.Networks.Simulation;
+using LibPeer.Protocols.Mx2;
+using LibPeer.Protocols.Gdp;
+using LibPeer.Protocols.Stp;
+using LibPeer.Networks;
+
+using Gee;
+
+namespace Discoverer {
+
+    class DiscoverWorker : Object {
+
+        private Muxer muxer = new Muxer();
+        private Network network;
+        private GeneralDiscoveryProtocol discovery;
+        private GdpApplication discovery_app;
+        private StreamTransmissionProtocol stp;
+        private Instance app_instance;
+        private int id;
+
+        public DiscoverWorker(int id, Network net) throws Error, IOError {
+            this.id = id;
+            network = net;
+            network.bring_up();
+            print("Instansiate GDP\n");
+            discovery = new GeneralDiscoveryProtocol(muxer);
+            print("Add network\n");
+            discovery.add_network(network);
+            
+            print("Setup application instance\n");
+            app_instance = muxer.create_instance("discovery_toy");
+            app_instance.incoming_greeting.connect(greeted_by_peer);
+            discovery_app = discovery.add_application (app_instance);
+            discovery_app.query_answered.connect(query_answered);
+
+            var ch = discovery_app.create_app_challenge();
+            var ch2 = new Challenge.from_values(ch.public_key, ch.challenge_blob);
+            print(@"Solved own challenge: $(discovery_app.solve_app_challenge(ch2))\n");
+
+            print("Instansiate STP\n");
+            stp = new StreamTransmissionProtocol(muxer, app_instance);
+            stp.incoming_stream.connect(ingress_stream_established);
+            
+            print("Querying\n");
+            discovery.query_general(discovery_app);
+        }
+
+        private void query_answered(Answer answer) {
+            print("[GOAL!] I received a query answer!\n");
+            if(answer.query_summary.is_null_resource()) {
+                muxer.inquire(app_instance, answer.instance_reference, answer.connection_methods);
+            }
+        }
+
+        private void greeted_by_peer(InstanceReference origin) {
+            print("[GOAL!] I received a greeting!\n");
+            stp.initialise_stream(origin).established.connect(egress_stream_established);
+        }
+
+        private void egress_stream_established(OutputStream stream) {
+            print("[GOAL!] I established an egress stream to a peer!\n");
+            stream.write(new uint8[] { 13, 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!'});
+            stream.close();
+        }
+
+        private void ingress_stream_established(InputStream stream) {
+            print("[GOAL!] An ingress stream has been established!\n");
+            var message_size = new uint8[1];
+            stream.read(message_size);
+
+            var message = new uint8[message_size[0]];
+            stream.read(message);
+
+            stream.close();
+
+            var message_str = new LibPeer.Util.ByteComposer().add_byte_array(message).to_string();
+            print(@"[GOAL!] I received a message from a peer: '$(message_str)'\n");
+        }
+
+    }
+}

+ 24 - 0
src/toys/discoverer_improved/Main.vala

@@ -0,0 +1,24 @@
+using LibPeer.Networks.Simulation;
+
+namespace Discoverer {
+
+    class Main : Object {
+
+        public static int main(string[] args) {
+            print("Discoverer\n");
+            int count = int.parse(args[1]);
+
+            Conduit conduit = new Conduit();
+
+            DiscoverWorker[] pingas = new DiscoverWorker[count];
+            for (int i = 0; i < count; i++){
+                pingas[i] = new DiscoverWorker(i, conduit.get_interface (10, 10, 0.0f));
+            }
+
+            while(true) {};
+
+            return 0;
+        }
+    }
+
+}

+ 20 - 0
src/toys/discoverer_improved/MainIP.vala

@@ -0,0 +1,20 @@
+using LibPeer.Networks.IPv4;
+
+namespace Discoverer {
+
+    class Main : Object {
+
+        public static int main(string[] args) {
+            print("Discoverer (IPv4)\n");
+            string address = args[1];
+            uint16 port = (uint16)int.parse(args[2]);
+
+            var worker = new DiscoverWorker(0, new IPv4(address, port));
+
+            while(true) {}
+
+            return 0;
+        }
+    }
+
+}

+ 17 - 0
src/toys/discoverer_improved/meson.build

@@ -0,0 +1,17 @@
+dependencies = [
+    dependency('glib-2.0'),
+    dependency('gobject-2.0'),
+    dependency('gio-2.0'),
+    dependency('gee-0.8'),
+    libpeer_dep
+]
+
+sources = files('Main.vala')
+sources += files('Discoverer.vala')
+
+executable('discoverer2', sources, dependencies: dependencies)
+
+sources = files('MainIP.vala')
+sources += files('Discoverer.vala')
+
+executable('discoverer2_ipv4', sources, dependencies: dependencies)

+ 1 - 0
src/toys/meson.build

@@ -3,4 +3,5 @@ subdir('exponential_pinger')
 subdir('give_file')
 subdir('replyer')
 subdir('discoverer')
+subdir('discoverer_improved')
 subdir('hello_world')