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 }