1 module mysql.protocol.packets;
2 
3 import std.algorithm;
4 import std.conv;
5 import std.datetime;
6 import std.digest.sha;
7 import std.exception;
8 import std.range;
9 import std.socket;
10 import std.stdio;
11 import std.string;
12 import std.traits;
13 import std.variant;
14 
15 import mysql.common;
16 import mysql.protocol.constants;
17 import mysql.protocol.extra_types;
18 public import mysql.protocol.packet_helpers;
19 
20 /++
21 The server sent back a MySQL error code and message. If the server is 4.1+,
22 there should also be an ANSI/ODBC-standard SQLSTATE error code.
23 
24 See_Also: https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
25 +/
26 class MySQLReceivedException: MySQLException
27 {
28 	ushort errorCode;
29 	char[5] sqlState;
30 
31 	this(OKErrorPacket okp, string file, size_t line) pure
32 	{
33 		this(okp.message, okp.serverStatus, okp.sqlState, file, line);
34 	}
35 
36 	this(string msg, ushort errorCode, char[5] sqlState, string file, size_t line) pure
37 	{
38 		this.errorCode = errorCode;
39 		this.sqlState = sqlState;
40 		super("MySQL error: " ~ msg, file, line);
41 	}
42 }
43 alias MYXReceived = MySQLReceivedException;
44 
45 void enforcePacketOK(string file = __FILE__, size_t line = __LINE__)(OKErrorPacket okp)
46 {
47 	enforce(!okp.error, new MYXReceived(okp, file, line));
48 }
49 
50 /++
51 A struct representing an OK or Error packet
52 See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Types_Of_Result_Packets
53 OK packets begin with a zero byte - Error packets with 0xff
54 +/
55 struct OKErrorPacket
56 {
57 	bool     error;
58 	ulong    affected;
59 	ulong    insertID;
60 	ushort   serverStatus;
61 	ushort   warnings;
62 	char[5]  sqlState;
63 	string   message;
64 
65 	this(ubyte[] packet)
66 	{
67 		if (packet.front == ResultPacketMarker.error)
68 		{
69 			packet.popFront(); // skip marker/field code
70 			error = true;
71 
72 			enforceEx!MYXProtocol(packet.length > 2, "Malformed Error packet - Missing error code");
73 			serverStatus = packet.consume!short(); // error code into server state
74 			if (packet.front == cast(ubyte) '#') //4.1+ error packet
75 			{
76 				packet.popFront(); // skip 4.1 marker
77 				enforceEx!MYXProtocol(packet.length > 5, "Malformed Error packet - Missing SQL state");
78 				sqlState[] = (cast(char[])packet[0 .. 5])[];
79 				packet = packet[5..$];
80 			}
81 		}
82 		else if(packet.front == ResultPacketMarker.ok)
83 		{
84 			packet.popFront(); // skip marker/field code
85 
86 			enforceEx!MYXProtocol(packet.length > 1, "Malformed OK packet - Missing affected rows");
87 			auto lcb = packet.consumeIfComplete!LCB();
88 			assert(!lcb.isNull);
89 			assert(!lcb.isIncomplete);
90 			affected = lcb.value;
91 
92 			enforceEx!MYXProtocol(packet.length > 1, "Malformed OK packet - Missing insert id");
93 			lcb = packet.consumeIfComplete!LCB();
94 			assert(!lcb.isNull);
95 			assert(!lcb.isIncomplete);
96 			insertID = lcb.value;
97 
98 			enforceEx!MYXProtocol(packet.length > 2,
99 					format("Malformed OK packet - Missing server status. Expected length > 2, got %d", packet.length));
100 			serverStatus = packet.consume!short();
101 
102 			enforceEx!MYXProtocol(packet.length >= 2, "Malformed OK packet - Missing warnings");
103 			warnings = packet.consume!short();
104 		}
105 		else
106 			throw new MYXProtocol("Malformed OK/Error packet - Incorrect type of packet", __FILE__, __LINE__);
107 
108 		// both OK and Error packets end with a message for the rest of the packet
109 		message = cast(string)packet.idup;
110 	}
111 }
112 
113 /++
114 A struct representing a field (column) description packet
115 
116 These packets, one for each column are sent before the data of a result set,
117 followed by an EOF packet.
118 
119 See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Field_Packet
120 +/
121 struct FieldDescription
122 {
123 private:
124 	string   _db;
125 	string   _table;
126 	string   _originalTable;
127 	string   _name;
128 	string   _originalName;
129 	ushort   _charSet;
130 	uint     _length;
131 	SQLType  _type;
132 	FieldFlags _flags;
133 	ubyte    _scale;
134 	ulong    _deflt;
135 	uint     chunkSize;
136 	void delegate(const(ubyte)[], bool) chunkDelegate;
137 
138 public:
139 	/++
140 	Construct a FieldDescription from the raw data packet
141 	
142 	Parameters: packet = The packet contents excluding the 4 byte packet header
143 	+/
144 	this(ubyte[] packet)
145 	in
146 	{
147 		assert(packet.length);
148 	}
149 	out
150 	{
151 		assert(!packet.length, "not all bytes read during FieldDescription construction");
152 	}
153 	body
154 	{
155 		packet.skip(4); // Skip catalog - it's always 'def'
156 		_db             = packet.consume!LCS();
157 		_table          = packet.consume!LCS();
158 		_originalTable  = packet.consume!LCS();
159 		_name           = packet.consume!LCS();
160 		_originalName   = packet.consume!LCS();
161 
162 		enforceEx!MYXProtocol(packet.length >= 13, "Malformed field specification packet");
163 		packet.popFront(); // one byte filler here
164 		_charSet    = packet.consume!short();
165 		_length     = packet.consume!int();
166 		_type       = cast(SQLType)packet.consume!ubyte();
167 		_flags      = cast(FieldFlags)packet.consume!short();
168 		_scale      = packet.consume!ubyte();
169 		packet.skip(2); // two byte filler
170 
171 		if(packet.length)
172 		{
173 			packet.skip(1); // one byte filler
174 			auto lcb = packet.consumeIfComplete!LCB();
175 			assert(!lcb.isNull);
176 			assert(!lcb.isIncomplete);
177 			_deflt = lcb.value;
178 		}
179 	}
180 
181 	/// Database name for column as string
182 	@property string db() pure const nothrow { return _db; }
183 
184 	/// Table name for column as string - this could be an alias as in 'from tablename as foo'
185 	@property string table() pure const nothrow { return _table; }
186 
187 	/// Real table name for column as string
188 	@property string originalTable() pure const nothrow { return _originalTable; }
189 
190 	/// Column name as string - this could be an alias
191 	@property string name() pure const nothrow { return _name; }
192 
193 	/// Real column name as string
194 	@property string originalName() pure const nothrow { return _originalName; }
195 
196 	/// The character set in force
197 	@property ushort charSet() pure const nothrow { return _charSet; }
198 
199 	/// The 'length' of the column as defined at table creation
200 	@property uint length() pure const nothrow { return _length; }
201 
202 	/// The type of the column hopefully (but not always) corresponding to enum SQLType.
203 	/// Only the low byte currently used.
204 	@property SQLType type() pure const nothrow { return _type; }
205 
206 	/// Column flags - unsigned, binary, null and so on
207 	@property FieldFlags flags() pure const nothrow { return _flags; }
208 
209 	/// Precision for floating point values
210 	@property ubyte scale() pure const nothrow { return _scale; }
211 
212 	/// NotNull from flags
213 	@property bool notNull() pure const nothrow { return (_flags & FieldFlags.NOT_NULL) != 0; }
214 
215 	/// Unsigned from flags
216 	@property bool unsigned() pure const nothrow { return (_flags & FieldFlags.UNSIGNED) != 0; }
217 
218 	/// Binary from flags
219 	@property bool binary() pure const nothrow { return (_flags & FieldFlags.BINARY) != 0; }
220 
221 	/// Is-enum from flags
222 	@property bool isenum() pure const nothrow { return (_flags & FieldFlags.ENUM) != 0; }
223 
224 	/// Is-set (a SET column that is) from flags
225 	@property bool isset() pure const nothrow { return (_flags & FieldFlags.SET) != 0; }
226 
227 	void show() const
228 	{
229 		writefln("%s %d %x %016b", _name, _length, _type, _flags);
230 	}
231 }
232 
233 /++
234 A struct representing a prepared statement parameter description packet
235 
236 These packets, one for each parameter are sent in response to the prepare
237 command, followed by an EOF packet.
238 
239 Sadly it seems that this facility is only a stub. The correct number of
240 packets is sent, but they contain no useful information and are all the same.
241 +/
242 struct ParamDescription
243 {
244 private:
245 	ushort _type;
246 	FieldFlags _flags;
247 	ubyte _scale;
248 	uint _length;
249 
250 public:
251 	this(ubyte[] packet)
252 	{
253 		_type   = packet.consume!short();
254 		_flags  = cast(FieldFlags)packet.consume!short();
255 		_scale  = packet.consume!ubyte();
256 		_length = packet.consume!int();
257 		assert(!packet.length);
258 	}
259 	@property uint length() pure const nothrow { return _length; }
260 	@property ushort type() pure const nothrow { return _type; }
261 	@property FieldFlags flags() pure const nothrow { return _flags; }
262 	@property ubyte scale() pure const nothrow { return _scale; }
263 	@property bool notNull() pure const nothrow { return (_flags & FieldFlags.NOT_NULL) != 0; }
264 	@property bool unsigned() pure const nothrow { return (_flags & FieldFlags.UNSIGNED) != 0; }
265 }
266 
267 bool isEOFPacket(in ubyte[] packet) pure nothrow
268 in
269 {
270 	assert(!packet.empty);
271 }
272 body
273 {
274 	return packet.front == ResultPacketMarker.eof && packet.length < 9;
275 }
276 
277 /++
278 A struct representing an EOF packet from the server
279 
280 An EOF packet is sent from the server after each sequence of field
281 description and parameter description packets, and after a sequence of
282 result set row packets.
283 An EOF packet is also called "Last Data Packet" or "End Packet".
284 
285 These EOF packets contain a server status and a warning count.
286 
287 See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#EOF_Packet
288 +/
289 struct EOFPacket
290 {
291 private:
292 	ushort _warnings;
293 	ushort _serverStatus;
294 
295 public:
296 
297 	/++
298 	Construct an EOFPacket struct from the raw data packet
299 	
300 	Parameters: packet = The packet contents excluding the 4 byte packet header
301 	+/
302 	this(ubyte[] packet)
303 	in
304 	{
305 		assert(packet.isEOFPacket());
306 		assert(packet.length == 5);
307 	}
308 	out
309 	{
310 		assert(!packet.length);
311 	}
312 	body
313 	{
314 		packet.popFront(); // eof marker
315 		_warnings = packet.consume!short();
316 		_serverStatus = packet.consume!short();
317 	}
318 
319 	/// Retrieve the warning count
320 	@property ushort warnings() pure const nothrow { return _warnings; }
321 
322 	/// Retrieve the server status
323 	@property ushort serverStatus() pure const nothrow { return _serverStatus; }
324 }
325 
326 
327 /++
328 A struct representing the collation of a sequence of FieldDescription packets.
329 
330 This data gets filled in after a query (prepared or otherwise) that creates
331 a result set completes. All the FD packets, and an EOF packet must be eaten
332 before the row data packets can be read.
333 +/
334 struct ResultSetHeaders
335 {
336 	import mysql.connection;
337 
338 private:
339 	FieldDescription[] _fieldDescriptions;
340 	string[] _fieldNames;
341 	ushort _warnings;
342 
343 public:
344 
345 	/++
346 	Construct a ResultSetHeaders struct from a sequence of FieldDescription
347 	packets and an EOF packet.
348 	
349 	Parameters:
350 		con = A Connection via which the packets are read
351 		fieldCount = the number of fields/columns generated by the query
352 	+/
353 	this(Connection con, uint fieldCount)
354 	{
355 		scope(failure) con.kill();
356 
357 		_fieldNames.length = _fieldDescriptions.length = fieldCount;
358 		foreach (size_t i; 0 .. fieldCount)
359 		{
360 			auto packet = con.getPacket();
361 			enforceEx!MYXProtocol(!packet.isEOFPacket(),
362 					"Expected field description packet, got EOF packet in result header sequence");
363 
364 			_fieldDescriptions[i]   = FieldDescription(packet);
365 			_fieldNames[i]          = _fieldDescriptions[i]._name;
366 		}
367 		auto packet = con.getPacket();
368 		enforceEx!MYXProtocol(packet.isEOFPacket(),
369 				"Expected EOF packet in result header sequence");
370 		auto eof = EOFPacket(packet);
371 		con._serverStatus = eof._serverStatus;
372 		_warnings = eof._warnings;
373 	}
374 
375 	/++
376 	Add specialization information to one or more field descriptions.
377 	
378 	Currently the only specialization supported is the capability to deal with long data
379 	e.g. BLOB or TEXT data in chunks by stipulating a chunkSize and a delegate to sink
380 	the data.
381 	
382 	Parameters:
383 		csa = An array of ColumnSpecialization structs
384 	+/
385 	void addSpecializations(ColumnSpecialization[] csa)
386 	{
387 		foreach(CSN csn; csa)
388 		{
389 			enforceEx!MYX(csn.cIndex < fieldCount && _fieldDescriptions[csn.cIndex].type == csn.type,
390 					"Column specialization index or type does not match the corresponding column.");
391 			_fieldDescriptions[csn.cIndex].chunkSize = csn.chunkSize;
392 			_fieldDescriptions[csn.cIndex].chunkDelegate = csn.chunkDelegate;
393 		}
394 	}
395 
396 	/// Index into the set of field descriptions
397 	FieldDescription opIndex(size_t i) pure nothrow { return _fieldDescriptions[i]; }
398 	/// Get the number of fields in a result row.
399 	@property size_t fieldCount() pure const nothrow { return _fieldDescriptions.length; }
400 	/// Get the warning count as per the EOF packet
401 	@property ushort warnings() pure const nothrow { return _warnings; }
402 	/// Get an array of strings representing the column names
403 	@property string[] fieldNames() pure nothrow { return _fieldNames; }
404 	/// Get an array of the field descriptions
405 	@property FieldDescription[] fieldDescriptions() pure nothrow { return _fieldDescriptions; }
406 
407 	void show() const
408 	{
409 		foreach (FieldDescription fd; _fieldDescriptions)
410 			fd.show();
411 	}
412 }
413 
414 /++
415 A struct representing the collation of a prepared statement parameter description sequence
416 
417 As noted above - parameter descriptions are not fully implemented by MySQL.
418 +/
419 struct PreparedStmtHeaders
420 {
421 	import mysql.connection;
422 	
423 package:
424 	Connection _con;
425 	ushort _colCount, _paramCount;
426 	FieldDescription[] _colDescriptions;
427 	ParamDescription[] _paramDescriptions;
428 	ushort _warnings;
429 
430 	bool getEOFPacket()
431 	{
432 		auto packet = _con.getPacket();
433 		if (!packet.isEOFPacket())
434 			return false;
435 		EOFPacket eof = EOFPacket(packet);
436 		_con._serverStatus = eof._serverStatus;
437 		_warnings += eof._warnings;
438 		return true;
439 	}
440 
441 public:
442 	this(Connection con, ushort cols, ushort params)
443 	{
444 		scope(failure) con.kill();
445 
446 		_con = con;
447 		_colCount = cols;
448 		_paramCount = params;
449 		_colDescriptions.length = cols;
450 		_paramDescriptions.length = params;
451 
452 		// The order in which fields are sent is params first, followed by EOF,
453 		// then cols followed by EOF The parameter specs are useless - they are
454 		// all the same. This observation is coroborated by the fact that the
455 		// C API does not have any information about parameter types either.
456 		// WireShark gives up on these records also.
457 		foreach (size_t i; 0.._paramCount)
458 			_con.getPacket();  // just eat them - they are not useful
459 
460 		if (_paramCount)
461 			enforceEx!MYXProtocol(getEOFPacket(), "Expected EOF packet in result header sequence");
462 
463 		foreach(size_t i; 0.._colCount)
464 		   _colDescriptions[i] = FieldDescription(_con.getPacket());
465 
466 		if (_colCount)
467 			enforceEx!MYXProtocol(getEOFPacket(), "Expected EOF packet in result header sequence");
468 	}
469 
470 	ParamDescription param(size_t i) pure const nothrow { return _paramDescriptions[i]; }
471 	FieldDescription col(size_t i) pure const nothrow { return _colDescriptions[i]; }
472 
473 	@property ParamDescription[] paramDescriptions() pure nothrow { return _paramDescriptions; }
474 	@property FieldDescription[] fieldDescriptions() pure nothrow { return _colDescriptions; }
475 
476 	@property paramCount() pure const nothrow { return _paramCount; }
477 	@property ushort warnings() pure const nothrow { return _warnings; }
478 
479 	void showCols() const
480 	{
481 		writefln("%d columns", _colCount);
482 		foreach (FieldDescription fd; _colDescriptions)
483 		{
484 			writefln("%10s %10s %10s %10s %10s %d %d %02x %016b %d",
485 					fd._db, fd._table, fd._originalTable, fd._name, fd._originalName,
486 					fd._charSet, fd._length, fd._type, fd._flags, fd._scale);
487 		}
488 	}
489 }