1 /**
2 Copyright: Copyright (c) 2014-2016 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module netlib.connection;
8 
9 import std.experimental.logger;
10 import std.conv : to;
11 
12 import derelict.enet.enet;
13 import cbor;
14 public import derelict.enet.enet : ENetPeer;
15 import std.string : format;
16 
17 
18 // Client id type. Used in server to identify clients.
19 alias ClientId = size_t;
20 
21 
22 void loadEnet(string[] libNames)
23 {
24 	DerelictENet.load(libNames);
25 
26 	int err = enet_initialize();
27 
28 	if (err != 0)
29 	{
30 		error("Error loading ENet library");
31 		return;
32 	}
33 	else
34 	{
35 		ENetVersion ever = enet_linked_version();
36 		infof("Loaded ENet library v%s.%s.%s",
37 			ENET_VERSION_GET_MAJOR(ever),
38 			ENET_VERSION_GET_MINOR(ever),
39 			ENET_VERSION_GET_PATCH(ever));
40 	}
41 }
42 
43 /// Packet handler.
44 /// Returns true if data was valid and false otherwise.
45 alias PacketHandler = void delegate(ubyte[] packetData, ClientId clientId);
46 
47 struct PacketInfo
48 {
49 	string name;
50 	PacketHandler handler;
51 	size_t id;
52 }
53 
54 struct ConnectionSettings
55 {
56 	ENetAddress* address;
57 	size_t maxPeers;
58 	size_t numChannels;
59 	uint incomingBandwidth;
60 	uint outgoingBandwidth;
61 }
62 
63 // packetData must contain data with packet id stripped off.
64 P unpackPacket(P)(ubyte[] packetData)
65 {
66 	return decodeCborSingleDup!P(packetData);
67 }
68 
69 P unpackPacketNoDup(P)(ubyte[] packetData)
70 {
71 	return decodeCborSingle!P(packetData);
72 }
73 
74 abstract class Connection
75 {
76 	// True if connection is still open.
77 	bool isRunning;
78 
79 	// Local side of connection.
80 	ENetHost* host;
81 
82 	// Used when handling packet based on its id.
83 	PacketInfo*[] packetArray;
84 
85 	// Used to get packet id when sending packet.
86 	PacketInfo*[TypeInfo] packetMap;
87 
88 	ubyte[] buffer = new ubyte[1024*1024];
89 
90 	void delegate(ref ENetEvent) connectHandler;
91 	void delegate(ref ENetEvent) disconnectHandler;
92 
93 	void start(ConnectionSettings settings)
94 	{
95 		if (isRunning) stop();
96 
97 		host = enet_host_create(settings.address,
98 			settings.maxPeers,
99 			settings.numChannels,
100 			settings.incomingBandwidth,
101 			settings.outgoingBandwidth);
102 
103 		if (host is null)
104 		{
105 			error("An error occured while trying to create an ENet host");
106 			return;
107 		}
108 
109 		isRunning = true;
110 	}
111 
112 	size_t packetId(P)()
113 	{
114 		return packetMap[typeid(P)].id;
115 	}
116 
117 	string packetName(size_t packetId)
118 	{
119 		if (packetId >= packetArray.length) return "!UnknownPacket!";
120 		return packetArray[packetId].name;
121 	}
122 
123 	void registerPacket(P)(PacketHandler handler = null, string packetName = P.stringof)
124 	{
125 		size_t newId = packetArray.length;
126 		PacketInfo* pinfo = new PacketInfo(packetName, handler, newId);
127 		packetArray ~= pinfo;
128 		assert(typeid(P) !in packetMap);
129 		packetMap[typeid(P)] = pinfo;
130 	}
131 
132 	void registerPacketHandler(P)(PacketHandler handler)
133 	{
134 		assert(typeid(P) in packetMap, format("Packet '%s' was not registered", typeid(P)));
135 		packetMap[typeid(P)].handler = handler;
136 	}
137 
138 	bool handlePacket(size_t packetId, ubyte[] packetData, ClientId peerInfo)
139 	{
140 		if (packetId >= packetArray.length)
141 			return false; // invalid packet
142 
143 		auto handler = packetArray[packetId].handler;
144 		if (handler is null)
145 			return false; // handler is not set
146 
147 		handler(packetData, peerInfo);
148 		return true;
149 	}
150 
151 	ubyte[] createPacket(P)(auto ref const(P) packet)
152 	{
153 		ubyte[] bufferTemp = buffer;
154 		size_t size;
155 
156 		size = encodeCbor(bufferTemp[], packetId!P);
157 		size += encodeCbor(bufferTemp[size..$], packet);
158 
159 		return bufferTemp[0..size];
160 	}
161 
162 	string[] packetNames() @property
163 	{
164 		import std.algorithm : map;
165 		import std.array : array;
166 		return packetArray.map!(a => a.name).array;
167 	}
168 
169 	void printPacketMap()
170 	{
171 		foreach(i, packetInfo; packetArray)
172 		{
173 			tracef("% 2s: %s", i, packetInfo.name);
174 		}
175 	}
176 
177 	void shufflePackets()
178 	{
179 		import std.random;
180 		randomShuffle(packetArray[1..$]);
181 		foreach (i, packetInfo; packetArray)
182 			packetInfo.id = i;
183 	}
184 
185 	void flush()
186 	{
187 		if (!isRunning) return;
188 		enet_host_flush(host);
189 	}
190 
191 	void stop()
192 	{
193 		isRunning = false;
194 		enet_host_destroy(host);
195 	}
196 
197 	void update()
198 	{
199 		if (!isRunning) return;
200 		ENetEvent event;
201 		while (enet_host_service(host, &event, 0) > 0)
202 		{
203 			final switch (event.type)
204 			{
205 				case ENET_EVENT_TYPE_NONE:
206 					break;
207 				case ENET_EVENT_TYPE_CONNECT:
208 					onConnect(event);
209 					break;
210 				case ENET_EVENT_TYPE_RECEIVE:
211 					onPacketReceived(event);
212 					break;
213 				case ENET_EVENT_TYPE_DISCONNECT:
214 					onDisconnect(event);
215 					break;
216 			}
217 		}
218 	}
219 
220 	void onConnect(ref ENetEvent event)
221 	{
222 		if (connectHandler) connectHandler(event);
223 	}
224 
225 	void onPacketReceived(ref ENetEvent event)
226 	{
227 		ubyte[] packetData = event.packet.data[0..event.packet.dataLength];
228 		auto fullPacketData = packetData;
229 		size_t packetId;
230 
231 		try
232 		{
233 			// decodes and pops ulong from range.
234 			packetId = cast(size_t)decodeCborSingle!ulong(packetData);
235 
236 			handlePacket(packetId, packetData, cast(ClientId)event.peer.data);
237 		}
238 		catch(CborException e)
239 		{
240 			error(e.to!string);
241 			errorf("packet:%s length:%s data:%(%x%)", packetName(packetId), event.packet.dataLength, fullPacketData);
242 			printCborStream(fullPacketData);
243 		}
244 	}
245 
246 	void onDisconnect(ref ENetEvent event)
247 	{
248 		if (disconnectHandler) disconnectHandler(event);
249 	}
250 }