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: Copyright (c) 2017-2018 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/sel-hncom/sel/hncom/util.d, sel/hncom/util.d)
28  */
29 module sel.hncom.util;
30 
31 import std.conv : to;
32 import std.zlib : Compress, UnCompress, HeaderFormat;
33 
34 import sel.hncom.about;
35 import sel.hncom.io : IO;
36 
37 /**
38  * Group of packets.
39  */
40 @clientbound @serverbound struct Uncompressed {
41 
42 	enum ubyte ID = 5;
43 
44 	/**
45 	 * If not 0 the same packet in the next field has the same id.
46 	 * Otherwise the packet id is the first byte of the packet.
47 	 */
48 	ubyte id;
49 
50 	/**
51 	 * List of the encoded packets. With the ID if the id field is 0,
52 	 * without the ID otherwise.
53 	 */
54 	ubyte[][] packets;
55 
56 	mixin IO!(id, packets);
57 
58 	/**
59 	 * Adds a serialised packet to the array of packets.
60 	 * Example:
61 	 * ---
62 	 * // same packet
63 	 * auto uc = Uncompressed(RemoveWorld.ID);
64 	 * uc.add(RemoveWorld(12).encode());
65 	 * uc.add(RemoveWorld(13));
66 	 * 
67 	 * // different packets
68 	 * auto uc = Uncompressed(0);
69 	 * uc.add(AddWorld(23, "test", 0));
70 	 * uc.add(RemoveWorld(20));
71 	 * ---
72 	 */
73 	typeof(this) add(ubyte[] packet) {
74 		if(this.id == 0) {
75 			this.packets ~= packet;
76 		} else {
77 			assert(packet[0] == this.id);
78 			this.packets ~= packet[1..$];
79 		}
80 		return this;
81 	}
82 
83 	/// ditto
84 	typeof(this) add(T)(T packet) if(is(typeof(T.encode))) {
85 		return this.add(packet.encode());
86 	}
87 
88 	/**
89 	 * Creates a packet and start to add packets.
90 	 */
91 	static Uncompressed fromPackets(ubyte[][] packets...) {
92 		auto ret = Uncompressed(0);
93 		foreach(packet ; packets) {
94 			ret.add(packet);
95 		}
96 		return ret;
97 	}
98 
99 	/**
100 	 * Creates an Uncompress packet from a list of packets
101 	 * of the same type.
102 	 * Example:
103 	 * ---
104 	 * Uncompress.fromPackets(RemoveWorld(3), RemoveWorld(4), RemoveWorld(5));
105 	 * Uncompress.fromPackets([RemoveWorld(1), RemoveWorld(44)]);
106 	 * ---
107 	 */
108 	static Uncompressed fromPackets(T)(T[] packets...) if(is(typeof(T.encode))) {
109 		auto ret = Uncompressed(T.ID);
110 		foreach(packet ; packets) {
111 			ret.packets ~= packet.encode()[1..$]; // remove ID
112 		}
113 		return ret;
114 	}
115 
116 }
117 
118 /**
119  * Compressed packets.
120  */
121 @clientbound @serverbound struct Compressed {
122 
123 	enum ubyte ID = 6;
124 
125 	/**
126 	 * Length of the uncompressed buffer.
127 	 */
128 	uint length;
129 
130 	/**
131 	 * Same as Uncompressed's id field.
132 	 */
133 	ubyte id;
134 
135 	/**
136 	 * Compressed data.
137 	 */
138 	ubyte[] payload;
139 
140 	mixin IO!(length, id, payload);
141 
142 	/**
143 	 * Creates a Compressed from an Uncompressed packet.
144 	 * The Uncompressed packet is encoded and the data is compressed using
145 	 * zlib's deflate algorithm.
146 	 * Example:
147 	 * ---
148 	 * Compress.compress(Uncompress.fromPackets(RemoveWorld(1), RemoveWorld(2)));
149 	 * ---
150 	 */
151 	static Compressed compress(Uncompressed uncompressed, int level=6) {
152 		ubyte[] buffer = uncompressed.encode()[1..$];
153 		auto ret = Compressed(buffer.length.to!uint, uncompressed.id);
154 		Compress compress = new Compress(level, HeaderFormat.deflate);
155 		ret.payload = cast(ubyte[])compress.compress(buffer);
156 		ret.payload ~= cast(ubyte[])compress.flush();
157 		return ret;
158 	}
159 
160 	/**
161 	 * Creates a Compressed from a list of encoded packets.
162 	 */
163 	static Compressed compress(ubyte[][] packets...) {
164 		assert(packets.length);
165 		ubyte id = packets[0][0];
166 		foreach(packet ; packets[1..$]) {
167 			if(packet[0] != id) {
168 				id = 0;
169 				break;
170 			}
171 		}
172 		if(id != 0) {
173 			// remove ids
174 			foreach(ref packet ; packets) {
175 				packet = packet[1..$];
176 			}
177 		}
178 		return compress(Uncompressed(id, packets));
179 	}
180 
181 	/**
182 	 * Uncompresses the data and returns an Uncompressed packet.
183 	 * Example:
184 	 * ---
185 	 * auto c = Compressed.fromBuffer(buffer);
186 	 * Uncompressed uc = c.uncompress();
187 	 * ---
188 	 */
189 	Uncompressed uncompress() {
190 		UnCompress uncompress = new UnCompress(this.length);
191 		ubyte[] buffer = cast(ubyte[])uncompress.uncompress(this.payload);
192 		buffer ~= cast(ubyte[])uncompress.flush();
193 		return Uncompressed.fromBuffer(buffer);
194 	}
195 
196 }
197 
198 unittest {
199 
200 	import sel.hncom.status;
201 
202 	Uncompressed uc;
203 
204 	uc.add(RemoveNode(44));
205 	uc.add(RemoveWorld(12).encode());
206 	assert(uc.encode() == [Uncompressed.ID, 0, 2, 2, RemoveNode.ID, 44, 2, RemoveWorld.ID, 12]);
207 
208 	uc = Uncompressed(RemoveNode.ID);
209 	uc.add(RemoveNode(4));
210 	assert(uc.encode() == [Uncompressed.ID, RemoveNode.ID, 1, 1, 4]);
211 
212 	uc = Uncompressed.fromPackets(RemoveWorld(13).encode(), RemoveWorld(2).encode(), RemoveNode(55).encode());
213 	assert(uc.encode() == [Uncompressed.ID, 0, 3, 2, RemoveWorld.ID, 13, 2, RemoveWorld.ID, 2, 2, RemoveNode.ID, 55]);
214 
215 	uc = Uncompressed.fromPackets(RemoveWorld(1), RemoveWorld(2));
216 	assert(uc.encode() == [Uncompressed.ID, RemoveWorld.ID, 2, 1, 1, 1, 2]);
217 
218 	Compressed c;
219 
220 	c = Compressed.compress(RemoveWorld(1).encode(), RemoveWorld(50).encode());
221 	c.encode();
222 
223 	c = Compressed.compress(RemoveNode(43).encode(), RemoveWorld(11).encode());
224 	c.encode();
225 
226 	c = Compressed.compress(uc);
227 	c.encode();
228 
229 }