Jelajahi Sumber

Let's get things online

Billy Barrow 4 tahun lalu
induk
melakukan
35de175634

+ 16 - 0
.vscode/launch.json

@@ -0,0 +1,16 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Debug",
+            "type": "gdb",
+            "request": "launch",
+            "target": "./toys/exponential_pinger/exponential_pinger",
+            "cwd": "${workspaceRoot}",
+            "valuesFormatting": "parseText"
+        }
+    ]
+}

+ 12 - 1
src/lib/Networks/Network.vala

@@ -16,8 +16,19 @@ namespace LibPeer.Networks {
 
         public abstract void advertise(InstanceReference instance_reference) throws IOError, Error;
 
-        public abstract void send(Bytes bytes, PeerInfo peer_info) throws IOError, Error;
+        public abstract void send(uint8[] bytes, PeerInfo peer_info) throws IOError, Error;
+
+        public void send_with_stream(PeerInfo peer_info, Serialiser serialiser) throws IOError, Error {
+            MemoryOutputStream stream = new MemoryOutputStream(null, GLib.realloc, GLib.free);
+            serialiser(stream);
+            stream.close();
+            uint8[] buffer = stream.steal_data();
+            buffer.length = (int)stream.get_data_size();
+            send(buffer, peer_info);
+        }
 
     }
 
+    public delegate void Serialiser(OutputStream stream) throws IOError, Error;
+
 }

+ 4 - 2
src/lib/Networks/Simulation/NetSim.vala

@@ -79,9 +79,9 @@ namespace LibPeer.Networks.Simulation {
             conduit.advertise(identifier, advertisement);
         }
     
