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