1 module vibe.rcon.protocol; 2 3 import std.format; 4 import std.bitmanip; 5 import std.exception; 6 7 import vibe.core.net; 8 import vibe.core.stream; 9 10 struct RCONPacket { 11 @safe: 12 13 enum Type { 14 AUTH = 3, 15 AUTH_RESPONSE = 2, 16 EXEC_COMMAND = 2, 17 RESPONSE_VALUE = 0, 18 } 19 20 int id; 21 Type type; 22 string message; 23 24 static RCONPacket read(S)(S stream) if (isInputStream!S) { 25 ubyte[4] sizeBuffer; 26 stream.read(sizeBuffer); 27 auto size = littleEndianToNative!int(sizeBuffer); 28 enforce(size >= 10 && size <= 4096, "Invalid packet size"); 29 30 auto buffer = new ubyte[size]; 31 stream.read(buffer); 32 return RCONPacket.fromBuffer(buffer); 33 } 34 35 static RCONPacket fromBuffer(ubyte[] data) @trusted { 36 auto id = data.read!(int, Endian.littleEndian); 37 auto type = data.read!(Type, Endian.littleEndian); 38 string message = null; 39 40 // Some messages are plain empty, including no \0 at the end 41 if (data.length > 2) { 42 // TODO: encode? 43 message = cast(string)data[0..data.length - 3]; 44 } 45 46 return RCONPacket(id, type, message); 47 } 48 49 unittest { 50 auto data = 51 nativeToLittleEndian(5) ~ 52 nativeToLittleEndian(3) ~ 53 cast(ubyte[])[0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]; 54 55 auto packet = RCONPacket.fromBuffer(data); 56 assert(packet.id == 5); 57 assert(packet.type == 3); 58 assert(packet.message == "foo"); 59 } 60 61 this(int id, Type type, string message) { 62 this.id = id; 63 this.type = type; 64 this.message = message; 65 } 66 67 @property int size() { 68 // Null character + header size 69 return cast(int)(message.length + 1 + 10); 70 } 71 72 ubyte[] header() @trusted { 73 auto data = new ubyte[12]; 74 data.write!(int, Endian.littleEndian)(size, 0); 75 data.write!(int, Endian.littleEndian)(id, 4); 76 data.write!(int, Endian.littleEndian)(type, 8); 77 return data; 78 } 79 80 ubyte[] toBuffer() @trusted { 81 auto data = new ubyte[size + 4]; 82 size_t offset = 0; 83 84 data[offset..offset + 12] = header(); 85 offset += 12; 86 87 // TODO: decode? 88 data[offset..offset + message.length] = cast(ubyte[])message; 89 offset += message.length; 90 91 data[offset..offset + 3] = [0x00, 0x00, 0x00]; 92 return data; 93 } 94 95 unittest { 96 auto packet = RCONPacket(5, Type.AUTH, "foo"); 97 98 assert(packet.toBuffer() == 99 nativeToLittleEndian(14) ~ 100 nativeToLittleEndian(5) ~ 101 nativeToLittleEndian(3) ~ 102 cast(ubyte[])[0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]); 103 } 104 105 void write(O)(O stream) if (isOutputStream!O) { 106 stream.write(toBuffer()); 107 } 108 109 string toString() { 110 return "RCON(%s, %s, %s)".format(id, type, message); 111 } 112 }