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