-        public override void send(Bytes bytes, PeerInfo peer_info) throws IOError, Error {
+        public override void send(uint8[] bytes, PeerInfo peer_info) throws IOError, Error {
             NetSimPeerInfo info = (NetSimPeerInfo)peer_info;
-            conduit.send_packet(this.identifier, new Bytes(info.identifier), bytes);
+            conduit.send_packet(this.identifier, new Bytes(info.identifier), new Bytes(bytes));
         }
 
         internal void receive_data(Bytes origin, Bytes data) {
@@ -91,6 +91,8 @@ namespace LibPeer.Networks.Simulation {
             // Create the packet
             var packet = new Packet(peer_info, data);
 
+            print(@"NET: $(data.get(0)) $(data.get(1)) $(data.get(2))\n");
+
             // Add packet to queue
             packet_queue.push(new QueueCommand<Packet>.with_payload(packet));
         }

+ 1 - 1
src/lib/Networks/Simulation/Packet.vala

@@ -1,7 +1,7 @@
 
 namespace LibPeer.Networks.Simulation {
 
-    public class Packet {
+    internal class Packet {
         public Bytes data;
 
         public NetSimPeerInfo peer_info;

+ 108 - 0
src/lib/Protocols/MX2/Frame.vala

@@ -0,0 +1,108 @@
+using Sodium;
+using Gee;
+
+using LibPeer.Util;
+
+namespace LibPeer.Protocols.Mx2 {
+
+    public class Frame {
+
+        private const uint8[] MAGIC_NUMBER = {'M', 'X', '2'};
+
+        public InstanceReference destination { get; private set; }
+        
+        public InstanceReference origin { get; private set; }
+
+        public PathInfo via { get; private set; }
+
+        public uint8[] payload { get; private set; }
+
+        public Frame(InstanceReference destination, InstanceReference origin, PathInfo via, uint8[] payload) {
+            this.destination = destination;
+            this.origin = origin;
+            this.via = via;
+            this.payload = payload;
+        }
+
+        public void serialise(OutputStream stream, Instance instance)
+            throws IOError
+            requires (instance.reference.compare(origin) == 0)
+        {
+            // Magic number
+            stream.write(MAGIC_NUMBER);
+
+            // Write the destination key
+            destination.serialise(stream);
+
+            // Write the origin key
+            origin.serialise(stream);
+
+            // Write the via field
+            via.serialise(stream);
+
+            // Sign the data
+            uint8[] signed_payload = Asymmetric.Signing.sign(payload, instance.sign_private_key);
+
+            // Encrypt the signed payload
+            uint8[] encrypted_signed_payload = Asymmetric.Sealing.seal(signed_payload, destination.public_key);
+
+            print(@"TX_PAYLOAD: $(new ByteComposer().add_byte_array(payload).to_string())\n");
+
+            // Write the signed and encrypted payload
+            stream.write(encrypted_signed_payload);
+        }
+
+        public Frame.from_stream(InputStream stream, HashMap<InstanceReference, Instance> instances) throws IOError, Error{
+            // Read the magic number
+            uint8[] magic = new uint8[3];
+            stream.read(magic);
+
+            if(new Bytes(magic).compare(new Bytes(MAGIC_NUMBER)) != 0) {
+                throw new IOError.FAILED("Invalid magic number");
+            }
+
+            // Read the destination
+            destination = new InstanceReference.from_stream(stream);
+
+            // Read the origin
+            origin = new InstanceReference.from_stream(stream);
+
+            // Do we have an instance matching the destination of this frame?
+            if (!instances.has_key(destination)) {
+                throw new IOError.FAILED("Message matches no provided instances");
+            }
+
+            // Get the instance
+            Instance instance = instances.get(destination);
+
+            // Read the via field
+            via = new PathInfo.from_stream(stream);
+
+            // The remainder of the stream is the encrypted payload
+            uint8[] encrypted_signed_payload = new uint8[uint16.MAX];
+            size_t bytes_read;
+            stream.read_all(encrypted_signed_payload, out bytes_read);
+            encrypted_signed_payload.resize((int)bytes_read);
+
+            // Decrypt the payload
+            uint8[]? signed_payload = Asymmetric.Sealing.unseal(encrypted_signed_payload, instance.seal_public_key, instance.seal_private_key);
+
+            if (signed_payload == null) {
+                throw new IOError.FAILED("Payload could not be decrypted");
+            }
+
+            // Verify the signature and get plaintext message
+            uint8[]? payload = Asymmetric.Signing.verify(signed_payload, origin.verification_key);
+                                
+            print(@"RX_PAYLOAD: $(new ByteComposer().add_byte_array(payload).to_string())\n");
+
+            if (payload == null) {
+                throw new IOError.FAILED("Payload signature is invalid");
+            }
+
+            this.payload = payload;
+        }
+
+    }
+
+}

+ 52 - 0
src/lib/Protocols/MX2/Inquiry.vala

@@ -0,0 +1,52 @@
+using LibPeer.Util;
+
+namespace LibPeer.Protocols.Mx2 {
+
+    public class Inquiry : Object {
+
+        public Bytes id { get; private set; }
+
+        public InstanceReference target { get; private set; }
+
+        public signal void complete(bool instance_found, int delay);
+
+        private Timer delay_timer;
+
+        private ThreadTimer timeout_thread;
+
+        public Inquiry(InstanceReference target, int timeout = 10000) {
+            uint8[] uuid = new uint8[16];
+            UUID.generate_random(uuid);
+            id = new Bytes(uuid);
+
+            this.target = target;
+            
+            delay_timer = new Timer();
+            delay_timer.start();
+
+            timeout_thread = new ThreadTimer(timeout, () => {
+                delay_timer.stop();
+                ulong microseconds;
+                complete(false, (int)(delay_timer.elapsed(out microseconds) * 1000));
+            });
+
+            timeout_thread.start();
+        }
+
+        internal int response_received() {
+            if (!timeout_thread.running) {
+                return 0;
+            }
+
+            timeout_thread.cancel();
+            delay_timer.stop();
+
+            ulong microseconds;
+            int milliseconds = (int)(delay_timer.elapsed(out microseconds) * 1000);
+            complete(true, milliseconds);
+            return milliseconds;
+        }
+
+    }
+
+}

+ 43 - 0
src/lib/Protocols/MX2/Instance.vala

@@ -0,0 +1,43 @@
+using Sodium;
+using Gee;
+
+namespace LibPeer.Protocols.Mx2 {
+
+    public class Instance {
+
+        public string application_namespace { get ; protected set; }
+
+        public uint8[] seal_public_key { get; protected set; }
+        public uint8[] seal_private_key { get; protected set; }
+
+        public uint8[] sign_public_key { get; protected set; }
+        public uint8[] sign_private_key { get; protected set; }
+
+        public HashSet<InstanceReference> reachable_peers { get; default = new HashSet<InstanceReference>((m) => m.hash(), (a, b) => a.compare(b) == 0); }
+
+        public signal void incoming_payload(Packet packet);
+        public signal void incoming_greeting(InstanceReference origin);
+
+        public InstanceReference reference {
+            owned get {
+                return new InstanceReference(sign_public_key, seal_public_key);
+            }
+        }
+
+        public Instance(string app_namespace) {
+            application_namespace = app_namespace;
+
+            seal_private_key = new uint8[Asymmetric.Sealing.SECRET_KEY_BYTES];
+            seal_public_key = new uint8[Asymmetric.Sealing.PUBLIC_KEY_BYTES];
+            Asymmetric.Sealing.generate_keypair(seal_public_key, seal_private_key);
+
+            sign_private_key = new uint8[Asymmetric.Signing.SECRET_KEY_BYTES];
+            sign_public_key = new uint8[Asymmetric.Signing.PUBLIC_KEY_BYTES];
+            Asymmetric.Signing.generate_keypair(sign_public_key, sign_private_key);
+
+
+        }
+
+    }
+
+}

+ 15 - 0
src/lib/Protocols/MX2/InstanceAccessInfo.vala

@@ -0,0 +1,15 @@
+using LibPeer.Networks;
+
+namespace LibPeer.Protocols.Mx2 {
+
+    internal struct InstanceAccessInfo {
+
+        public Network network;
+
+        public PeerInfo peer_info;
+
+        public PathInfo path_info;
+
+    }
+
+}

+ 18 - 3
src/lib/Protocols/MX2/InstanceReference.vala

@@ -3,8 +3,8 @@ namespace LibPeer.Protocols.Mx2 {
 
     public class InstanceReference {
 
-        protected uint8[] verification_key;
-        protected uint8[] public_key;
+        public uint8[] verification_key { get; protected set; }
+        public uint8[] public_key { get; protected set; }
 
         public InstanceReference(uint8[] verification_key, uint8[] public_key) 
         requires (verification_key.length == 32)
@@ -19,7 +19,7 @@ namespace LibPeer.Protocols.Mx2 {
             stream.read(verification_key);
 
             public_key = new uint8[32];
-            stream.read(verification_key);
+            stream.read(public_key);
         }
 
         public void serialise(OutputStream stream) throws IOError {
@@ -27,6 +27,21 @@ namespace LibPeer.Protocols.Mx2 {
             stream.write(public_key);
         }
 
+        private Bytes combined_bytes () {
+            uint8[] combined = new uint8[64];
+            MemoryOutputStream stream = new MemoryOutputStream(combined);
+            serialise(stream);
+            return new Bytes(combined);
+        }
+
+        public uint hash() {
+            return combined_bytes().hash();
+        }
+
+        public int compare(InstanceReference other) {
+            return combined_bytes().compare(other.combined_bytes());
+        }
+
     }
 
 }

+ 254 - 0
src/lib/Protocols/MX2/Muxer.vala

@@ -0,0 +1,254 @@
+using LibPeer.Networks;
+using LibPeer.Util;
+
+using Gee;
+
+namespace LibPeer.Protocols.Mx2 {
+
+    public class Muxer {
+
+        private const uint8 PACKET_INQUIRE = 5;
+        private const uint8 PACKET_GREET = 6;
+        private const uint8 PACKET_PAYLOAD = 22;
+
+        private const int FALLBACK_PING_VALUE = 120000;
+        
+        private HashMap<Bytes, HashSet<Network>> networks = new HashMap<Bytes, HashSet<Network>>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+
+        private HashMap<InstanceReference, Instance> instances = new HashMap<InstanceReference, Instance>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+
+        private HashMap<InstanceReference, InstanceAccessInfo?> remote_instance_mapping = new HashMap<InstanceReference, InstanceAccessInfo>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+
+        private HashMap<Bytes, Inquiry> inquiries = new HashMap<Bytes, Inquiry>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+
+        private HashMap<InstanceReference, int> pings = new HashMap<InstanceReference, int>((a) => a.hash(), (a, b) => a.compare(b) == 0);
+
+        
+        public void register_network(Network network) {
+            // Get the network identifier
+            Bytes network_identifier = network.get_network_identifier();
+
+            // Do we have a set for this network type yet?
+            if (!networks.has_key(network_identifier)) {
+                // No, add one
+                networks.set(network_identifier, new HashSet<Network>());
+            }
+
+            // Get the network set
+            HashSet<Network> network_set = networks.get(network_identifier);
+
+            // Do we have this network already?
+            if (network_set.contains(network)) {
+                // Yes, nothing to do
+                return;
+            }
+
+            // Add the network to the set
+            network_set.add(network);
+
+            // Handle incoming data
+            network.incoming_receiption.connect((receiption) => handle_receiption(receiption));
+        }
+
+        public Instance create_instance(string application_namespace) {
+            // Create the instance
+            Instance instance = new Instance(application_namespace);
+
+            // Save the instance to the map
+            instances.set(instance.reference, instance);
+
+            // Return the instance
+            return instance;
+        }
+
+        public Inquiry inquire(Instance instance, InstanceReference destination, GLib.List<PeerInfo> peers) throws IOError, Error {
+            // Create an inquiry
+            var inquiry = new Inquiry(destination);
+            inquiries.set(inquiry.id, inquiry);
+
+            int packets = 0;
+
+            // Loop over each peer to try
+            foreach (PeerInfo peer in peers) {
+                // Get peer network identifier
+                Bytes network_identifier = peer.get_network_identifier();
+                // Do we have the network associated with the peer info?
+                if (!networks.has_key(network_identifier)) {
+                    // We don't have this peer's network
+                    continue;
+                }
+
+                // Loop over the networks that match the type
+                foreach (Network network in networks.get(network_identifier)) {
+                    // Create the inquire packet
+                    uint8[] packet = new ByteComposer()
+                        .add_byte(PACKET_INQUIRE)
+                        .add_bytes(inquiry.id)
+                        .add_char_array(instance.application_namespace.to_utf8())
+                        .to_byte_array();
+
+                    // Create a frame containing an inquire packet
+                    var frame = new Frame(destination, instance.reference, new PathInfo.empty(), packet);
+
+                    // Send using the network and peer info
+                    network.send_with_stream(peer, (stream) => frame.serialise(stream, instance));
+                }
+            }
+
+            return inquiry;
+        }
+
+        // TODO: Add Inquire via paths
+
+        public PeerInfo? get_peer_info_for_instance(InstanceReference instance) {
+            if (remote_instance_mapping.has_key(instance)) {
+                return remote_instance_mapping.get(instance).peer_info;
+            }
+            return null;
+        }
+
+        public Network? get_target_network_for_instance(InstanceReference instance) {
+            if(remote_instance_mapping.has_key(instance)) {
+                return remote_instance_mapping.get(instance).network;
+            }
+            return null;
+        }
+
+        public void send(Instance instance, InstanceReference destination, uint8[] data) throws IOError, Error {
+            uint8[] payload = new ByteComposer()
+                .add_byte(PACKET_PAYLOAD)
+                .add_byte_array(data)
+                .to_byte_array();
+
+            send_packet(instance, destination, payload);
+        }
+
+        // TODO: Send
+
+        public int suggested_timeout_for_instance(InstanceReference instance) {
+            if(pings.has_key(instance)) {
+                return pings.get(instance) * 2;
+            }
+            return FALLBACK_PING_VALUE;
+        }
+
+        protected void send_packet(Instance instance, InstanceReference destination, uint8[] payload) throws IOError, Error {
+            // Do we know the destination instance?
+            if(!remote_instance_mapping.has_key(destination)) {
+                // No, throw an error
+                throw new IOError.HOST_NOT_FOUND("No knwon way to reach the specified instance");
+            }
+
+            // Get access information
+            InstanceAccessInfo access_info = remote_instance_mapping.get(destination);
+
+            // Create a frame
+            Frame frame = new Frame(destination, instance.reference, access_info.path_info, payload);
+
+            // Send the frame over the network
+            access_info.network.send_with_stream(access_info.peer_info, (stream) => frame.serialise(stream, instance));
+        }
+
+        protected void handle_receiption(Receiption receiption) throws Error, IOError {
+            // Read the incoming frame
+            Frame frame = new Frame.from_stream(receiption.stream, instances);
+
+            // Get the instance
+            Instance instance = instances.get(frame.destination);
+
+            // Read the packet type
+            uint8 packet_type = frame.payload[0];
+
+            // Determine what to do
+            switch (packet_type) {
+                case PACKET_INQUIRE:
+                    handle_inquire(receiption, frame, instance);
+                    break;
+                
+                case PACKET_GREET:
+                    handle_greet(receiption, frame, instance);
+                    break;
+
+                case PACKET_PAYLOAD:
+                    handle_payload(receiption, frame, instance);
+                    break;
+
+                default:
+                    throw new IOError.INVALID_DATA("Invalid packet type");
+            }
+            
+        }
+
+        protected void handle_inquire(Receiption receiption, Frame frame, Instance instance) throws Error {
+            // Next 16 bytes of packet is the inquiriy ID
+            uint8[] inquiry_id = frame.payload[1:17];
+
+            // Rest of the packet indicates the desired application namespace
+            string application_namespace = new ByteComposer()
+                .add_byte_array(frame.payload[17:frame.payload.length])
+                .to_string();
+
+            print(@"NAMESPACE: $(application_namespace)\n");
+
+            // Does the application namespace match the instance's
+            if (instance.application_namespace == application_namespace) {
+                // Yes, save this instance's information locally for use later
+                remote_instance_mapping.set(frame.origin, InstanceAccessInfo() { 
+                    network = receiption.network,
+                    peer_info = receiption.peer_info,
+                    path_info = frame.via.return_path
+                });
+
+                // Create the greeting
+                uint8[] greeting = new ByteComposer()
+                    .add_byte(PACKET_GREET)
+                    .add_byte_array(inquiry_id)
+                    .to_byte_array();
+
+                // Send the greeting
+                send_packet(instance, frame.origin, greeting);
+            }
+
+        }
+
+        protected void handle_greet(Receiption receiption, Frame frame, Instance instance) throws Error {
+            // We have received a greeting!
+            // Have we received one from this instance before?
+            if (!remote_instance_mapping.has_key(frame.origin)) {
+                // No, this is the first (and therefore least latent) method of reaching this instance
+                remote_instance_mapping.set(frame.origin, InstanceAccessInfo() { 
+                    network = receiption.network,
+                    peer_info = receiption.peer_info,
+                    path_info = frame.via.return_path
+                });
+
+                // Get the inquiry id
+                Bytes inquiry_id = new Bytes(frame.payload[1:17]);
+
+                // Determine the ping
+                int ping = FALLBACK_PING_VALUE;
+                if (inquiries.has_key(inquiry_id)) {
+                    ping = inquiries.get(inquiry_id).response_received();
+                }
+
+                // Save the ping
+                pings.set(frame.origin, ping);
+            }
+
+            // Does the instance know that this is now a reachable peer?
+            if (!instance.reachable_peers.contains(frame.origin)) {
+                // No, notify it
+                instance.reachable_peers.add(frame.origin);
+                instance.incoming_greeting(frame.origin);
+            }
+        }
+
+        protected void handle_payload(Receiption receiption, Frame frame, Instance instance) throws Error {
+            // This is a payload for the next layer to handle, pass it up.
+            MemoryInputStream stream = new MemoryInputStream.from_data(frame.payload[1:frame.payload.length]);
+            instance.incoming_payload(new Packet(frame.origin, frame.destination, stream));
+        }
+
+    }
+
+}

+ 19 - 0
src/lib/Protocols/MX2/Packet.vala

@@ -0,0 +1,19 @@
+
+namespace LibPeer.Protocols.Mx2 {
+
+    public class Packet {
+
+        public InstanceReference origin { get ; protected set; }
+
+        public InstanceReference destination { get ; protected set; }
+
+        public InputStream stream { get; protected set; }
+
+        public Packet(InstanceReference origin, InstanceReference destination, InputStream stream) {
+            this.origin = origin;
+            this.destination = destination;
+            this.stream = stream;
+        }
+    }
+
+}

+ 48 - 0
src/lib/Protocols/MX2/PathInfo.vala

@@ -0,0 +1,48 @@
+
+namespace LibPeer.Protocols.Mx2 {
+
+    public class PathInfo {
+
+        public unowned List<InstanceReference> repeaters { get; protected set; }
+
+        public PathInfo return_path {
+            owned get {
+                var path = repeaters.copy_deep((m) => m);
+                path.reverse();
+                return new PathInfo(path);
+            }
+        }
+
+        public PathInfo(List<InstanceReference> repeaters) {
+            this.repeaters = repeaters;
+        }
+
+        public void serialise(OutputStream stream) throws IOError {
+            // Write number of repeaters
+            stream.write({(uint8)repeaters.length()});
+
+            // Write the repeaters
+            foreach (var repeater in repeaters) {
+                repeater.serialise(stream);
+            }
+        }
+
+        public PathInfo.from_stream(InputStream stream) throws IOError, Error {
+            // Get number of repeaters
+            uint8 repeater_count = stream.read_bytes(1).get(0);
+
+            // Create list
+            repeaters = new List<InstanceReference>();
+
+            // Read repeaters
+            for (uint8 i = 0; i < repeater_count; i++) {
+                repeaters.append(new InstanceReference.from_stream(stream));
+            }
+        }
+
+        public PathInfo.empty() {
+            this(new List<InstanceReference>());
+        }
+    }
+
+}

+ 18 - 0
src/lib/Protocols/MX2/PathStrategy.vala

@@ -0,0 +1,18 @@
+using LibPeer.Networks;
+
+namespace LibPeer.Protocols.Mx2 {
+
+    internal class PathStrategy {
+
+        public PathInfo path { get; private set; }
+
+        public PeerInfo first_hop { get; private set; }
+
+        public PathStrategy(PathInfo path, PeerInfo first_hop) {
+            this.path = path;
+            this.first_hop = first_hop;
+        }
+
+    }
+
+}

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

@@ -0,0 +1,47 @@
+namespace LibPeer.Util {
+
+    public class ByteComposer {
+
+        private List<Bytes> components = new List<Bytes>();
+
+        public ByteComposer add_byte(uint8 byte) {
+            components.append(new Bytes({byte}));
+            return this;
+        }
+
+        public ByteComposer add_bytes(Bytes bytes) {
+            components.append(bytes);
+            return this;
+        }
+
+        public ByteComposer add_byte_array(uint8[] bytes) {
+            components.append(new Bytes(bytes));
+            return this;
+        }
+
+        public ByteComposer add_char_array(char[] chars) {
+            components.append(new Bytes((uint8[]) chars));
+            return this;
+        }
+
+        public uint8[] to_byte_array() {
+            uint8[] data = {};
+            foreach (Bytes bytes in components) {
+                foreach (uint8 byte in bytes.get_data()) {
+                    data += byte;
+                }
+            }
+            return data;
+        }
+
+        public Bytes to_bytes() {
+            return new Bytes(to_byte_array());
+        }
+
+        public string to_string(bool null_terminate = true) {
+            add_byte(0);
+            return (string)to_byte_array();
+        }
+    }
+
+}

+ 2 - 2
src/lib/Util/QueueCommand.vala

@@ -1,11 +1,11 @@
 namespace LibPeer.Util {
 
-    public enum QueueControl {
+    internal enum QueueControl {
         Payload,
         Stop,
     }
 
-    public class QueueCommand<T> {
+    internal class QueueCommand<T> {
 
         public T payload;
 

+ 51 - 0
src/lib/Util/ThreadTimer.vala

@@ -0,0 +1,51 @@
+
+namespace LibPeer.Util {
+
+    internal class ThreadTimer {
+
+        public bool running {
+            get {
+                return has_started && !has_been_canceled && !has_elapsed;
+            }
+        }
+
+        private bool has_been_canceled = false;
+        private bool has_elapsed = false;
+        private bool has_started = false;
+        
+        private int timeout;
+        private TimerFunc timer_func;
+    
+
+        public ThreadTimer(int timeout, TimerFunc timer_func) {
+            this.timeout = timeout;
+            this.timer_func = timer_func;
+        }
+
+        public void start() {
+            if(this.has_started) {
+                return;
+            }
+
+            this.has_started = true;
+            ThreadFunc<bool> runner = () => {
+                Thread<bool>.usleep(timeout * 1000);
+                if(has_been_canceled) {
+                    return false;
+                }
+                has_elapsed = true;
+                timer_func();
+                return true;
+            };
+
+            new Thread<bool>(@"$(timeout)ms timer thread", runner);
+        }
+
+        public void cancel() {
+            has_been_canceled = true;
+        }
+    }
+
+    internal delegate void TimerFunc();
+
+}

+ 13 - 1
src/lib/meson.build

@@ -9,7 +9,9 @@ dependencies = [
     dependency('gee-0.8'),
     meson.get_compiler('vala').find_library('posix'),
     meson.get_compiler('vala').find_library('uuid', dirs: vapi_dir),
-    meson.get_compiler('c').find_library('uuid')
+    meson.get_compiler('c').find_library('uuid'),
+    meson.get_compiler('vala').find_library('libsodium', dirs: vapi_dir),
+    meson.get_compiler('c').find_library('sodium')
 ]
 
 sources = files('Networks/Advertisement.vala')
@@ -21,8 +23,18 @@ sources += files('Networks/Simulation/NetSimPeerInfo.vala')
 sources += files('Networks/Simulation/Conduit.vala')
 sources += files('Networks/Simulation/NetSim.vala')
 sources += files('Networks/Simulation/Packet.vala')
+sources += files('Protocols/MX2/Muxer.vala')
+sources += files('Protocols/MX2/Frame.vala')
+sources += files('Protocols/MX2/Inquiry.vala')
+sources += files('Protocols/MX2/Instance.vala')
+sources += files('Protocols/MX2/InstanceAccessInfo.vala')
 sources += files('Protocols/MX2/InstanceReference.vala')
+sources += files('Protocols/MX2/Packet.vala')
+sources += files('Protocols/MX2/PathInfo.vala')
+sources += files('Protocols/MX2/PathStrategy.vala')
+sources += files('Util/ByteComposer.vala')
 sources += files('Util/QueueCommand.vala')
+sources += files('Util/ThreadTimer.vala')
 
 libpeer = library('peer', sources, dependencies: dependencies)
 libpeer_dep = declare_dependency(link_with: libpeer, include_directories: include_directories('.'))

+ 237 - 0
src/lib/vapi/libsodium.vapi

@@ -0,0 +1,237 @@
+/* Vala Bindings for LibSodium
+ * Copyright (c) 2020 Billy Barrow <billyb@pcthingz.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+ [CCode (cheader_filename = "sodium.h", lower_case_cprefix = "sodium_")]
+ namespace Sodium {
+ 
+   namespace Random {
+     [CCode (cname = "randombytes_SEEDBYTES")]
+     public const size_t SEED_BYTES;
+   
+     [CCode (cname = "randombytes_random")]
+     public uint32 random();
+   
+     [CCode (cname = "randombytes_uniform")]
+     public uint32 random_uniform(uint32 upper_bound);
+   
+     [CCode (cname = "randombytes_buf")]
+     public void random_bytes(uint8[] buffer);
+   
+     [CCode (cname = "randombytes_buf_deterministic")]
+     public void random_bytes_deterministic(uint8[] buffer, uint8[] seed);
+   }
+ 
+   namespace Symmetric {
+     [CCode (cname = "crypto_secretbox_KEYBYTES")]
+     public const size_t KEY_BYTES;
+ 
+     [CCode (cname = "crypto_secretbox_NONCEBYTES")]
+     public const size_t NONCE_BYTES;
+ 
+     [CCode (cname = "crypto_secretbox_MACBYTES")]
+     public const size_t MAC_BYTES;
+ 
+     [CCode (cname = "crypto_secretbox_keygen")]
+     private void key_gen([CCode (array_length = false)]uint8[] key);
+ 
+     public uint8[] generate_key() {
+       uint8[KEY_BYTES] key = new uint8[KEY_BYTES];
+       key_gen(key);
+       return key;
+     }
+ 
+     [CCode (cname = "crypto_secretbox_easy")]
+     private void secretbox(
+       [CCode (array_length = false)]uint8[] ciphertext,
+       uint8[] message,
+       [CCode (array_length = false)]uint8[] nonce,
+       [CCode (array_length = false)]uint8[] key
+     );
+ 
+     public uint8[] encrypt(uint8[] message, uint8[] key, uint8[] nonce)
+       requires (key.length == KEY_BYTES) 
+       requires (nonce.length == NONCE_BYTES)
+     {
+       // Initialise array for ciphertext
+       size_t ciphertext_size = MAC_BYTES + message.length;
+       uint8[ciphertext_size] ciphertext = new uint8[ciphertext_size];
+ 
+       // Encrypt
+       secretbox(ciphertext, message, nonce, key);
+ 
+       // Return ciphertext
+       return ciphertext;
+     }
+ 
+     [CCode (cname = "crypto_secretbox_open_easy")]
+     private int secretbox_open(
+       [CCode (array_length = false)]uint8[] message,
+       uint8[] ciphertext,
+       [CCode (array_length = false)]uint8[] nonce,
+       [CCode (array_length = false)]uint8[] key
+     );
+ 
+     public uint8[]? decrypt(uint8[] ciphertext, uint8[] key, uint8[] nonce)
+       requires (ciphertext.length > MAC_BYTES)
+       requires (key.length == KEY_BYTES) 
+       requires (nonce.length == NONCE_BYTES)
+     {
+       // Initialise array for message
+       size_t message_size = ciphertext.length - MAC_BYTES;
+       uint8[message_size] message = new uint8[message_size];
+ 
+       // Decrypt
+       int status = secretbox_open(message, ciphertext, nonce, key);
+ 
+       // Did it work?
+       if(status != 0) {
+         // No, return null
+         return null;
+       }
+ 
+       return message;
+     }
+   }
+   
+   namespace Asymmetric {
+ 
+     namespace Signing {
+ 
+         [CCode (cname = "crypto_sign_PUBLICKEYBYTES")]
+         public const size_t PUBLIC_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_sign_SECRETKEYBYTES")]
+         public const size_t SECRET_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_sign_BYTES")]
+         public const size_t MAX_HEADER_BYTES;
+ 
+         [CCode (cname = "crypto_sign_keypair")]
+         public void generate_keypair(
+             [CCode (array_length = false)]uint8[] public_key,
+             [CCode (array_length = false)]uint8[] secret_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+             requires (secret_key.length == SECRET_KEY_BYTES);
+             
+         [CCode (cname = "crypto_sign")]
+         private void sign_message(
+             [CCode (array_length = false)] uint8[] signed_message,
+             out int signature_length,
+             uint8[] message,
+             [CCode (array_length = false)] uint8[] secret_key
+         );
+ 
+         public uint8[] sign(
+             uint8[] message,
+             uint8[] secret_key)
+             requires (secret_key.length == SECRET_KEY_BYTES)
+         {
+             int signature_length;
+             uint8[] signed_message = new uint8[MAX_HEADER_BYTES + message.length];
+             sign_message(signed_message, out signature_length, message, secret_key);
+             signed_message.resize(signature_length);
+ 
+             return signed_message;
+         }
+ 
+         [CCode (cname = "crypto_sign_open")]
+         private int sign_open(
+             [CCode (array_length = false)] uint8[] message,
+             out int message_length,
+             uint8[] signed_message,
+             [CCode (array_length = false)] uint8[] public_key
+         );
+ 
+         public uint8[]? verify(
+             uint8[] signed_message,
+             uint8[] public_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+         {
+             int message_length;
+             uint8[] message = new uint8[signed_message.length];
+             if(sign_open(message, out message_length, signed_message, public_key) != 0) {
+                 return null;
+             }
+             message.resize(message_length);
+ 
+             return message;
+         }
+ 
+     }
+ 
+     namespace Sealing {
+ 
+         [CCode (cname = "crypto_box_PUBLICKEYBYTES")]
+         public const size_t PUBLIC_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_box_SECRETKEYBYTES")]
+         public const size_t SECRET_KEY_BYTES;
+ 
+         [CCode (cname = "crypto_box_SEALBYTES")]
+         public const size_t HEADER_BYTES;
+ 
+         [CCode (cname = "crypto_box_keypair")]
+         public void generate_keypair(
+             [CCode (array_length = false)]uint8[] public_key,
+             [CCode (array_length = false)]uint8[] secret_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+             requires (secret_key.length == SECRET_KEY_BYTES);
+ 
+         [CCode (cname = "crypto_box_seal")]
+         private void seal_message(
+             [CCode (array_length = false)] uint8[] ciphertext,
+             uint8[] message,
+             [CCode (array_length = false)] uint8[] public_key
+         );
+ 
+         public uint8[] seal(uint8[] message, uint8[] public_key)
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+         {
+             uint8[] ciphertext = new uint8[HEADER_BYTES + message.length];
+             seal_message(ciphertext, message, public_key);
+             return ciphertext;
+         }
+ 
+         [CCode (cname = "crypto_box_seal_open")]
+         private int seal_open(
+             [CCode (array_length = false)] uint8[] message,
+             uint8[] ciphertext,
+             [CCode (array_length = false)] uint8[] public_key,
+             [CCode (array_length = false)] uint8[] secret_key
+         );
+ 
+         public uint8[]? unseal(
+             uint8[] ciphertext,
+             uint8[] public_key,
+             uint8[] secret_key) 
+             requires (public_key.length == PUBLIC_KEY_BYTES)
+             requires (secret_key.length == SECRET_KEY_BYTES)
+             requires (ciphertext.length > HEADER_BYTES)
+         {
+             uint8[] message = new uint8[ciphertext.length - HEADER_BYTES];
+             if(seal_open(message, ciphertext, public_key, secret_key) != 0){
+                 return null;
+             }
+             return message;
+         }
+         
+     }
+ 
+   }
+   
+ 
+ }

+ 8 - 1
src/toys/exponential_pinger/Main.vala

@@ -5,10 +5,17 @@ namespace ExponentialPinger {
     class Main : Object {
 
         public static int main(string[] args) {
-            stderr.printf("Exponential Pinger\n");
+            print("Exponential Pinger\n");
 
             Conduit conduit = new Conduit();
 
+            Pinger[] pingas = new Pinger[10];
+            for (int i = 0; i < 10; i++){
+                pingas[i] = new Pinger(conduit);
+            }
+
+            while(true) {};
+
             return 0;
         }
     }

+ 53 - 1
src/toys/exponential_pinger/Pinger.vala

@@ -1,10 +1,62 @@
 using LibPeer.Networks.Simulation;
+using LibPeer.Protocols.Mx2;
+using LibPeer.Networks;
+
+using Gee;
 
 namespace ExponentialPinger {
 
     class Pinger : Object {
 
-        
+        private Muxer muxer = new Muxer();
+        private Network network;
+        private Instance instance;
+        private HashSet<InstanceReference> peers = new HashSet<InstanceReference>((m) => m.hash(), (a, b) => a.compare(b) == 0);
+
+        public Pinger(Conduit conduit) throws Error, IOError {
+            network = conduit.get_interface();
+            network.bring_up();
+            muxer.register_network(network);
+            instance = muxer.create_instance("ExpontntialPinger");
+            
+            instance.incoming_greeting.connect((origin) => rx_greeting(origin));
+            instance.incoming_payload.connect((packet) => rx_data(packet));
+            network.incoming_advertisment.connect((adv) => rx_advertisement(adv));
+
+            network.advertise(instance.reference);
+            print("A pinger has been spawned\n");
+        }
+
+        private void rx_advertisement(Advertisement adv) throws Error, IOError {
+            lock (peers) {
+                if(!peers.contains(adv.instance_reference)) {
+                    var peer_info = new GLib.List<PeerInfo>();
+                    peer_info.append(adv.peer_info);
+                    muxer.inquire(instance, adv.instance_reference, peer_info);
+                }
+            }
+        }
+
+        private void rx_greeting(InstanceReference origin) throws Error, IOError {
+            lock (peers) {
+                peers.add(origin);
+            }
+            muxer.send(instance, origin, "Hello World!".data);
+        }
+
+        private void rx_data(Packet packet) throws Error, IOError {
+            lock (peers) {
+                peers.add(packet.origin);
+                network.advertise(instance.reference);
+                print(@"RX DATA, I have $(peers.size) peers\n");
+            }
+
+            uint8[] data = new uint8[13];
+            packet.stream.read(data);
+            foreach (InstanceReference peer in peers) {
+                muxer.send(instance, peer, data);
+            }
+        }
 
     }
 

+ 1 - 0
src/toys/exponential_pinger/meson.build

@@ -2,6 +2,7 @@ dependencies = [
     dependency('glib-2.0'),
     dependency('gobject-2.0'),
     dependency('gio-2.0'),
+    dependency('gee-0.8'),
     libpeer_dep
 ]