1 module connection;
2 
3 import std.stdio;
4 
5 import derelict.enet.enet;
6 import cbor;
7 
8 
9 //version = debug_packets;
10 
11 // Stores info about connected peer. Used in server
12 alias UserId = size_t;
13 
14 /// Packet handler.
15 /// Returns true if data was valid and false otherwise.
16 alias PacketHandler = void delegate(ubyte[] packetData, UserId peer);
17 
18 struct PacketInfo
19 {
20 	string name;
21 	PacketHandler handler;
22 	TypeInfo packetType;
23 	size_t id;
24 }
25 
26 abstract class Connection
27 {
28 	// Local side of connection.
29 	ENetHost* host;
30 
31 	// Used when handling packet based on its id.
32 	PacketInfo[] packetArray;
33 	// Used to get packet id when sending packet.
34 	PacketInfo[TypeInfo] packetMap;
35 
36 	ubyte[] buffer = new ubyte[1024*1024];
37 
38 	string side;
39 
40 	size_t packetId(P)()
41 	{
42 		return packetMap[typeid(P)].id;
43 	}
44 
45 	string packetName(size_t packetId)
46 	{
47 		if (packetId >= packetArray.length) return "!Unknown!";
48 		return packetArray[packetId].name;
49 	}
50 
51 	void registerPacket(P)(PacketHandler handler = null, string packetName = P.stringof)
52 	{
53 		size_t newId = packetArray.length;
54 		PacketInfo pinfo = PacketInfo(packetName, handler, typeid(P), newId);
55 		packetArray ~= pinfo;
56 		assert(typeid(P) !in packetMap);
57 		packetMap[typeid(P)] = pinfo;
58 	}
59 
60 	bool handlePacket(size_t packetId, ubyte[] packetData, UserId peerInfo)
61 	{
62 		if (packetId >= packetArray.length)
63 			return false; // invalid packet
64 
65 		auto handler = packetArray[packetId].handler;
66 		if (handler is null)
67 			return false; // handler is not set
68 
69 		handler(packetData, peerInfo);
70 		return true;
71 	}
72 
73 	ubyte[] createPacket(P)(auto ref P packet)
74 	{
75 		ubyte[] bufferTemp = buffer;
76 		size_t size;
77 
78 		version(debug_packets) writefln("%s: creating packet %s with id %s", side, P.stringof, packetId!P);
79 		version(debug_packets) writefln("%s: with fields {%s}:%s", side, packet.tupleof, numEncodableMembers!(packet));
80 		
81 		size = encodeCbor(bufferTemp[], packetId!P);
82 		
83 		version(debug_packets) writef("%s: size %s, bufferTemp[0]%02x", side, size, bufferTemp[0]);
84 		version(debug_packets) writefln(" %s", decodeCbor(bufferTemp[0..1]));
85 		
86 		size += encodeCbor(bufferTemp[size..$], packet);
87 		return bufferTemp[0..size];
88 	}
89 
90 	// packetData must contain data with packet id stripped off.
91 	auto ref P unpackPacket(P)(ubyte[] packetData)
92 	{
93 		return decodeCborSingleDup!P(packetData); // TODO: check for excess data.
94 	}
95 
96 	void printPacketMap()
97 	{
98 		foreach(i, packetInfo; packetArray)
99 		{
100 			writefln("% 2s: %s", i, packetInfo.name);
101 		}
102 	}
103 
104 	void flush()
105 	{
106 		enet_host_flush(host);
107 	}
108 
109 	void stop()
110 	{
111 		enet_host_destroy(host);
112 	}
113 
114 	void update(uint msecs = 1000)
115 	{
116 		ENetEvent event;
117 		int eventStatus = enet_host_service(host, &event, msecs);
118 
119 		if (eventStatus == 0) return;
120 
121 		final switch (event.type)
122 		{
123 			case ENET_EVENT_TYPE_NONE:
124 				break;
125 			case ENET_EVENT_TYPE_CONNECT:
126 				onConnect(event);
127 				break;
128 			case ENET_EVENT_TYPE_RECEIVE:
129 				onPacketReceived(event);
130 				break;
131 			case ENET_EVENT_TYPE_DISCONNECT:
132 				onDisconnect(event);
133 				break;
134 		}
135 	}
136 
137 	void onConnect(ref ENetEvent event)
138 	{
139 	}
140 
141 	void onPacketReceived(ref ENetEvent event)
142 	{
143 		try
144 		{
145 			ubyte[] packetData = event.packet.data[0..event.packet.dataLength];
146 			auto fullPacketData = packetData;
147 			size_t packetId = cast(size_t)decodeCborSingle!ulong(packetData); // decodes and pops ulong from range.
148 
149 			version(debug_packets) writefln("%s: %s:%s received len %s | text %s | hex %(%02x%)",
150 				side, packetName(packetId), packetId,
151 				event.packet.dataLength, cast(char[])fullPacketData, fullPacketData);
152 
153 			handlePacket(packetId, packetData, cast(UserId)event.peer.data);
154 		}
155 		catch(CborException e)
156 		{
157 			writeln(e);
158 		}
159 	}
160 
161 	void onDisconnect(ref ENetEvent event)
162 	{
163 	}
164 }