1 /// Internal - Helper functions for the communication protocol. 2 module mysql.protocol.packet_helpers; 3 4 import std.algorithm; 5 import std.conv; 6 import std.datetime; 7 import std.exception; 8 import std.range; 9 import std.string; 10 import std.traits; 11 12 import mysql.exceptions; 13 import mysql.protocol.constants; 14 import mysql.protocol.extra_types; 15 import mysql.protocol.sockets; 16 import mysql.types; 17 18 @safe: 19 20 /++ 21 Function to extract a time difference from a binary encoded row. 22 23 Time/date structures are packed by the server into a byte sub-packet 24 with a leading length byte, and a minimal number of bytes to embody the data. 25 26 Params: a = slice of a protocol packet beginning at the length byte for a chunk of time data 27 28 Returns: A populated or default initialized TimeDiff struct. 29 +/ 30 TimeDiff toTimeDiff(in ubyte[] a) pure 31 { 32 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 33 TimeDiff td; 34 uint l = a[0]; 35 enforce!MYXProtocol(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row."); 36 if (l >= 5) 37 { 38 td.negative = (a[1] != 0); 39 td.days = (a[5] << 24) + (a[4] << 16) + (a[3] << 8) + a[2]; 40 } 41 if (l >= 8) 42 { 43 td.hours = a[6]; 44 td.minutes = a[7]; 45 td.seconds = a[8]; 46 } 47 // Note that the fractional seconds part is not stored by MySQL 48 return td; 49 } 50 51 /++ 52 Function to extract a time difference from a text encoded column value. 53 54 Text representations of a time difference are like -750:12:02 - 750 hours 55 12 minutes and two seconds ago. 56 57 Params: s = A string representation of the time difference. 58 Returns: A populated or default initialized TimeDiff struct. 59 +/ 60 TimeDiff toTimeDiff(string s) 61 { 62 TimeDiff td; 63 int t = parse!int(s); 64 if (t < 0) 65 { 66 td.negative = true; 67 t = -t; 68 } 69 td.hours = cast(ubyte) t%24; 70 td.days = cast(ubyte) t/24; 71 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 72 td.minutes = parse!ubyte(s); 73 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 74 td.seconds = parse!ubyte(s); 75 return td; 76 } 77 78 /++ 79 Function to extract a TimeOfDay from a binary encoded row. 80 81 Time/date structures are packed by the server into a byte sub-packet 82 with a leading length byte, and a minimal number of bytes to embody the data. 83 84 Params: a = slice of a protocol packet beginning at the length byte for a 85 chunk of time data. 86 Returns: A populated or default initialized std.datetime.TimeOfDay struct. 87 +/ 88 TimeOfDay toTimeOfDay(in ubyte[] a) pure 89 { 90 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 91 uint l = a[0]; 92 enforce!MYXProtocol(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row."); 93 enforce!MYXProtocol(l >= 8, "Time column value is not in a time-of-day format"); 94 95 TimeOfDay tod; 96 tod.hour = a[6]; 97 tod.minute = a[7]; 98 tod.second = a[8]; 99 return tod; 100 } 101 102 /++ 103 Function to extract a TimeOfDay from a text encoded column value. 104 105 Text representations of a time of day are as in 14:22:02 106 107 Params: s = A string representation of the time. 108 Returns: A populated or default initialized std.datetime.TimeOfDay struct. 109 +/ 110 TimeOfDay toTimeOfDay(const(char)[] s) 111 { 112 TimeOfDay tod; 113 tod.hour = parse!int(s); 114 enforce!MYXProtocol(tod.hour <= 24 && tod.hour >= 0, "Time column value is in time difference form"); 115 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 116 tod.minute = parse!ubyte(s); 117 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 118 tod.second = parse!ubyte(s); 119 return tod; 120 } 121 122 /++ 123 Function to pack a TimeOfDay into a binary encoding for transmission to the server. 124 125 Time/date structures are packed into a string of bytes with a leading length 126 byte, and a minimal number of bytes to embody the data. 127 128 Params: tod = TimeOfDay struct. 129 Returns: Packed ubyte[]. 130 +/ 131 ubyte[] pack(in TimeOfDay tod) pure nothrow 132 { 133 ubyte[] rv; 134 if (tod == TimeOfDay.init) 135 { 136 rv.length = 1; 137 rv[0] = 0; 138 } 139 else 140 { 141 rv.length = 9; 142 rv[0] = 8; 143 rv[6] = tod.hour; 144 rv[7] = tod.minute; 145 rv[8] = tod.second; 146 } 147 return rv; 148 } 149 150 /++ 151 Function to extract a Date from a binary encoded row. 152 153 Time/date structures are packed by the server into a byte sub-packet 154 with a leading length byte, and a minimal number of bytes to embody the data. 155 156 Params: a = slice of a protocol packet beginning at the length byte for a 157 chunk of Date data. 158 Returns: A populated or default initialized `std.datetime.Date` struct. 159 +/ 160 Date toDate(in ubyte[] a) pure 161 { 162 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 163 if (a[0] == 0) 164 return Date(0,0,0); 165 166 enforce!MYXProtocol(a[0] >= 4, "Binary date representation is too short"); 167 int year = (a[2] << 8) + a[1]; 168 int month = cast(int) a[3]; 169 int day = cast(int) a[4]; 170 return Date(year, month, day); 171 } 172 173 /++ 174 Function to extract a Date from a text encoded column value. 175 176 Text representations of a Date are as in 2011-11-11 177 178 Params: s = A string representation of the time difference. 179 Returns: A populated or default initialized `std.datetime.Date` struct. 180 +/ 181 Date toDate(const(char)[] s) 182 { 183 int year = parse!(ushort)(s); 184 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 185 int month = parse!(ubyte)(s); 186 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 187 int day = parse!(ubyte)(s); 188 return Date(year, month, day); 189 } 190 191 /++ 192 Function to pack a Date into a binary encoding for transmission to the server. 193 194 Time/date structures are packed into a string of bytes with a leading length 195 byte, and a minimal number of bytes to embody the data. 196 197 Params: dt = `std.datetime.Date` struct. 198 Returns: Packed ubyte[]. 199 +/ 200 ubyte[] pack(in Date dt) pure nothrow 201 { 202 ubyte[] rv; 203 if (dt.year < 0) 204 { 205 rv.length = 1; 206 rv[0] = 0; 207 } 208 else 209 { 210 rv.length = 5; 211 rv[0] = 4; 212 rv[1] = cast(ubyte) ( dt.year & 0xff); 213 rv[2] = cast(ubyte) ((dt.year >> 8) & 0xff); 214 rv[3] = cast(ubyte) dt.month; 215 rv[4] = cast(ubyte) dt.day; 216 } 217 return rv; 218 } 219 220 /++ 221 Function to extract a DateTime from a binary encoded row. 222 223 Time/date structures are packed by the server into a byte sub-packet 224 with a leading length byte, and a minimal number of bytes to embody the data. 225 226 Params: a = slice of a protocol packet beginning at the length byte for a 227 chunk of DateTime data 228 Returns: A populated or default initialized `std.datetime.DateTime` struct. 229 +/ 230 DateTime toDateTime(in ubyte[] a) pure 231 { 232 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 233 if (a[0] == 0) 234 return DateTime(); 235 236 enforce!MYXProtocol(a[0] >= 4, "Supplied ubyte[] is not long enough"); 237 int year = (a[2] << 8) + a[1]; 238 int month = a[3]; 239 int day = a[4]; 240 DateTime dt; 241 if (a[0] == 4) 242 { 243 dt = DateTime(year, month, day); 244 } 245 else 246 { 247 enforce!MYXProtocol(a[0] >= 7, "Supplied ubyte[] is not long enough"); 248 int hour = a[5]; 249 int minute = a[6]; 250 int second = a[7]; 251 dt = DateTime(year, month, day, hour, minute, second); 252 } 253 return dt; 254 } 255 256 /++ 257 Function to extract a DateTime from a text encoded column value. 258 259 Text representations of a DateTime are as in 2011-11-11 12:20:02 260 261 Params: s = A string representation of the time difference. 262 Returns: A populated or default initialized `std.datetime.DateTime` struct. 263 +/ 264 DateTime toDateTime(const(char)[] s) 265 { 266 int year = parse!(ushort)(s); 267 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 268 int month = parse!(ubyte)(s); 269 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 270 int day = parse!(ubyte)(s); 271 enforce!MYXProtocol(s.skipOver(" "), `Expected: " "`); 272 int hour = parse!(ubyte)(s); 273 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 274 int minute = parse!(ubyte)(s); 275 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 276 int second = parse!(ubyte)(s); 277 return DateTime(year, month, day, hour, minute, second); 278 } 279 280 /++ 281 Function to extract a DateTime from a ulong. 282 283 This is used to support the TimeStamp struct. 284 285 Params: x = A ulong e.g. 20111111122002UL. 286 Returns: A populated `std.datetime.DateTime` struct. 287 +/ 288 DateTime toDateTime(ulong x) 289 { 290 int second = cast(int) (x%100); 291 x /= 100; 292 int minute = cast(int) (x%100); 293 x /= 100; 294 int hour = cast(int) (x%100); 295 x /= 100; 296 int day = cast(int) (x%100); 297 x /= 100; 298 int month = cast(int) (x%100); 299 x /= 100; 300 int year = cast(int) (x%10000); 301 // 2038-01-19 03:14:07 302 enforce!MYXProtocol(year >= 1970 && year < 2039, "Date/time out of range for 2 bit timestamp"); 303 enforce!MYXProtocol(year != 2038 || (month < 1 && day < 19 && hour < 3 && minute < 14 && second < 7), 304 "Date/time out of range for 2 bit timestamp"); 305 return DateTime(year, month, day, hour, minute, second); 306 } 307 308 /++ 309 Function to pack a DateTime into a binary encoding for transmission to the server. 310 311 Time/date structures are packed into a string of bytes with a leading length byte, 312 and a minimal number of bytes to embody the data. 313 314 Params: dt = `std.datetime.DateTime` struct. 315 Returns: Packed ubyte[]. 316 +/ 317 ubyte[] pack(in DateTime dt) pure nothrow 318 { 319 uint len = 1; 320 if (dt.year || dt.month || dt.day) len = 5; 321 if (dt.hour || dt.minute|| dt.second) len = 8; 322 ubyte[] rv; 323 rv.length = len; 324 rv[0] = cast(ubyte)(rv.length - 1); // num bytes 325 if(len >= 5) 326 { 327 rv[1] = cast(ubyte) ( dt.year & 0xff); 328 rv[2] = cast(ubyte) ((dt.year >> 8) & 0xff); 329 rv[3] = cast(ubyte) dt.month; 330 rv[4] = cast(ubyte) dt.day; 331 } 332 if(len == 8) 333 { 334 rv[5] = cast(ubyte) dt.hour; 335 rv[6] = cast(ubyte) dt.minute; 336 rv[7] = cast(ubyte) dt.second; 337 } 338 return rv; 339 } 340 341 342 T consume(T)(MySQLSocket conn) pure { 343 ubyte[T.sizeof] buffer; 344 conn.read(buffer); 345 ubyte[] rng = buffer; 346 return consume!T(rng); 347 } 348 349 string consume(T:string, ubyte N=T.sizeof)(ref ubyte[] packet) pure 350 { 351 return packet.consume!string(N); 352 } 353 354 string consume(T:string)(ref ubyte[] packet, size_t N) pure 355 in 356 { 357 assert(packet.length >= N); 358 } 359 do 360 { 361 auto result = packet.consume(N); 362 return (() @trusted => cast(string)result)(); 363 } 364 365 /// Returns N number of bytes from the packet and advances the array 366 ubyte[] consume()(ref ubyte[] packet, size_t N) pure nothrow 367 in 368 { 369 assert(packet.length >= N); 370 } 371 do 372 { 373 auto result = packet[0..N]; 374 packet = packet[N..$]; 375 return result; 376 } 377 378 T decode(T:ulong)(in ubyte[] packet, size_t n) pure nothrow 379 { 380 switch(n) 381 { 382 case 8: return packet.decode!(T, 8)(); 383 case 4: return packet.decode!(T, 4)(); 384 case 3: return packet.decode!(T, 3)(); 385 case 2: return packet.decode!(T, 2)(); 386 case 1: return packet.decode!(T, 1)(); 387 default: assert(0); 388 } 389 } 390 391 T consume(T)(ref ubyte[] packet, int n) pure nothrow 392 if(isIntegral!T) 393 { 394 switch(n) 395 { 396 case 8: return packet.consume!(T, 8)(); 397 case 4: return packet.consume!(T, 4)(); 398 case 3: return packet.consume!(T, 3)(); 399 case 2: return packet.consume!(T, 2)(); 400 case 1: return packet.consume!(T, 1)(); 401 default: assert(0); 402 } 403 } 404 405 TimeOfDay consume(T:TimeOfDay, ubyte N=T.sizeof)(ref ubyte[] packet) pure 406 in 407 { 408 static assert(N == T.sizeof); 409 } 410 do 411 { 412 enforce!MYXProtocol(packet.length, "Supplied byte array is zero length"); 413 uint length = packet.front; 414 enforce!MYXProtocol(length == 0 || length == 5 || length == 8 || length == 12, "Bad Time length in binary row."); 415 enforce!MYXProtocol(length >= 8, "Time column value is not in a time-of-day format"); 416 417 packet.popFront(); // length 418 auto bytes = packet.consume(length); 419 420 // TODO: What are the fields in between!?! Blank Date? 421 TimeOfDay tod; 422 tod.hour = bytes[5]; 423 tod.minute = bytes[6]; 424 tod.second = bytes[7]; 425 return tod; 426 } 427 428 Date consume(T:Date, ubyte N=T.sizeof)(ref ubyte[] packet) pure 429 in 430 { 431 static assert(N == T.sizeof); 432 } 433 do 434 { 435 return toDate(packet.consume(5)); 436 } 437 438 DateTime consume(T:DateTime, ubyte N=T.sizeof)(ref ubyte[] packet) pure 439 in 440 { 441 assert(packet.length); 442 assert(N == T.sizeof); 443 } 444 do 445 { 446 auto numBytes = packet.consume!ubyte(); 447 if(numBytes == 0) 448 return DateTime(); 449 450 enforce!MYXProtocol(numBytes >= 4, "Supplied packet is not large enough to store DateTime"); 451 452 int year = packet.consume!ushort(); 453 int month = packet.consume!ubyte(); 454 int day = packet.consume!ubyte(); 455 int hour = 0; 456 int minute = 0; 457 int second = 0; 458 if(numBytes > 4) 459 { 460 enforce!MYXProtocol(numBytes >= 7, "Supplied packet is not large enough to store a DateTime with TimeOfDay"); 461 hour = packet.consume!ubyte(); 462 minute = packet.consume!ubyte(); 463 second = packet.consume!ubyte(); 464 } 465 return DateTime(year, month, day, hour, minute, second); 466 } 467 468 469 @property bool hasEnoughBytes(T, ubyte N=T.sizeof)(in ubyte[] packet) pure 470 in 471 { 472 static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes"); 473 } 474 do 475 { 476 return packet.length >= N; 477 } 478 479 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow 480 if(isIntegral!T) 481 in 482 { 483 static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot decode integral value. Invalid size: "~N.stringof); 484 static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes"); 485 assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof); 486 } 487 do 488 { 489 T value = 0; 490 static if(N == 8) // 64 bit 491 { 492 value |= cast(T)(packet[7]) << (8*7); 493 value |= cast(T)(packet[6]) << (8*6); 494 value |= cast(T)(packet[5]) << (8*5); 495 value |= cast(T)(packet[4]) << (8*4); 496 } 497 static if(N >= 4) // 32 bit 498 { 499 value |= cast(T)(packet[3]) << (8*3); 500 } 501 static if(N >= 3) // 24 bit 502 { 503 value |= cast(T)(packet[2]) << (8*2); 504 } 505 static if(N >= 2) // 16 bit 506 { 507 value |= cast(T)(packet[1]) << (8*1); 508 } 509 static if(N >= 1) // 8 bit 510 { 511 value |= cast(T)(packet[0]) << (8*0); 512 } 513 return value; 514 } 515 516 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow 517 if(isIntegral!T) 518 in 519 { 520 static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot consume integral value. Invalid size: "~N.stringof); 521 static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes"); 522 assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof); 523 } 524 do 525 { 526 // The uncommented line triggers a template deduction error, 527 // so we need to store a temporary first 528 // could the problem be method chaining? 529 //return packet.consume(N).decode!(T, N)(); 530 auto bytes = packet.consume(N); 531 return bytes.decode!(T, N)(); 532 } 533 534 535 T myto(T)(const(char)[] value) 536 { 537 static if(is(T == DateTime)) 538 return toDateTime(value); 539 else static if(is(T == Date)) 540 return toDate(value); 541 else static if(is(T == TimeOfDay)) 542 return toTimeOfDay(value); 543 else 544 return to!T(value); 545 } 546 547 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow @trusted 548 if(isFloatingPoint!T) 549 in 550 { 551 static assert((is(T == float) && N == float.sizeof) 552 || is(T == double) && N == double.sizeof); 553 } 554 do 555 { 556 T result = 0; 557 (cast(ubyte*)&result)[0..T.sizeof] = packet[0..T.sizeof]; 558 return result; 559 } 560 561 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow 562 if(isFloatingPoint!T) 563 in 564 { 565 static assert((is(T == float) && N == float.sizeof) 566 || is(T == double) && N == double.sizeof); 567 } 568 do 569 { 570 return packet.consume(T.sizeof).decode!T(); 571 } 572 573 574 SQLValue consumeBinaryValueIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool unsigned) 575 { 576 SQLValue result; 577 578 // Length of DateTime packet is NOT DateTime.sizeof, it can be 1, 5 or 8 bytes 579 static if(is(T==DateTime)) 580 result.isIncomplete = packet.length < 1; 581 else 582 result.isIncomplete = packet.length < N; 583 584 // isNull should have been handled by the caller as the binary format uses a 585 // null bitmap, and we don't have access to that information at this point 586 assert(!result.isNull); 587 if(!result.isIncomplete) 588 { 589 // only integral types is unsigned 590 static if(isIntegral!T) 591 { 592 if(unsigned) 593 result.value = packet.consume!(Unsigned!T)(); 594 else 595 result.value = packet.consume!(Signed!T)(); 596 } 597 else 598 { 599 // TODO: DateTime values etc might be incomplete! 600 result.value = packet.consume!(T, N)(); 601 } 602 } 603 return result; 604 } 605 606 SQLValue consumeNonBinaryValueIfComplete(T)(ref ubyte[] packet, bool unsigned) 607 { 608 SQLValue result; 609 auto lcb = packet.decode!LCB(); 610 result.isIncomplete = lcb.isIncomplete || packet.length < (lcb.value+lcb.totalBytes); 611 result.isNull = lcb.isNull; 612 if(!result.isIncomplete) 613 { 614 // The packet has all the data we need, so we'll remove the LCB 615 // and convert the data 616 packet.skip(lcb.totalBytes); 617 assert(packet.length >= lcb.value); 618 auto value = cast(char[]) packet.consume(cast(size_t)lcb.value); 619 620 if(!result.isNull) 621 { 622 assert(!result.isIncomplete); 623 assert(!result.isNull); 624 static if(isIntegral!T) 625 { 626 if(unsigned) 627 result.value = to!(Unsigned!T)(value); 628 else 629 result.value = to!(Signed!T)(value); 630 } 631 else 632 { 633 result.value = myto!T(value); 634 } 635 } 636 } 637 return result; 638 } 639 640 SQLValue consumeIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool binary, bool unsigned) 641 { 642 return binary 643 ? packet.consumeBinaryValueIfComplete!(T, N)(unsigned) 644 : packet.consumeNonBinaryValueIfComplete!T(unsigned); 645 } 646 647 SQLValue consumeIfComplete()(ref ubyte[] packet, SQLType sqlType, bool binary, bool unsigned, ushort charSet) 648 { 649 switch(sqlType) 650 { 651 default: assert(false, "Unsupported SQL type "~to!string(sqlType)); 652 case SQLType.NULL: 653 SQLValue result; 654 result.isIncomplete = false; 655 result.isNull = true; 656 return result; 657 case SQLType.TINY: 658 return packet.consumeIfComplete!byte(binary, unsigned); 659 case SQLType.SHORT: 660 return packet.consumeIfComplete!short(binary, unsigned); 661 case SQLType.INT24: 662 return packet.consumeIfComplete!(int, 3)(binary, unsigned); 663 case SQLType.INT: 664 return packet.consumeIfComplete!int(binary, unsigned); 665 case SQLType.LONGLONG: 666 return packet.consumeIfComplete!long(binary, unsigned); 667 case SQLType.FLOAT: 668 return packet.consumeIfComplete!float(binary, unsigned); 669 case SQLType.DOUBLE: 670 return packet.consumeIfComplete!double(binary, unsigned); 671 case SQLType.TIMESTAMP: 672 return packet.consumeIfComplete!DateTime(binary, unsigned); 673 case SQLType.TIME: 674 return packet.consumeIfComplete!TimeOfDay(binary, unsigned); 675 case SQLType.YEAR: 676 return packet.consumeIfComplete!ushort(binary, unsigned); 677 case SQLType.DATE: 678 return packet.consumeIfComplete!Date(binary, unsigned); 679 case SQLType.DATETIME: 680 return packet.consumeIfComplete!DateTime(binary, unsigned); 681 case SQLType.VARCHAR: 682 case SQLType.ENUM: 683 case SQLType.SET: 684 case SQLType.VARSTRING: 685 case SQLType.STRING: 686 case SQLType.NEWDECIMAL: 687 return packet.consumeIfComplete!string(false, unsigned); 688 case SQLType.TINYBLOB: 689 case SQLType.MEDIUMBLOB: 690 case SQLType.BLOB: 691 case SQLType.LONGBLOB: 692 case SQLType.BIT: // Yes, BIT. See note below. 693 694 // TODO: This line should work. Why doesn't it? 695 //return packet.consumeIfComplete!(ubyte[])(binary, unsigned); 696 697 auto lcb = packet.consumeIfComplete!LCB(); 698 assert(!lcb.isIncomplete); 699 SQLValue result; 700 result.isIncomplete = false; 701 result.isNull = lcb.isNull; 702 if(result.isNull) 703 { 704 // TODO: consumeIfComplete!LCB should be adjusted to do 705 // this itself, but not until I'm certain that nothing 706 // is reliant on the current behavior. 707 packet.popFront(); // LCB length 708 } 709 else 710 { 711 auto data = packet.consume(cast(size_t)lcb.value); 712 if(charSet == 0x3F) // CharacterSet == binary 713 result.value = data; // BLOB-ish 714 else 715 result.value = (() @trusted => cast(string)data)(); // TEXT-ish 716 } 717 718 // Type BIT is treated as a length coded binary (like a BLOB or VARCHAR), 719 // not like an integral type. So convert the binary data to a bool. 720 // See: http://dev.mysql.com/doc/internals/en/binary-protocol-value.html 721 if(sqlType == SQLType.BIT) 722 { 723 enforce!MYXProtocol(result.value.length == 1, 724 "Expected BIT to arrive as an LCB with length 1, but got length "~to!string(result.value.length)); 725 726 result.value = result.value[0] == 1; 727 } 728 729 return result; 730 } 731 } 732 733 /++ 734 Extract number of bytes used for this LCB 735 736 Returns the number of bytes required to store this LCB 737 738 See_Also: $(LINK http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements) 739 740 Returns: 0 if it's a null value, or number of bytes in other cases 741 +/ 742 byte getNumLCBBytes(in ubyte lcbHeader) pure nothrow 743 { 744 switch(lcbHeader) 745 { 746 case 251: return 0; // null 747 case 0: .. case 250: return 1; // 8-bit 748 case 252: return 2; // 16-bit 749 case 253: return 3; // 24-bit 750 case 254: return 8; // 64-bit 751 752 case 255: 753 default: 754 assert(0); 755 } 756 assert(0); 757 } 758 759 760 /++ 761 Decodes a Length Coded Binary from a packet 762 763 See_Also: struct `mysql.protocol.extra_types.LCB` 764 765 Params: 766 packet = A packet that starts with a LCB. The bytes is popped off 767 iff the packet is complete. See `mysql.protocol.extra_types.LCB`. 768 769 Returns: A decoded LCB value 770 +/ 771 T consumeIfComplete(T:LCB)(ref ubyte[] packet) pure nothrow 772 in 773 { 774 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 775 } 776 do 777 { 778 auto lcb = packet.decodeLCBHeader(); 779 if(lcb.isNull || lcb.isIncomplete) 780 return lcb; 781 782 if(lcb.numBytes > 1) 783 { 784 // We know it's complete, so we have to start consuming the LCB 785 // Single byte values doesn't have a length 786 packet.popFront(); // LCB length 787 } 788 789 assert(packet.length >= lcb.numBytes); 790 791 lcb.value = packet.consume!ulong(lcb.numBytes); 792 return lcb; 793 } 794 795 LCB decodeLCBHeader(in ubyte[] packet) pure nothrow 796 in 797 { 798 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 799 } 800 do 801 { 802 LCB lcb; 803 lcb.numBytes = getNumLCBBytes(packet.front); 804 if(lcb.numBytes == 0) 805 { 806 lcb.isNull = true; 807 return lcb; 808 } 809 810 assert(!lcb.isNull); 811 // -1 for LCB length as we haven't popped it off yet 812 lcb.isIncomplete = (lcb.numBytes > 1) && (packet.length-1 < lcb.numBytes); 813 if(lcb.isIncomplete) 814 { 815 // Not enough bytes to store data. We don't remove any data, and expect 816 // the caller to check isIncomplete and take action to fetch more data 817 // and call this method at a later time 818 return lcb; 819 } 820 821 assert(!lcb.isIncomplete); 822 return lcb; 823 } 824 825 /++ 826 Decodes a Length Coded Binary from a packet 827 828 See_Also: struct `mysql.protocol.extra_types.LCB` 829 830 Params: 831 packet = A packet that starts with a LCB. See `mysql.protocol.extra_types.LCB`. 832 833 Returns: A decoded LCB value 834 +/ 835 LCB decode(T:LCB)(in ubyte[] packet) pure nothrow 836 in 837 { 838 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 839 } 840 do 841 { 842 auto lcb = packet.decodeLCBHeader(); 843 if(lcb.isNull || lcb.isIncomplete) 844 return lcb; 845 assert(packet.length >= lcb.totalBytes); 846 847 if(lcb.numBytes == 0) 848 lcb.value = 0; 849 else if(lcb.numBytes == 1) 850 lcb.value = packet.decode!ulong(lcb.numBytes); 851 else 852 { 853 // Skip the throwaway byte that indicated "at least 2 more bytes coming" 854 lcb.value = packet[1..$].decode!ulong(lcb.numBytes); 855 } 856 857 return lcb; 858 } 859 860 /++ 861 Parse Length Coded String 862 863 See_Also: $(LINK http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements) 864 +/ 865 string consume(T:LCS)(ref ubyte[] packet) pure 866 in 867 { 868 assert(packet.length >= 1, "LCS packet needs to store at least the LCB header"); 869 } 870 do 871 { 872 auto lcb = packet.consumeIfComplete!LCB(); 873 assert(!lcb.isIncomplete); 874 if(lcb.isNull) 875 return null; 876 enforce!MYXProtocol(lcb.value <= uint.max, "Protocol Length Coded String is too long"); 877 return cast(string)packet.consume(cast(size_t)lcb.value).idup; 878 } 879 880 /++ 881 Skips over n items, advances the array, and return the newly advanced array 882 to allow method chaining. 883 +/ 884 T[] skip(T)(ref T[] array, size_t n) pure nothrow 885 in 886 { 887 assert(n <= array.length); 888 } 889 do 890 { 891 array = array[n..$]; 892 return array; 893 } 894 895 /++ 896 Converts a value into a sequence of bytes and fills the supplied array. 897 898 Params: 899 IsInt24 = If only the most significant 3 bytes from the value should be used. 900 value = The value to add to array. 901 array = The array we should add the values for. It has to be large enough, 902 and the values are packed starting index 0. 903 +/ 904 void packInto(T, bool IsInt24 = false)(T value, ubyte[] array) pure nothrow 905 in 906 { 907 static if(IsInt24) 908 assert(array.length >= 3); 909 else 910 assert(array.length >= T.sizeof, "Not enough space to unpack "~T.stringof); 911 } 912 do 913 { 914 static if(T.sizeof >= 1) 915 { 916 array[0] = cast(ubyte) (value >> 8*0) & 0xff; 917 } 918 static if(T.sizeof >= 2) 919 { 920 array[1] = cast(ubyte) (value >> 8*1) & 0xff; 921 } 922 static if(!IsInt24) 923 { 924 static if(T.sizeof >= 4) 925 { 926 array[2] = cast(ubyte) (value >> 8*2) & 0xff; 927 array[3] = cast(ubyte) (value >> 8*3) & 0xff; 928 } 929 static if(T.sizeof >= 8) 930 { 931 array[4] = cast(ubyte) (value >> 8*4) & 0xff; 932 array[5] = cast(ubyte) (value >> 8*5) & 0xff; 933 array[6] = cast(ubyte) (value >> 8*6) & 0xff; 934 array[7] = cast(ubyte) (value >> 8*7) & 0xff; 935 } 936 } 937 else 938 { 939 array[2] = cast(ubyte) (value >> 8*2) & 0xff; 940 } 941 } 942 943 ubyte[] packLength(size_t l, out size_t offset) pure nothrow 944 out(result) 945 { 946 assert(result.length >= 1); 947 } 948 do 949 { 950 ubyte[] t; 951 if (!l) 952 { 953 t.length = 1; 954 t[0] = 0; 955 } 956 else if (l <= 250) 957 { 958 t.length = 1+l; 959 t[0] = cast(ubyte) l; 960 offset = 1; 961 } 962 else if (l <= 0xffff) // 16-bit 963 { 964 t.length = 3+l; 965 t[0] = 252; 966 packInto(cast(ushort)l, t[1..3]); 967 offset = 3; 968 } 969 else if (l < 0xffffff) // 24-bit 970 { 971 t.length = 4+l; 972 t[0] = 253; 973 packInto!(uint, true)(cast(uint)l, t[1..4]); 974 offset = 4; 975 } 976 else // 64-bit 977 { 978 ulong u = cast(ulong) l; 979 t.length = 9+l; 980 t[0] = 254; 981 u.packInto(t[1..9]); 982 offset = 9; 983 } 984 return t; 985 } 986 987 ubyte[] packLCS(const(void)[] a) pure nothrow 988 { 989 size_t offset; 990 ubyte[] t = packLength(a.length, offset); 991 if (t[0]) 992 t[offset..$] = (cast(const(ubyte)[]) a)[0..$]; 993 return t; 994 } 995 996 @("testLCB") 997 debug(MYSQLN_TESTS) 998 unittest 999 { 1000 static void testLCB(string parseLCBFunc)(bool shouldConsume) 1001 { 1002 ubyte[] buf = [ 0xde, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x01, 0x00 ]; 1003 ubyte[] bufCopy; 1004 1005 bufCopy = buf; 1006 LCB lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1007 assert(lcb.value == 0xde && !lcb.isNull && lcb.totalBytes == 1); 1008 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1009 1010 buf[0] = 251; 1011 bufCopy = buf; 1012 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1013 assert(lcb.value == 0 && lcb.isNull && lcb.totalBytes == 1); 1014 //TODO: This test seems to fail for consumeIfComplete, need to investigate. 1015 // Don't know if fixing it might cause a problem, or if I simple misunderstood 1016 // the function's intent. 1017 if(parseLCBFunc != "consumeIfComplete") 1018 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1019 1020 buf[0] = 252; 1021 bufCopy = buf; 1022 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1023 assert(lcb.value == 0xbbcc && !lcb.isNull && lcb.totalBytes == 3); 1024 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1025 1026 buf[0] = 253; 1027 bufCopy = buf; 1028 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1029 assert(lcb.value == 0xaabbcc && !lcb.isNull && lcb.totalBytes == 4); 1030 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1031 1032 buf[0] = 254; 1033 bufCopy = buf; 1034 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1035 assert(lcb.value == 0x5566778899aabbcc && !lcb.isNull && lcb.totalBytes == 9); 1036 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1037 } 1038 1039 //TODO: Merge 'consumeIfComplete(T:LCB)' and 'decode(T:LCB)', they do 1040 // basically the same thing, only one consumes input and the other 1041 // doesn't. Just want a better idea of where/how/why they're both 1042 // used, and maybe more tests, before I go messing with them. 1043 testLCB!"consumeIfComplete"(true); 1044 testLCB!"decode"(false); 1045 } 1046 1047 @("consume!LCS") 1048 debug(MYSQLN_TESTS) 1049 unittest 1050 { 1051 ubyte[] buf; 1052 ubyte[] bufCopy; 1053 1054 buf.length = 0x2000200; 1055 buf[] = '\x01'; 1056 buf[0] = 250; 1057 buf[1] = '<'; 1058 buf[249] = '!'; 1059 buf[250] = '>'; 1060 bufCopy = buf; 1061 string x = consume!LCS(bufCopy); 1062 assert(x.length == 250 && x[0] == '<' && x[249] == '>'); 1063 1064 buf[] = '\x01'; 1065 buf[0] = 252; 1066 buf[1] = 0xff; 1067 buf[2] = 0xff; 1068 buf[3] = '<'; 1069 buf[0x10000] = '*'; 1070 buf[0x10001] = '>'; 1071 bufCopy = buf; 1072 x = consume!LCS(bufCopy); 1073 assert(x.length == 0xffff && x[0] == '<' && x[0xfffe] == '>'); 1074 1075 buf[] = '\x01'; 1076 buf[0] = 253; 1077 buf[1] = 0xff; 1078 buf[2] = 0xff; 1079 buf[3] = 0xff; 1080 buf[4] = '<'; 1081 buf[0x1000001] = '*'; 1082 buf[0x1000002] = '>'; 1083 bufCopy = buf; 1084 x = consume!LCS(bufCopy); 1085 assert(x.length == 0xffffff && x[0] == '<' && x[0xfffffe] == '>'); 1086 1087 buf[] = '\x01'; 1088 buf[0] = 254; 1089 buf[1] = 0xff; 1090 buf[2] = 0x00; 1091 buf[3] = 0x00; 1092 buf[4] = 0x02; 1093 buf[5] = 0x00; 1094 buf[6] = 0x00; 1095 buf[7] = 0x00; 1096 buf[8] = 0x00; 1097 buf[9] = '<'; 1098 buf[0x2000106] = '!'; 1099 buf[0x2000107] = '>'; 1100 bufCopy = buf; 1101 x = consume!LCS(bufCopy); 1102 assert(x.length == 0x20000ff && x[0] == '<' && x[0x20000fe] == '>'); 1103 } 1104 1105 /// Set packet length and number. It's important that the length of packet has 1106 /// already been set to the final state as its length is used. 1107 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber) pure nothrow 1108 in 1109 { 1110 // packet should include header, and possibly data 1111 assert(packet.length >= 4); 1112 } 1113 do 1114 { 1115 auto dataLength = packet.length - 4; // don't include header in calculated size 1116 assert(dataLength <= uint.max); 1117 packet.setPacketHeader(packetNumber, cast(uint)dataLength); 1118 } 1119 1120 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber, uint dataLength) pure nothrow 1121 in 1122 { 1123 // packet should include header 1124 assert(packet.length >= 4); 1125 // Length is always a 24-bit int 1126 assert(dataLength <= 0xffff_ffff_ffff); 1127 } 1128 do 1129 { 1130 dataLength.packInto!(uint, true)(packet); 1131 packet[3] = packetNumber; 1132 }