1 module libyggdrasil.libyggdrasil;
2 
3 import libyggdrasil.utils : attemptString;
4 
5 import std.stdio;
6 import std.json;
7 import std.socket;
8 import std.string;
9 import std.conv : to;
10 import libchonky : ChonkReader;
11 
12 
13 
14 public final class BuildInfo
15 {
16 	public ubyte wellFormed = 0;
17 	private string _version = "none", arch = "none", platform = "none", name = "none";
18 
19 	this(JSONValue nodeInfo)
20 	{
21 		/* Attempt extraction */
22 		extractInfo(nodeInfo);
23 	}
24 
25 	private void extractInfo(JSONValue nodeInfo)
26 	{
27 		if(attemptString(nodeInfo, &_version, "buildversion"))
28 		{
29 			wellFormed++;
30 		}
31 		if(attemptString(nodeInfo, &arch, "buildarch"))
32 		{
33 			wellFormed++;
34 		}
35 		if(attemptString(nodeInfo, &platform, "buildplatform"))
36 		{
37 			wellFormed++;
38 		}
39 		if(attemptString(nodeInfo, &name, "buildname"))
40 		{
41 			wellFormed++;
42 		}
43 	}
44 
45 	public bool isWellFormed()
46 	{
47 		return wellFormed == 4;
48 	}
49 
50 	public string getVersion()
51 	{
52 		return _version;
53 	}
54 
55 	public string getName()
56 	{
57 		return name;
58 	}
59 
60 	public string getPlatform()
61 	{
62 		return platform;
63 	}
64 
65 	public string getArchitecture()
66 	{
67 		return arch;
68 	}
69 
70 }
71 
72 public final class NodeService
73 {
74 	private string serviceName;
75 	private string protocol;
76 	private ushort[] ports;
77 	
78 	this(string serviceName, string protocol, ushort[] ports)
79 	{
80 		this.serviceName = serviceName;
81 		this.protocol = protocol;
82 		this.ports = ports;
83 	}
84 
85 	public string getName()
86 	{
87 		return serviceName;
88 	}
89 
90 	public string getProtocol()
91 	{
92 		return protocol;
93 	}
94 
95 	public ushort[] getPorts()
96 	{
97 		return ports;
98 	}
99 }
100 
101 public class NodeInfo
102 {
103 	/* Key of the node this NodeInfo is associated with */
104 	private string key;
105 
106 	/**
107 	* NodeInfo data
108 	*/
109 	public ubyte wellFormed = 0;
110 	private JSONValue nodeInfoJSON;
111 	private string name = "<no name>";
112 	private string group = "<no name>";
113 	private string location = "<no name>";
114 	private string contact = "<no name>";
115 
116 
117 	/**
118 	* Given the response JSON this will extract the
119 	* key, NodeInfo as a whole and also attempt to extract
120 	* standardized aspects of the NodeInfo
121 	*/
122 	this(JSONValue nodeInfoJSON)
123 	{
124 		/* Save the key from the response */
125 		key = nodeInfoJSON.object().keys[0];
126 
127 		/* Extract the entry */
128 		this.nodeInfoJSON = nodeInfoJSON[key];
129 
130 		/* Attempt to parse the standardized parts */
131 		parse();
132 	}
133 	
134 	public string getName()
135 	{
136 		return name;
137 	}
138 
139 	public string getGroup()
140 	{
141 		return group;
142 	}
143 
144 	public string getLocation()
145 	{
146 		return location;
147 	}
148 
149 	public string getContact()
150 	{
151 		return contact;
152 	}
153 
154 	public string getKey()
155 	{
156 		return key;
157 	}
158 
159 	public JSONValue getFullJSON()
160 	{
161 		return nodeInfoJSON;
162 	}
163 
164 	private void parse()
165 	{
166 	
167 				if(attemptString(nodeInfoJSON, &name, "name"))
168 				{
169 					wellFormed++;
170 				}
171 		
172 				if(attemptString(nodeInfoJSON, &contact, "contact"))
173 				{
174 					wellFormed++;
175 				}
176 			
177 				if(attemptString(nodeInfoJSON, &group, "group"))
178 				{
179 					wellFormed++;
180 				}
181 			
182 				if(attemptString(nodeInfoJSON, &location, "location"))
183 				{
184 					wellFormed++;
185 				}
186 			
187 			
188 		
189 	}
190 
191 	public bool isWellFormed()
192 	{
193 		return wellFormed == 4;
194 	}
195 
196 	public BuildInfo getBuildInfo()
197 	{
198 		return new BuildInfo(nodeInfoJSON);
199 	}
200 
201 
202 	public override string toString()
203 	{
204 		/* TODO: */
205 		return ""; 
206 	}
207 }
208 
209 public final class DHTInfo
210 {
211 	this()
212 	{
213 		/* TODO: Implement me */
214 	}
215 }
216 
217 /**
218 * YggdrasilNode
219 *
220 * Given a key
221 */
222 public class YggdrasilNode
223 {
224 	private YggdrasilPeer peer;
225 	
226 	private string key;
227 	private JSONValue selfInfo;
228 	private JSONValue nodeInfo;
229 	
230 	
231 	this(YggdrasilPeer peer, string key)
232 	{
233 		this.peer = peer;
234 		this.key = key;
235 	}
236 
237 	public NodeInfo getNodeInfo()
238 	{
239 		/* Create the NodeInfo request */
240 		YggdrasilRequest req = new YggdrasilRequest(RequestType.NODEINFO, key);
241 
242 		/* Make the request */
243 		YggdrasilResponse resp = makeRequest(peer, req);
244 
245 		/* Create a new NodeInfo object */
246 		return new NodeInfo(resp.getJSON());
247 	}
248 
249 	public DHTInfo getDHT()
250 	{
251 		/* Create the getDHT request */
252 		YggdrasilRequest req = new YggdrasilRequest(RequestType.GETDHT, key);
253 
254 		/* Make the request */
255 		YggdrasilResponse resp = makeRequest(peer, req);
256 
257 		/* TODO: Implement me */
258 		return null;
259 	}
260 
261 	public YggdrasilNode[] getPeers()
262 	{
263 		/* Peers */
264 		YggdrasilNode[] peers;
265 
266 		/* Create the getPeers request */
267 		YggdrasilRequest req = new YggdrasilRequest(RequestType.GETPEERS, key);
268 
269 		/* Make the request */
270 		YggdrasilResponse resp = makeRequest(peer, req);
271 
272 		/* Get the JSON and process the list */
273 		JSONValue respJSON = resp.getJSON();
274 
275 		foreach(JSONValue ckey; respJSON[respJSON.object().keys[0]]["keys"].array())
276 		{
277 			string ckeyStr = ckey.str();
278 			peers ~= new YggdrasilNode(peer, ckeyStr);
279 		}
280 
281 		return peers;
282 	}
283 
284 	public string getKey()
285 	{
286 		return key;
287 	}
288 
289 	public override string toString()
290 	{
291 		/* TODO: Fetch getNodeInfo, if possible, else leave key */		
292 		return getNodeInfo().toString();
293 	}
294 
295 
296 	/**
297 	* Checks if the node is online
298 	*
299 	* This is implemented by doing a getDHT()
300 	*/
301 	public bool ping()
302 	{
303 		try
304 		{
305 			/* Attempt to do getDHT */
306 			getDHT();
307 			return true;
308 		}
309 		catch(YggdrasilException)
310 		{
311 			return false;
312 		}
313 	}
314 }
315 
316 /**
317 * YggdrasilNode
318 *
319 * This represents a peer of which we can
320 * connect to its control socket using TCP
321 */
322 public class YggdrasilPeer
323 {
324 	private Address yggdrasilAddress;
325 
326 	this(Address yggdrasilAddress)
327 	{
328 		this.yggdrasilAddress = yggdrasilAddress;	
329 	}
330 
331 	public Address getAddress()
332 	{
333 		return yggdrasilAddress;
334 	}
335 
336 	private void initData()
337 	{
338 		/* TODO: Add exception throwing here */
339 
340 			
341 	}
342 
343 	public YggdrasilNode fetchNode(string key)
344 	{
345 		return new YggdrasilNode(this, key);		
346 	}
347 }
348 
349 public enum RequestType
350 {
351 	NODEINFO, GETDHT, GETPEERS, GETSELF
352 }
353 
354 public final class YggdrasilRequest
355 {
356 	private RequestType requestType;
357 	private string key;
358 
359 	this(RequestType requestType, string key)
360 	{
361 		this.requestType = requestType;
362 		this.key = key;
363 	} 
364 
365 
366 	public JSONValue generateJSON()
367 	{
368 		JSONValue requestBlock;
369 
370 		/* Set the key of the node to request from */
371 		requestBlock["key"] = key;
372 
373 		if(requestType == RequestType.NODEINFO)
374 		{
375 			requestBlock["request"] = "getnodeinfo";
376 		}
377 		else if(requestType == RequestType.GETSELF)
378 		{
379 			requestBlock["request"] = "debug_remotegetself";
380 		}
381 		else if(requestType == RequestType.GETPEERS)
382 		{
383 			requestBlock["request"] = "debug_remotegetpeers";
384 		}
385 		else if(requestType == RequestType.GETDHT)
386 		{
387 			requestBlock["request"] = "debug_remotegetdht";
388 		}
389 		
390 
391 
392 		return requestBlock;
393 	}
394 }
395 
396 public final class YggdrasilResponse
397 {
398 	private JSONValue responseBlock;
399 
400 	this(JSONValue responseBlock)
401 	{
402 		this.responseBlock = responseBlock;
403 	}
404 
405 	public JSONValue getJSON()
406 	{
407 		return responseBlock;
408 	}
409 }
410 
411 public final class YggdrasilException : Exception
412 {
413 	public enum ErrorType
414 	{
415 		CONTROL_SOCKET_ERROR,
416 		JSON_PARSE_ERROR,
417 		TIMED_OUT
418 	}
419 
420 	private ErrorType errType;
421 
422 	this(ErrorType errType)
423 	{
424 		super("YggdrasilError: "~to!(string)(errType));
425 		this.errType = errType;
426 	}
427 
428 	public ErrorType getError()
429 	{
430 		return errType;
431 	}
432 }
433 
434 public YggdrasilResponse makeRequest(YggdrasilPeer peer, YggdrasilRequest request)
435 {
436 	/* The response */
437 	YggdrasilResponse response;
438 
439 	/* Communication socket */
440 	Socket controlSocket;
441 
442 	try
443 	{
444 		/* Attempt to create the socket and connect it */
445 		controlSocket = new Socket(AddressFamily.INET6, SocketType.STREAM, ProtocolType.TCP);
446 		controlSocket.connect(peer.getAddress());
447 
448 		/* Make the request */
449 		JSONValue requestBlock = request.generateJSON();
450 		controlSocket.send(cast(byte[])toJSON(requestBlock));
451 
452 		/* Await reply till socket closes */
453 		ChonkReader reader = new ChonkReader(controlSocket);
454 		byte[] buffer;
455 		reader.receiveUntilClose(buffer);
456 
457 		/* Parse the response */
458 		JSONValue responseBlock;
459 		try
460 		{
461 			/* Parse the JSON */
462 			responseBlock = parseJSON(cast(string)buffer);
463 
464 			/* Check status of request */
465 			if(cmp(responseBlock["status"].str(), "success") == 0)
466 			{
467 				/* Extract response */
468 				JSONValue reuqestResponse = responseBlock["response"];
469 
470 				/* Create the YggdrasilResponse object */
471 				response = new YggdrasilResponse(reuqestResponse);
472 
473 				/* Close the socket */
474 				controlSocket.close();
475 			}
476 			else
477 			{
478 				throw new YggdrasilException(YggdrasilException.ErrorType.TIMED_OUT);
479 			}
480 		}
481 		catch(JSONException e)
482 		{
483 			throw new YggdrasilException(YggdrasilException.ErrorType.JSON_PARSE_ERROR);
484 		}
485 	}
486 	catch(SocketOSException e)
487 	{
488 		throw new YggdrasilException(YggdrasilException.ErrorType.CONTROL_SOCKET_ERROR);
489 	}
490 
491 	return response;
492 }