1 module shark.util;
2 
3 import std.array : Appender;
4 import std.conv : to;
5 import std.socket : Socket, SocketException, lastSocketError;
6 import std.system : Endian, endian;
7 
8 import xbuffer : Buffer;
9 
10 // debug
11 import std.stdio;
12 
13 class Stream(size_t idLength, Endian endianness, size_t length, bool lengthIncludesItself, Endian sequenceEndianness=Endian.littleEndian, S=Object) {
14 
15 	private enum usesSequence = !is(S == Object);
16 
17 	protected Socket _socket;
18 	
19 	private void[] _recv;
20 	private Buffer _buffer;
21 	private union {
22 		version(LittleEndian) struct {
23 			ubyte[length] _lengthData;
24 			ubyte[size_t.sizeof-length] filler;
25 		}
26 		version(BigEndian) struct {
27 			ubyte[size_t.sizeof-length] filler;
28 			ubyte[length] _lengthData;
29 		}
30 		size_t _length;
31 	}
32 
33 	private Buffer _returnBuffer;
34 
35 	static if(idLength) private void[idLength] _id;
36 	
37 	static if(usesSequence) private S sequence;
38 	
39 	public this(Socket socket, size_t buffer) {
40 		_socket = socket;
41 		_recv = new void[buffer];
42 		_buffer = new Buffer(buffer);
43 		_returnBuffer = new Buffer(buffer);
44 	}
45 
46 	public @property Socket socket() {
47 		return _socket;
48 	}
49 
50 	static if(idLength) public @property T[idLength] id(T=void)() {
51 		return cast(T[idLength])_id;
52 	}
53 
54 	static if(idLength) public @property T[idLength] id(T)(T[idLength] id) {
55 		_id = cast(void[])id;
56 		return id;
57 	}
58 	
59 	static if(usesSequence) public void resetSequence() {
60 		sequence = 0;
61 	}
62 	
63 	public Buffer receive() {
64 		if(_length == 0) return readLength();
65 		else return readBody();
66 	}
67 	
68 	private void receiveImpl() {
69 		immutable recv = _socket.receive(_recv);
70 		if(recv == Socket.ERROR) throw new SocketException(lastSocketError);
71 		else if(recv == 0) throw new SocketException("Connection timed out");
72 		_buffer.writeData(_recv[0..recv]);
73 	}
74 	
75 	private Buffer readLength() {
76 		static if(idLength) immutable requiredLength = idLength + length;
77 		else immutable requiredLength = length;
78 		if(_buffer.canRead(requiredLength)) {
79 			static if(idLength) _id = _buffer.readData(idLength);
80 			_lengthData = _buffer.read!(ubyte[])(length);
81 			static if(endianness != endian) reverse(_lengthData);
82 			static if(lengthIncludesItself) _length -= length;
83 			return readSequence();
84 		} else {
85 			receiveImpl();
86 			return readLength();
87 		}
88 	}
89 	
90 	private Buffer readSequence() {
91 		static if(usesSequence) {
92 			if(_buffer.canRead(S.sizeof)) {
93 				static if(usesSequence) {
94 					_buffer.read!(sequenceEndianness, S)();
95 					sequence++;
96 				}
97 				return readBody();
98 			} else {
99 				receiveImpl();
100 				return readSequence();
101 			}
102 		} else {
103 			return readBody();
104 		}
105 	}
106 	
107 	private Buffer readBody() {
108 		if(_buffer.data.length >= _length) {
109 			_returnBuffer.data = _buffer.readData(_length);
110 			_length = 0;
111 			return _returnBuffer;
112 		} else {
113 			receiveImpl();
114 			return readBody();
115 		}
116 	}
117 	
118 	public void send(Buffer buffer) {
119 		writeLength(buffer);
120 		static if(usesSequence) buffer.write!(sequenceEndianness, S)(sequence++, length);
121 		static if(idLength) buffer.write(_id, 0);
122 		if(_socket.send(buffer.data) == Socket.ERROR) throw new SocketException(lastSocketError);
123 	}
124 	
125 	protected void writeLength(Buffer buffer) {
126 		immutable rlength = _length;
127 		_length = buffer.data.length;
128 		static if(lengthIncludesItself) _length += length;
129 		static if(endianness != endian) reverse(_lengthData);
130 		buffer.write(_lengthData, 0);
131 		_length = rlength;
132 	}
133 
134 }
135 
136 private void reverse(T)(ref T array) {
137 	T ret;
138 	foreach(i ; 0..array.length) {
139 		ret[$-1-i] = array[i];
140 	}
141 	array = ret;
142 }
143 
144 string read0String(Buffer buffer) {
145 	Appender!string ret;
146 	char c;
147 	while((c = buffer.read!char()) != '\0') {
148 		ret.put(c);
149 	}
150 	return ret.data;
151 }
152 
153 void write0String(Buffer buffer, string str) {
154 	buffer.writeData(cast(void[])str);
155 	buffer.write(ubyte(0));
156 }
157 
158 ubyte[] fromHexString(string hex) {
159 	ubyte[] ret = new ubyte[hex.length / 2];
160 	foreach(i ; 0..ret.length) {
161 		ret[i] = to!ubyte(hex[i*2..i*2+2], 16);
162 	}
163 	return ret;
164 }
165 
166 string toSnakeCase(string input) {
167 	Appender!string output;
168 	foreach(c ; input) {
169 		if(c >= 'A' && c <= 'Z') {
170 			output.put('_');
171 			output.put(cast(char)(c + 32));
172 		} else {
173 			output.put(c);
174 		}
175 	}
176 	return output.data;
177 }
178 
179 unittest {
180 
181 	assert("testTest".toSnakeCase() == "test_test");
182 
183 }