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