1 module libsweatyballs.zwitch.core;
2 
3 import libsweatyballs.engine.core : Engine;
4 import core.thread : Thread;
5 import crypto.rsa : RSA;
6 import libsweatyballs.zwitch.neighbor : Neighbor;
7 import core.sync.mutex : Mutex;
8 import std.string : cmp;
9 import std.conv : to;
10 import gogga;
11 import std.socket;
12 import libsweatyballs.router.table : Route;
13 import libsweatyballs.link.message.core;
14 import std.array : array;
15 import google.protobuf;
16 
17 /**
18 * Switch
19 *
20 * Manages session (far) and neighbour comms
21 *
22 * You send and receive data here. The switch asks router for routes
23 * and then sends stuff on the next-hop to them (if direct) or via them
24 * (indirect/routed-through)
25 */
26 public final class Switch : Thread
27 {
28     private Engine engine;
29     // private Session[] sessions;
30 
31     /**
32     * Neighboring nodes
33     */
34     private Neighbor[] neighbors;
35     private Mutex neighborsLock;
36 
37     /**
38     * Neighbor communications
39     *
40     * Node-to-node
41     */
42     private Socket neighborSocket;
43 
44     this(Engine engine)
45     {
46         /* Set the thread's worker function */
47         super(&worker);
48 
49         this.engine = engine;
50    
51         /* Initialize locks */
52         initMutexes();
53     }
54 
55     /**
56     * Initializes all the mutexes
57     */
58     private void initMutexes()
59     {
60         neighborsLock = new Mutex();
61     }
62 
63     public Neighbor[] getNeighbors()
64     {
65         Neighbor[] copy;
66 
67         neighborsLock.lock();
68         foreach(Neighbor neighbor; neighbors)
69         {
70             copy ~= neighbor;
71         }
72         neighborsLock.unlock();
73 
74         return copy;
75     }
76 
77     public void addNeighbor(Neighbor neighbor)
78     {
79         /* Lock the neighbors table */
80         neighborsLock.lock();
81 
82         /* Add the route (only if it doesn't already exist) */
83         foreach(Neighbor cNeighbour; neighbors)
84         {
85             /* If the neighbor is already known of */
86             /* TODO: Add neighbor expirations */
87             if(cmp(cNeighbour.getIdentity(), neighbor.getIdentity()) == 0)
88             {
89                 goto no_add_neighbor;
90             }
91         }
92 
93         neighbors ~= neighbor;
94         gprintln("NeighborDB: Added a new neighbor "~to!(string)(neighbor));
95         
96         no_add_neighbor:
97 
98         /* Unlock the neighbors table */
99         neighborsLock.unlock();
100     }
101 
102     private void initSockets()
103     {
104         neighborSocket = new Socket(AddressFamily.INET6, SocketType.DGRAM, ProtocolType.UDP);
105     }
106 
107     private void worker()
108     {
109         /* TODO: Implement me */
110         while(true)
111         {
112 
113         }
114     }
115 
116     public void launch()
117     {
118         start();
119     }
120 
121     public Neighbor isNeighbour(string address)
122     {
123         Neighbor match;
124 
125         Neighbor[] neighbors = getNeighbors();
126         foreach(Neighbor neighbor; neighbors)
127         {
128             if(cmp(neighbor.getIdentity(), address) == 0)
129             {
130                 match = neighbor;
131                 break;
132             }
133         }
134 
135         return match;
136     }
137 
138 
139     private Packet constructPacket(string toAddress, string fromAddress, byte[] data)
140     {
141         /* The final message */
142         Packet packet = new Packet();
143         packet.fromKey = fromAddress;
144         packet.toKey = toAddress;
145         packet.siganture = "not yet implemented";
146         packet.ttl = 64;
147         /* TODO: Generate signature */
148 
149         /* Encrypt the payload to `toAddress` (final destination) */
150         ubyte[] encryptedPayload = RSA.encrypt(toAddress, cast(ubyte[])data);
151         packet.payload = encryptedPayload;
152 
153         return packet;
154     }
155 
156     // private LinkMessage constructLinkMessage(byte[] data, LinkMessageType type, string )
157 
158     /**
159     * Send a packet
160     *
161     * Send a packet containing `data` to node at `address`
162     * from `us`
163     */
164     public void sendPacket(string address, byte[] data)
165     {
166         /* Construct the packet */
167         Packet packet = constructPacket(address, engine.getRouter().getIdentity().getKeys().publicKey, data);
168 
169         /* LinkMessage */
170         LinkMessage linkMsg = new LinkMessage();
171         linkMsg.type = LinkMessageType.PACKET;
172         linkMsg.publicKey = engine.getRouter().getIdentity().getKeys().publicKey;
173         linkMsg.signature = "not yet implemented";
174         linkMsg.payload = cast(ubyte[])array(toProtobuf(packet));
175         
176         /* Next-hop (for delivery), this is either a router or destination direct */
177         Neighbor nextHop;
178 
179 
180         /* Find out whether `address` is local (a neighbour) or not */
181         Neighbor possibleNeighbor = isNeighbour(address);
182         if(possibleNeighbor)
183         {
184             gprintln("sendPacket: We are sending to a neighor", DebugType.WARNING);
185             nextHop = possibleNeighbor;
186         }
187         else
188         {
189             gprintln("sendPacket: We are sending to a node VIA router", DebugType.WARNING);
190 
191             /* Make sure there is a route entry for it */
192             engine.getRouter().getTable().lockTable();
193             Route routeToHost = engine.getRouter().getTable().lookup(address);
194             engine.getRouter().getTable().unlockTable();
195             if(routeToHost)
196             {
197                 /* Set the next hop to the neighbor with the address in the route entry */
198                 nextHop = routeToHost.getNexthop();
199                 gprintln("sendPacket: Next-hop (indirect): "~to!(string)(routeToHost));
200             }
201             else
202             {
203                 gprintln("sendPacket: No route to "~address, DebugType.ERROR);
204                 return;
205             }
206         }
207 
208 
209 
210         /* TODO: Validate key */
211 
212         /* TODO: Add signature */
213 
214         import libsweatyballs.link.message.core;
215 
216         
217         /* Set neighbor port depending on which link it goes out on */
218         linkMsg.neighborPort = to!(string)(nextHop.getLink().getR2RPort());
219 
220         /* ProtoBuf encoded message */
221         byte[] message = cast(byte[])array(toProtobuf(linkMsg));
222 
223         /* TODO: Open socket to Neighbor and send the ProtoBuf-encoded and encrypted payload packet */
224         bool status = sendNBR(message, nextHop.getAddress());
225         gprintln("SendNBR Status: "~to!(string)(status));
226         /* TODO: Handle status */
227     }
228 
229     /**
230     * Opens a socket and sends `data` to `address`
231     *
232     * Returns status
233     */
234     private bool sendNBR(byte[] data, Address address)
235     {
236         try
237         {
238             Socket neighborSocket = new Socket(AddressFamily.INET6, SocketType.DGRAM, ProtocolType.UDP);
239             long status = neighborSocket.sendTo(data, address);
240 
241             return status > 0;
242         }
243         catch(SocketOSException)
244         {
245             return false;
246         }
247     }
248 
249     public void forward(Packet packet)
250     {
251         /* Decrement ttl */
252         packet.ttl--;
253         if(packet.ttl == 0)
254         {
255             gprintln("TTL REACHED, DAAI DING IS DOOD!", DebugType.ERROR);
256             return;
257         }
258 
259         string address = packet.toKey;
260 
261         /* Next-hop (for delivery), this is either a router or destination direct */
262         Neighbor nextHop;
263         
264         /* Make sure there is a route entry for it */
265         Route routeToHost = engine.getRouter().getTable().lookup(address);
266         if(routeToHost)
267         {
268             /* Set the next hop to the neighbor with the address in the route entry */
269             nextHop = routeToHost.getNexthop();
270             gprintln("foward(): Next-hop (router): "~to!(string)(routeToHost));
271         }
272         else
273         {
274             gprintln("foward(): No route to "~address, DebugType.ERROR);
275             return;
276         }
277 
278         /* LinkMessage */
279         LinkMessage linkMsg = new LinkMessage();
280         linkMsg.type = LinkMessageType.PACKET;
281         linkMsg.publicKey = engine.getRouter().getIdentity().getKeys().publicKey;
282         linkMsg.signature = "not yet implemented";
283         linkMsg.payload = cast(ubyte[])array(toProtobuf(packet));
284 
285         /* Set neighbor port depending on which link it goes out on */
286         linkMsg.neighborPort = to!(string)(nextHop.getLink().getR2RPort());
287 
288         /* ProtoBuf encoded message */
289         byte[] message = cast(byte[])array(toProtobuf(linkMsg));
290 
291         /* TODO: Open socket to Neighbor and send the ProtoBuf-encoded and encrypted payload packet */
292         bool status = sendNBR(message, nextHop.getAddress());
293         gprintln("SendNBR Status: "~to!(string)(status));
294         /* TODO: Handle status */
295     }
296 
297    
298 
299 }