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