1 /*
2  * Copyright (c) 2017-2018 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: 2017-2018 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/hub/handler/hncom.d, selery/hub/handler/hncom.d)
28  */
29 module selery.hub.handler.hncom;
30 
31 import core.atomic : atomicOp;
32 import core.thread : Thread;
33 
34 import std.algorithm : canFind;
35 import std.concurrency : spawn;
36 import std.conv : to;
37 import std.datetime : dur;
38 import std.json : JSONValue;
39 import std.math : round;
40 import std.regex : ctRegex, matchFirst;
41 import std.socket;
42 import std.string;
43 import std.system : Endian;
44 import std.zlib;
45 
46 import sel.hncom.about;
47 import sel.hncom.handler : Handler = HncomHandler;
48 import sel.net.modifiers : LengthPrefixedStream;
49 import sel.net.stream : TcpStream;
50 import sel.server.query : Query;
51 import sel.server.util;
52 
53 import selery.about;
54 import selery.hub.player : WorldSession = World, PlayerSession, Skin;
55 import selery.hub.server : HubServer;
56 import selery.lang : translate;
57 import selery.util.thread : SafeThread;
58 import selery.util.util : microseconds;
59 
60 import Util = sel.hncom.util;
61 import Login = sel.hncom.login;
62 import Status = sel.hncom.status;
63 import Player = sel.hncom.player;
64 
65 alias HncomStream = LengthPrefixedStream!(uint, Endian.littleEndian);
66 
67 class HncomHandler {
68 
69 	private shared HubServer server;
70 	
71 	private shared JSONValue* additionalJson;
72 
73 	private shared Address address;
74 	
75 	public shared this(shared HubServer server, shared JSONValue* additionalJson) {
76 		this.server = server;
77 		this.additionalJson = additionalJson;
78 	}
79 
80 	public shared void start(inout(string)[] accepted, ushort port) {
81 		bool v4, v6, public_;
82 		foreach(address ; accepted) {
83 			switch(address) {
84 				case "127.0.0.1":
85 					v4 = true;
86 					break;
87 				case "::1":
88 					v6 = true;
89 					break;
90 				default:
91 					if(address.canFind(":")) v6 = true;
92 					else v4 = true;
93 					public_ = true;
94 					break;
95 			}
96 		}
97 		Address address = getAddress(public_ ? (v4 ? "0.0.0.0" : "::") : (v4 ? "127.0.0.1" : "::1"), port)[0];
98 		Socket socket = new TcpSocket(v4 && v6 ? AddressFamily.INET | AddressFamily.INET6 : address.addressFamily);
99 		socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
100 		socket.setOption(SocketOptionLevel.IPV6, SocketOption.IPV6_V6ONLY, !v4 || !v6);
101 		socket.blocking = true;
102 		socket.bind(address);
103 		socket.listen(8);
104 		this.address = cast(shared)address;
105 		spawn(&this.acceptClients, cast(shared)socket);
106 	}
107 	
108 	private shared void acceptClients(shared Socket _socket) {
109 		debug Thread.getThis().name = "hncom_server@" ~ (cast()_socket).localAddress.toString();
110 		Socket socket = cast()_socket;
111 		while(true) {
112 			Socket client = socket.accept();
113 			Address address;
114 			try {
115 				address = client.remoteAddress;
116 			} catch(Exception) {
117 				continue;
118 			}
119 			if(this.server.acceptNode(address)) {
120 				new SafeThread(this.server.config.lang, {
121 					shared ClassicNode node = new shared ClassicNode(this.server, client, this.additionalJson);
122 					delete node;
123 				}).start();
124 			} else {
125 				client.close();
126 			}
127 		}
128 	}
129 
130 	public shared pure nothrow @property @safe @nogc shared(Address) localAddress() {
131 		return this.address;
132 	}
133 	
134 }
135 
136 /**
137  * Session of a node. It's executed in a dedicated thread.
138  */
139 abstract class AbstractNode : Handler!serverbound {
140 
141 	private static shared uint _id;
142 
143 	public immutable uint id;
144 	
145 	private shared HubServer server;
146 	private shared JSONValue* additionalJson;
147 
148 	protected HncomStream stream;
149 	
150 	private shared bool n_main;
151 	private shared string n_name;
152 	
153 	private shared uint[][ubyte] accepted;
154 	
155 	private shared uint n_max;
156 	public shared Login.NodeInfo.Plugin[] plugins;
157 	
158 	private shared PlayerSession[immutable(uint)] players;
159 	private shared WorldSession[immutable(uint)] _worlds;
160 	
161 	private uint n_latency;
162 	
163 	private shared float n_tps;
164 	private shared ulong n_ram;
165 	private shared float n_cpu;
166 	
167 	public shared this(shared HubServer server, shared JSONValue* additionalJson) {
168 		this.id = atomicOp!"+="(_id, 1);
169 		this.server = server;
170 		this.additionalJson = additionalJson;
171 	}
172 
173 	protected shared void exchageInfo(HncomStream stream) {
174 		with(cast()server.config.hub) {
175 			Login.HubInfo.GameInfo[ubyte] games;
176 			if(bedrock) games[__BEDROCK__] = Login.HubInfo.GameInfo(bedrock.motd, bedrock.protocols, bedrock.onlineMode, ushort(0));
177 			if(java) games[__JAVA__] = Login.HubInfo.GameInfo(java.motd, java.protocols, java.onlineMode, ushort(0));
178 			this.sendHubInfo(stream, Login.HubInfo(server.id, server.nextPool, displayName, games, server.onlinePlayers, server.maxPlayers, server.config.lang.language, server.config.lang.acceptedLanguages.dup, webAdmin, cast()*this.additionalJson));
179 		}
180 		auto info = this.receiveNodeInfo(stream);
181 		this.n_max = info.max;
182 		this.accepted = cast(shared uint[][ubyte])info.acceptedGames;
183 		this.plugins = cast(shared)info.plugins;
184 		foreach(node ; server.nodesList) stream.send(node.addPacket.encode());
185 		server.add(this);
186 		this.loop(stream);
187 		server.remove(this);
188 		this.onClosed();
189 	}
190 
191 	protected abstract shared void sendHubInfo(HncomStream stream, Login.HubInfo packet);
192 
193 	protected abstract shared Login.NodeInfo receiveNodeInfo(HncomStream stream);
194 
195 	protected abstract shared void loop(HncomStream stream);
196 
197 	protected abstract void send(ubyte[] buffer);
198 
199 	protected shared void send(ubyte[] buffer) {
200 		return (cast()this).send(buffer);
201 	}
202 
203 	/**
204 	 * Gets the name of the node. The name is different for every node
205 	 * connected to hub and it should be used other nodes with
206 	 * the transfer function.
207 	 */
208 	public shared nothrow @property @safe @nogc const string name() {
209 		return this.n_name;
210 	}
211 	
212 	/**
213 	 * Indicates whether or not this is a main node.
214 	 * A main node is able to receive players without the
215 	 * use of the transfer function.
216 	 * Every hub should have at least one main node, otherwise
217 	 * every player that tries to connect will be disconnected with
218 	 * the 'end of stream' message.
219 	 */
220 	public shared nothrow @property @safe @nogc const bool main() {
221 		return this.n_main;
222 	}
223 	
224 	/**
225 	 * Gets the highest number of players that can connect to the node.
226 	 */
227 	public shared nothrow @property @safe @nogc const uint max() {
228 		return this.n_max;
229 	}
230 	
231 	/**
232 	 * Gets the number of players connected to the node.
233 	 */
234 	public shared nothrow @property @safe @nogc const uint online() {
235 		version(X86_64) {
236 			return cast(uint)this.players.length;
237 		} else {
238 			return this.players.length;
239 		}
240 	}
241 	
242 	/**
243 	 * Indicates whether the node is full.
244 	 */
245 	public shared nothrow @property @safe @nogc const bool full() {
246 		return this.max != Login.NodeInfo.UNLIMITED && this.online >= this.max;
247 	}
248 
249 	/**
250 	 * Gets the list of worlds loaded on the node.
251 	 */
252 	public shared nothrow @property shared(WorldSession)[] worlds() {
253 		return this._worlds.values;
254 	}
255 	
256 	/**
257 	 * Gets the node's latency (it may not be precise).
258 	 */
259 	public shared nothrow @property @safe @nogc const uint latency() {
260 		return this.n_latency;
261 	}
262 	
263 	/**
264 	 * Gets the node's usage, updated with the ResourcesUsage packet.
265 	 */
266 	public shared nothrow @property @safe @nogc const float tps() {
267 		return this.n_tps;
268 	}
269 	
270 	/// ditto
271 	public shared nothrow @property @safe @nogc const ulong ram() {
272 		return this.n_ram;
273 	}
274 	
275 	/// ditto
276 	public shared nothrow @property @safe @nogc const float cpu() {
277 		return this.n_cpu;
278 	}
279 	
280 	public shared nothrow @property @safe bool accepts(ubyte game, uint protocol) {
281 		auto p = game in this.accepted;
282 		return p && (*p).canFind(protocol);
283 	}
284 	
285 	public shared @property Status.AddNode addPacket() {
286 		return Status.AddNode(this.id, this.name, this.main, cast(uint[][ubyte])this.accepted);
287 	}
288 
289 	protected override void handleUtilUncompressed(Util.Uncompressed packet) {
290 		assert(packet.id == 0); //TODO
291 		foreach(p ; packet.packets) {
292 			if(p.length) this.handleHncom(p.dup);
293 		}
294 	}
295 	
296 	protected override void handleUtilCompressed(Util.Compressed packet) {
297 		this.handleUtilUncompressed(packet.uncompress());
298 	}
299 
300 	protected override void handleStatusLatency(Status.Latency packet) {
301 		this.send(packet.encode());
302 	}
303 
304 	protected override void handleStatusLog(Status.Log packet) {
305 		string name;
306 		if(packet.worldId != -1) {
307 			auto world = packet.worldId in this._worlds;
308 			if(world) name = world.name;
309 		}
310 		this.server.handleLog((cast(shared)this).name, packet.message, packet.timestamp, packet.commandId, packet.worldId, name);
311 	}
312 
313 	protected override void handleStatusSendMessage(Status.SendMessage packet) {
314 		if(packet.addressees.length) {
315 			foreach(addressee ; packet.addressees) {
316 				auto node = this.server.nodeById(addressee);
317 				if(node !is null) node.sendMessage(this.id, false, packet.payload);
318 			}
319 		} else {
320 			foreach(node ; this.server.nodesList) {
321 				if(node.id != this.id) node.sendMessage(this.id, true, packet.payload);
322 			}
323 		}
324 	}
325 
326 	protected override void handleStatusUpdateMaxPlayers(Status.UpdateMaxPlayers packet) {
327 		this.n_max = packet.max;
328 		this.server.updateMaxPlayers();
329 	}
330 
331 	protected override void handleStatusUpdateUsage(Status.UpdateUsage packet) {
332 		this.n_ram = (cast(ulong)packet.ram) * 1024Lu;
333 		this.n_cpu = packet.cpu;
334 	}
335 
336 	protected override void handleStatusUpdateLanguageFiles(Status.UpdateLanguageFiles packet) {
337 		this.server.config.lang.add(packet.language, packet.messages);
338 	}
339 
340 	protected override void handleStatusAddWorld(Status.AddWorld packet) {
341 		auto world = new shared WorldSession(packet.worldId, packet.name, packet.dimension);
342 		if(packet.parent != -1) {
343 			auto parent = packet.parent in this._worlds;
344 			if(parent) world.parent = *parent;
345 		}
346 		this._worlds[packet.worldId] = world;
347 	}
348 
349 	protected override void handleStatusRemoveWorld(Status.RemoveWorld packet) {
350 		this._worlds.remove(packet.worldId);
351 	}
352 
353 	protected override void handlePlayerKick(Player.Kick packet) {
354 		auto player = packet.hubId in this.players;
355 		if(player) {
356 			this.players.remove(packet.hubId);
357 			(*player).kick(packet.reason, packet.translation, packet.parameters);
358 		}
359 	}
360 
361 	protected override void handlePlayerTransfer(Player.Transfer packet) {
362 		auto player = packet.hubId in this.players;
363 		if(player) {
364 			this.players.remove(packet.hubId);
365 			(*player).connect(Player.Add.TRANSFERRED, packet.node, packet.message, packet.onFail);
366 		}
367 	}
368 
369 	protected override void handlePlayerUpdateDisplayName(Player.UpdateDisplayName packet) {
370 		auto player = packet.hubId in this.players;
371 		if(player) {
372 			(*player).displayName = packet.displayName;
373 		}
374 	}
375 
376 	protected override void handlePlayerUpdateWorld(Player.UpdateWorld packet) {
377 		auto player = packet.hubId in this.players;
378 		auto world = packet.worldId in this._worlds;
379 		if(player && world) {
380 			(*player).world = *world;
381 		}
382 	}
383 
384 	protected override void handlePlayerUpdatePermissionLevel(Player.UpdatePermissionLevel packet) {
385 		auto player = packet.hubId in this.players;
386 		if(player) {
387 			(*player).permissionLevel = packet.permissionLevel;
388 		}
389 	}
390 
391 	protected override void handlePlayerGamePacket(Player.GamePacket packet) {
392 		//TODO compress if needed and send
393 	}
394 
395 	protected override void handlePlayerSerializedGamePacket(Player.SerializedGamePacket packet) {
396 		auto player = packet.hubId in this.players;
397 		if(player) {
398 			(*player).sendFromNode(packet.payload);
399 		}
400 	}
401 
402 	protected override void handlePlayerOrderedGamePacket(Player.OrderedGamePacket packet) {
403 		auto player = packet.hubId in this.players;
404 		if(player) {
405 			(*player).sendOrderedFromNode(packet.order, packet.payload);
406 		}
407 	}
408 	
409 	/**
410 	 * Sends data to the node received from a player.
411 	 */
412 	public shared void sendTo(shared PlayerSession player, ubyte[] data) {
413 		this.send(Player.GamePacket(player.id, data).encode());
414 	}
415 	
416 	/**
417 	 * Executes a remote command.
418 	 */
419 	public shared void remoteCommand(string command, ubyte origin, Address address, int commandId) {
420 		this.send(Status.RemoteCommand(origin, address, command, commandId).encode());
421 	}
422 	
423 	/**
424 	 * Notifies the node that another node has connected
425 	 * to the hub.
426 	 */
427 	public shared void addNode(shared AbstractNode node) {
428 		this.send(node.addPacket.encode());
429 	}
430 	
431 	/**
432 	 * Notifies the node that another node has been
433 	 * disconnected from the hub.
434 	 */
435 	public shared void removeNode(shared AbstractNode node) {
436 		this.send(Status.RemoveNode(node.id).encode());
437 	}
438 	
439 	/**
440 	 * Sends a message to the node.
441 	 */
442 	public shared void sendMessage(uint sender, bool broadcasted, ubyte[] payload) {
443 		this.send(Status.ReceiveMessage(sender, broadcasted, payload).encode());
444 	}
445 	
446 	/**
447 	 * Sends the number of online players and maximum number of
448 	 * players to the node.
449 	 */
450 	public shared void updatePlayers(inout uint online, inout uint max) {
451 		this.send(Status.UpdatePlayers(online, max).encode());
452 	}
453 	
454 	/**
455 	 * Adds a player to the node.
456 	 */
457 	public shared void addPlayer(shared PlayerSession player, ubyte reason, ubyte[] transferMessage) {
458 		this.players[player.id] = player;
459 		this.send(Player.Add(player.id, reason, transferMessage, player.type, player.protocol, player.uuid, player.username, player.displayName, player.gameName, player.gameVersion, player.permissionLevel, player.dimension, player.viewDistance, player.address, Player.Add.ServerAddress(player.serverIp, player.serverPort), player.skin is null ? Player.Add.Skin.init : Player.Add.Skin(player.skin.name, player.skin.data.dup, player.skin.cape.dup, player.skin.geometryName, player.skin.geometryData.dup), player.language, cast(ubyte)player.inputMode, player.hncomAddData()).encode());
460 	}
461 	
462 	/**
463 	 * Called when a player is transferred by the hub (not by the node)
464 	 * to another node.
465 	 */
466 	public shared void onPlayerTransferred(shared PlayerSession player) {
467 		this.onPlayerGone(player, Player.Remove.TRANSFERRED);
468 	}
469 	
470 	/**
471 	 * Called when a player lefts the server using the disconnect
472 	 * button or closing the socket.
473 	 */
474 	public shared void onPlayerLeft(shared PlayerSession player) {
475 		this.onPlayerGone(player, Player.Remove.LEFT);
476 	}
477 	
478 	/**
479 	 * Called when a player times out.
480 	 */
481 	public shared void onPlayerTimedOut(shared PlayerSession player) {
482 		this.onPlayerGone(player, Player.Remove.TIMED_OUT);
483 	}
484 	
485 	/**
486 	 * Called when a player is kicked (not by the node).
487 	 */
488 	public shared void onPlayerKicked(shared PlayerSession player) {
489 		this.onPlayerGone(player, Player.Remove.KICKED);
490 	}
491 	
492 	/**
493 	 * Generic function that removes a player from the
494 	 * node's list and sends a PlayerDisconnected packet to
495 	 * notify the node of the disconnection.
496 	 */
497 	protected shared void onPlayerGone(shared PlayerSession player, ubyte reason) {
498 		if(this.players.remove(player.id)) {
499 			this.send(Player.Remove(player.id, reason).encode());
500 		}
501 	}
502 
503 	public shared void sendDisplayNameUpdate(shared PlayerSession player, string displayName) {
504 		this.send(Player.UpdateDisplayName(player.id, displayName).encode());
505 	}
506 
507 	public shared void sendPermissionLevelUpdate(shared PlayerSession player, ubyte permissionLevel) {
508 		this.send(Player.UpdatePermissionLevel(player.id, permissionLevel).encode());
509 	}
510 
511 	public shared void sendViewDistanceUpdate(shared PlayerSession player, uint viewDistance) {
512 		this.send(Player.UpdateViewDistance(player.id, viewDistance).encode());
513 	}
514 
515 	public shared void sendLanguageUpdate(shared PlayerSession player, string language) {
516 		this.send(Player.UpdateLanguage(player.id, language).encode());
517 	}
518 	
519 	/**
520 	 * Updates a player's latency (usually sent every 30 seconds).
521 	 */
522 	public shared void sendLatencyUpdate(shared PlayerSession player) {
523 		this.send(Player.UpdateLatency(player.id, player.latency).encode());
524 	}
525 	
526 	/**
527 	 * Updates a player's packet loss (usually sent every 30 seconds).
528 	 */
529 	public shared void sendPacketLossUpdate(shared PlayerSession player) {
530 		this.send(new Player.UpdatePacketLoss(player.id, player.packetLoss).encode());
531 	}
532 	
533 	/**
534 	 * Called when the client closes the connection.
535 	 * Tries to transfer every connected player to the main node.
536 	 */
537 	public shared void onClosed(bool transfer=true) {
538 		if(transfer) {
539 			foreach(shared PlayerSession player ; this.players) {
540 				player.connect(Player.Add.FORCIBLY_TRANSFERRED);
541 			}
542 		} else {
543 			foreach(shared PlayerSession player ; this.players) {
544 				player.kick("disconnect.close", true, []);
545 			}
546 		}
547 	}
548 	
549 	public abstract shared inout string toString();
550 	
551 }
552 
553 class ClassicNode : AbstractNode {
554 
555 	private shared Socket socket;
556 	private immutable string remoteAddress;
557 
558 	public shared this(shared HubServer server, Socket socket, shared JSONValue* additionalJson) {
559 		super(server, additionalJson);
560 		this.socket = cast(shared)socket;
561 		this.remoteAddress = socket.remoteAddress.toString();
562 		debug Thread.getThis().name = "hncom_client#" ~ to!string(this.id);
563 		socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"msecs"(2500));
564 		socket.blocking = true;
565 		auto stream = new HncomStream(new TcpStream(socket, 4096));
566 		this.stream = cast(shared)stream;
567 		auto payload = stream.receive();
568 		if(payload.length && payload[0] == Login.ConnectionRequest.ID) {
569 			immutable password = server.config.hub.hncomPassword;
570 			auto request = Login.ConnectionRequest.fromBuffer(payload[1..$]);
571 			this.n_name = request.name.idup;
572 			this.n_main = request.main;
573 			Login.ConnectionResponse response;
574 			if(request.protocol > __PROTOCOL__) response.status = Login.ConnectionResponse.OUTDATED_HUB;
575 			else if(request.protocol < __PROTOCOL__) response.status = Login.ConnectionResponse.OUTDATED_NODE;
576 			else if(password.length && !password.length) response.status = Login.ConnectionResponse.PASSWORD_REQUIRED;
577 			else if(password.length && password != request.password) response.status = Login.ConnectionResponse.WRONG_PASSWORD;
578 			else if(!this.n_name.length || this.n_name.length > 32) response.status = Login.ConnectionResponse.INVALID_NAME_LENGTH;
579 			else if(!this.n_name.matchFirst(ctRegex!r"[^a-zA-Z0-9_+-.,!?:@#$%\/]").empty) response.status = Login.ConnectionResponse.INVALID_NAME_CHARACTERS;
580 			else if(server.nodeNames.canFind(this.n_name)) response.status = Login.ConnectionResponse.NAME_ALREADY_USED;
581 			else if(["reload", "stop"].canFind(this.n_name.toLower)) response.status = Login.ConnectionResponse.NAME_RESERVED;
582 			stream.send(response.encode());
583 			if(response.status == Login.ConnectionResponse.OK) {
584 				this.exchageInfo(stream);
585 			}
586 		}
587 		socket.close();
588 	}
589 
590 	protected override shared void sendHubInfo(HncomStream stream, Login.HubInfo packet) {
591 		stream.send(packet.encode());
592 	}
593 
594 	protected override shared Login.NodeInfo receiveNodeInfo(HncomStream stream) {
595 		stream.stream.socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"minutes"(5)); // giving it the time to load resorces and generate worlds
596 		auto payload = stream.receive();
597 		if(payload.length && payload[0] == Login.NodeInfo.ID) return Login.NodeInfo.fromBuffer(payload[1..$]);
598 		else return Login.NodeInfo.init;
599 	}
600 
601 	protected override shared void loop(HncomStream stream) {
602 		auto _this = cast()this;
603 		stream.stream.socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"msecs"(0)); // blocking without timeout
604 		while(true) {
605 			auto payload = stream.receive();
606 			if(payload.length) _this.handleHncom(payload);
607 			else break; // connection closed or error
608 		}
609 	}
610 
611 	protected override void send(ubyte[] payload) {
612 		this.stream.send(payload);
613 	}
614 	
615 	public override shared inout string toString() {
616 		return "Node(" ~ to!string(this.id) ~ ", " ~ this.name ~ ", " ~ this.remoteAddress ~ ", " ~ to!string(this.n_main) ~ ")";
617 	}
618 
619 }
620 
621 class LiteNode : AbstractNode {
622 
623 	static import std.concurrency;
624 	
625 	public shared static bool ready = false;
626 	public shared static std.concurrency.Tid tid;
627 	
628 	private std.concurrency.Tid node;
629 
630 	public shared this(shared HubServer server, shared JSONValue* additionalJson) {
631 		super(server, additionalJson);
632 		tid = cast(shared)std.concurrency.thisTid;
633 		ready = true;
634 		this.node = cast(shared)std.concurrency.receiveOnly!(std.concurrency.Tid)();
635 		this.n_main = true;
636 		this.exchageInfo(null);
637 	}
638 
639 	protected override shared void sendHubInfo(HncomStream stream, Login.HubInfo packet) {
640 		std.concurrency.send(cast()this.node, cast(shared)packet);
641 	}
642 
643 	protected override shared Login.NodeInfo receiveNodeInfo(HncomStream stream) {
644 		return cast()std.concurrency.receiveOnly!(shared Login.NodeInfo)();
645 	}
646 
647 	protected override shared void loop(HncomStream stream) {
648 		auto _this = cast()this;
649 		while(true) {
650 			ubyte[] payload = std.concurrency.receiveOnly!(immutable(ubyte)[])().dup;
651 			if(payload.length) {
652 				_this.handleHncom(payload);
653 			} else {
654 				break;
655 			}
656 		}
657 	}
658 	
659 	protected override void send(ubyte[] buffer) {
660 		std.concurrency.send(this.node, buffer.idup);
661 	}
662 	
663 	protected override shared void send(ubyte[] buffer) {
664 		std.concurrency.send(cast()this.node, buffer.idup);
665 	}
666 	
667 	public override shared inout string toString() {
668 		return "LiteNode(" ~ to!string(this.id) ~ ")";
669 	}
670 	
671 }