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             Route routeToHost = engine.getRouter().getTable().lookup(address);
193             if(routeToHost)
194             {
195                 /* Set the next hop to the neighbor with the address in the route entry */
196                 nextHop = routeToHost.getNexthop();
197                 gprintln("sendPacket: Next-hop (indirect): "~to!(string)(routeToHost));
198             }
199             else
200             {
201                 gprintln("sendPacket: No route to "~address, DebugType.ERROR);
202                 return;
203             }
204         }
205 
206 
207 
208         /* TODO: Validate key */
209 
210         /* TODO: Add signature */
211 
212         import libsweatyballs.link.message.core;
213 
214         
215         /* Set neighbor port depending on which link it goes out on */
216         linkMsg.neighborPort = to!(string)(nextHop.getLink().getR2RPort());
217 
218         /* ProtoBuf encoded message */
219         byte[] message = cast(byte[])array(toProtobuf(linkMsg));
220 
221         /* TODO: Open socket to Neighbor and send the ProtoBuf-encoded and encrypted payload packet */
222         bool status = sendNBR(message, nextHop.getAddress());
223         gprintln("SendNBR Status: "~to!(string)(status));
224         /* TODO: Handle status */
225     }
226 
227     /**
228     * Opens a socket and sends `data` to `address`
229     *
230     * Returns status
231     */
232     private bool sendNBR(byte[] data, Address address)
233     {
234         try
235         {
236             Socket neighborSocket = new Socket(AddressFamily.INET6, SocketType.DGRAM, ProtocolType.UDP);
237             long status = neighborSocket.sendTo(data, address);
238 
239             return status > 0;
240         }
241         catch(SocketOSException)
242         {
243             return false;
244         }
245     }
246 
247     public void forward(Packet packet)
248     {
249         /* Decrement ttl */
250         packet.ttl--;
251         if(packet.ttl == 0)
252         {
253             gprintln("TTL REACHED, DAAI DING IS DOOD!", DebugType.ERROR);
254             return;
255         }
256 
257         string address = packet.toKey;
258 
259         /* Next-hop (for delivery), this is either a router or destination direct */
260         Neighbor nextHop;
261         
262         /* Make sure there is a route entry for it */
263         Route routeToHost = engine.getRouter().getTable().lookup(address);
264         if(routeToHost)
265         {
266             /* Set the next hop to the neighbor with the address in the route entry */
267             nextHop = routeToHost.getNexthop();
268             gprintln("foward(): Next-hop (router): "~to!(string)(routeToHost));
269         }
270         else
271         {
272             gprintln("foward(): No route to "~address, DebugType.ERROR);
273             return;
274         }
275 
276         /* LinkMessage */
277         LinkMessage linkMsg = new LinkMessage();
278         linkMsg.type = LinkMessageType.PACKET;
279         linkMsg.publicKey = engine.getRouter().getIdentity().getKeys().publicKey;
280         linkMsg.signature = "not yet implemented";
281         linkMsg.payload = cast(ubyte[])array(toProtobuf(packet));
282 
283         /* Set neighbor port depending on which link it goes out on */
284         linkMsg.neighborPort = to!(string)(nextHop.getLink().getR2RPort());
285 
286         /* ProtoBuf encoded message */
287         byte[] message = cast(byte[])array(toProtobuf(linkMsg));
288 
289         /* TODO: Open socket to Neighbor and send the ProtoBuf-encoded and encrypted payload packet */
290         bool status = sendNBR(message, nextHop.getAddress());
291         gprintln("SendNBR Status: "~to!(string)(status));
292         /* TODO: Handle status */
293     }
294 
295     /* TODO: Move this elsewhere */
296     public class Session : Thread
297     {
298         private string aesKey;
299         private string sessionID;
300 
301         private Socket neighborSock;
302 
303         this(Socket clientSocket)
304         {
305             super(&worker);
306             this.neighborSock = neighborSock;
307         }
308 
309         private void worker()
310         {
311 
312         }
313     }
314 
315     private Session fetchSession(string address)
316     {
317         return null;
318     }
319 
320     private Session createSession(string address)
321     {
322         /* TODO: Generate random AES key */
323 
324         return null;
325     }
326 
327     private bool isSessionExists(string address)
328     {
329         /* Lock sessions */
330 
331         /* Find the session */
332         foreach(Session session; sessions)
333         {
334 
335         }
336 
337 
338         /* Unlockk sessions */
339 
340         return true;
341     }
342 }