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 MySQLReceivedException MYXReceived;
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(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 }