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: a = 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: a = 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 bool consume(T:bool, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow 515 { 516 static assert(N == 1); 517 return packet.consume!ubyte() == 1; 518 } 519 520 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow 521 if(isIntegral!T) 522 in 523 { 524 static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot consume integral value. Invalid size: "~N.stringof); 525 static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes"); 526 assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof); 527 } 528 body 529 { 530 // The uncommented line triggers a template deduction error, 531 // so we need to store a temporary first 532 // could the problem be method chaining? 533 //return packet.consume(N).decode!(T, N)(); 534 auto bytes = packet.consume(N); 535 return bytes.decode!(T, N)(); 536 } 537 538 539 T myto(T)(string value) 540 { 541 static if(is(T == DateTime)) 542 return toDateTime(value); 543 else static if(is(T == Date)) 544 return toDate(value); 545 else static if(is(T == TimeOfDay)) 546 return toTimeOfDay(value); 547 else 548 return to!T(value); 549 } 550 551 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow 552 if(isFloatingPoint!T) 553 in 554 { 555 static assert((is(T == float) && N == float.sizeof) 556 || is(T == double) && N == double.sizeof); 557 } 558 body 559 { 560 T result = 0; 561 (cast(ubyte*)&result)[0..T.sizeof] = packet[0..T.sizeof]; 562 return result; 563 } 564 565 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow 566 if(isFloatingPoint!T) 567 in 568 { 569 static assert((is(T == float) && N == float.sizeof) 570 || is(T == double) && N == double.sizeof); 571 } 572 body 573 { 574 return packet.consume(T.sizeof).decode!T(); 575 } 576 577 578 SQLValue consumeBinaryValueIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool unsigned) 579 { 580 SQLValue result; 581 result.isIncomplete = packet.length < N; 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) 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.BIT: 671 return packet.consumeIfComplete!bool(binary, unsigned); 672 case SQLType.TINY: 673 return packet.consumeIfComplete!byte(binary, unsigned); 674 case SQLType.SHORT: 675 return packet.consumeIfComplete!short(binary, unsigned); 676 case SQLType.INT24: 677 return packet.consumeIfComplete!(int, 3)(binary, unsigned); 678 case SQLType.INT: 679 return packet.consumeIfComplete!int(binary, unsigned); 680 case SQLType.LONGLONG: 681 return packet.consumeIfComplete!long(binary, unsigned); 682 case SQLType.FLOAT: 683 return packet.consumeIfComplete!float(binary, unsigned); 684 case SQLType.DOUBLE: 685 case SQLType.NEWDECIMAL: 686 return packet.consumeIfComplete!double(binary, unsigned); 687 case SQLType.TIMESTAMP: 688 return packet.consumeIfComplete!DateTime(binary, unsigned); 689 case SQLType.TIME: 690 return packet.consumeIfComplete!TimeOfDay(binary, unsigned); 691 case SQLType.YEAR: 692 return packet.consumeIfComplete!ushort(binary, unsigned); 693 case SQLType.DATE: 694 return packet.consumeIfComplete!Date(binary, unsigned); 695 case SQLType.DATETIME: 696 return packet.consumeIfComplete!DateTime(binary, unsigned); 697 case SQLType.VARCHAR: 698 case SQLType.ENUM: 699 case SQLType.SET: 700 case SQLType.VARSTRING: 701 case SQLType.STRING: 702 return packet.consumeIfComplete!string(false, unsigned); 703 case SQLType.TINYBLOB: 704 case SQLType.MEDIUMBLOB: 705 case SQLType.BLOB: 706 case SQLType.LONGBLOB: 707 708 // TODO: This line should work. Why doesn't it? 709 //return packet.consumeIfComplete!(ubyte[])(binary, unsigned); 710 711 auto lcb = packet.consumeIfComplete!LCB(); 712 assert(!lcb.isIncomplete); 713 SQLValue result; 714 result.isIncomplete = false; 715 result.isNull = lcb.isNull; 716 if(result.isNull) 717 { 718 // TODO: consumeIfComplete!LCB should be adjusted to do 719 // this itself, but not until I'm certain that nothing 720 // is reliant on the current behavior. 721 packet.popFront(); // LCB length 722 } 723 else 724 result.value = packet.consume(cast(size_t)lcb.value); 725 return result; 726 } 727 } 728 729 /** 730 * Extract number of bytes used for this LCB 731 * 732 * Returns the number of bytes required to store this LCB 733 * 734 * See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements 735 * 736 * Returns: 0 if it's a null value, or number of bytes in other cases 737 * */ 738 byte getNumLCBBytes(in ubyte lcbHeader) pure nothrow 739 { 740 switch(lcbHeader) 741 { 742 case 251: return 0; // null 743 case 0: .. case 250: return 1; // 8-bit 744 case 252: return 2; // 16-bit 745 case 253: return 3; // 24-bit 746 case 254: return 8; // 64-bit 747 748 case 255: 749 default: 750 assert(0); 751 } 752 assert(0); 753 } 754 755 756 /** 757 * Decodes a Length Coded Binary from a packet 758 * 759 * See_Also: struct LCB 760 * 761 * Parameters: 762 * packet = A packet that starts with a LCB. The bytes is popped off 763 * iff the packet is complete. See LCB. 764 * 765 * Returns: A decoded LCB value 766 * */ 767 T consumeIfComplete(T:LCB)(ref ubyte[] packet) pure nothrow 768 in 769 { 770 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 771 } 772 body 773 { 774 auto lcb = packet.decodeLCBHeader(); 775 if(lcb.isNull || lcb.isIncomplete) 776 return lcb; 777 778 if(lcb.numBytes > 1) 779 { 780 // We know it's complete, so we have to start consuming the LCB 781 // Single byte values doesn't have a length 782 packet.popFront(); // LCB length 783 } 784 785 assert(packet.length >= lcb.numBytes); 786 787 lcb.value = packet.consume!ulong(lcb.numBytes); 788 return lcb; 789 } 790 791 LCB decodeLCBHeader(in ubyte[] packet) pure nothrow 792 in 793 { 794 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 795 } 796 body 797 { 798 LCB lcb; 799 lcb.numBytes = getNumLCBBytes(packet.front); 800 if(lcb.numBytes == 0) 801 { 802 lcb.isNull = true; 803 return lcb; 804 } 805 806 assert(!lcb.isNull); 807 // -1 for LCB length as we haven't popped it off yet 808 lcb.isIncomplete = (lcb.numBytes > 1) && (packet.length-1 < lcb.numBytes); 809 if(lcb.isIncomplete) 810 { 811 // Not enough bytes to store data. We don't remove any data, and expect 812 // the caller to check isIncomplete and take action to fetch more data 813 // and call this method at a later time 814 return lcb; 815 } 816 817 assert(!lcb.isIncomplete); 818 return lcb; 819 } 820 821 /** 822 * Decodes a Length Coded Binary from a packet 823 * 824 * See_Also: struct LCB 825 * 826 * Parameters: 827 * packet = A packet that starts with a LCB. See LCB. 828 * 829 * Returns: A decoded LCB value 830 * */ 831 LCB decode(T:LCB)(in ubyte[] packet) pure nothrow 832 in 833 { 834 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 835 } 836 body 837 { 838 auto lcb = packet.decodeLCBHeader(); 839 if(lcb.isNull || lcb.isIncomplete) 840 return lcb; 841 assert(packet.length >= lcb.totalBytes); 842 843 if(lcb.numBytes == 0) 844 lcb.value = 0; 845 else if(lcb.numBytes == 1) 846 lcb.value = packet.decode!ulong(lcb.numBytes); 847 else 848 { 849 // Skip the throwaway byte that indicated "at least 2 more bytes coming" 850 lcb.value = packet[1..$].decode!ulong(lcb.numBytes); 851 } 852 853 return lcb; 854 } 855 856 /** Parse Length Coded String 857 * 858 * See_Also: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements 859 * */ 860 string consume(T:LCS)(ref ubyte[] packet) pure 861 in 862 { 863 assert(packet.length >= 1, "LCS packet needs to store at least the LCB header"); 864 } 865 body 866 { 867 auto lcb = packet.consumeIfComplete!LCB(); 868 assert(!lcb.isIncomplete); 869 if(lcb.isNull) 870 return null; 871 enforceEx!MYXProtocol(lcb.value <= uint.max, "Protocol Length Coded String is too long"); 872 return cast(string)packet.consume(cast(size_t)lcb.value).idup; 873 } 874 875 /** 876 * Skips over n items, advances the array, and return the newly advanced array 877 * to allow method chaining. 878 * */ 879 T[] skip(T)(ref T[] array, size_t n) pure nothrow 880 in 881 { 882 assert(n <= array.length); 883 } 884 body 885 { 886 array = array[n..$]; 887 return array; 888 } 889 890 /** 891 * Converts a value into a sequence of bytes and fills the supplied array 892 * 893 * Parameters: 894 * IsInt24 = If only the most significant 3 bytes from the value should be used 895 * value = The value to add to array 896 * array = The array we should add the values for. It has to be large enough, 897 * and the values are packed starting index 0 898 */ 899 void packInto(T, bool IsInt24 = false)(T value, ubyte[] array) pure nothrow 900 in 901 { 902 static if(IsInt24) 903 assert(array.length >= 3); 904 else 905 assert(array.length >= T.sizeof, "Not enough space to unpack "~T.stringof); 906 } 907 body 908 { 909 static if(T.sizeof >= 1) 910 { 911 array[0] = cast(ubyte) (value >> 8*0) & 0xff; 912 } 913 static if(T.sizeof >= 2) 914 { 915 array[1] = cast(ubyte) (value >> 8*1) & 0xff; 916 } 917 static if(!IsInt24) 918 { 919 static if(T.sizeof >= 4) 920 { 921 array[2] = cast(ubyte) (value >> 8*2) & 0xff; 922 array[3] = cast(ubyte) (value >> 8*3) & 0xff; 923 } 924 static if(T.sizeof >= 8) 925 { 926 array[4] = cast(ubyte) (value >> 8*4) & 0xff; 927 array[5] = cast(ubyte) (value >> 8*5) & 0xff; 928 array[6] = cast(ubyte) (value >> 8*6) & 0xff; 929 array[7] = cast(ubyte) (value >> 8*7) & 0xff; 930 } 931 } 932 else 933 { 934 array[2] = cast(ubyte) (value >> 8*2) & 0xff; 935 } 936 } 937 938 ubyte[] packLength(size_t l, out size_t offset) pure nothrow 939 out(result) 940 { 941 assert(result.length >= 1); 942 } 943 body 944 { 945 ubyte[] t; 946 if (!l) 947 { 948 t.length = 1; 949 t[0] = 0; 950 } 951 else if (l <= 250) 952 { 953 t.length = 1+l; 954 t[0] = cast(ubyte) l; 955 offset = 1; 956 } 957 else if (l <= 0xffff) // 16-bit 958 { 959 t.length = 3+l; 960 t[0] = 252; 961 packInto(cast(ushort)l, t[1..3]); 962 offset = 3; 963 } 964 else if (l < 0xffffff) // 24-bit 965 { 966 t.length = 4+l; 967 t[0] = 253; 968 packInto!(uint, true)(cast(uint)l, t[1..4]); 969 offset = 4; 970 } 971 else // 64-bit 972 { 973 ulong u = cast(ulong) l; 974 t.length = 9+l; 975 t[0] = 254; 976 u.packInto(t[1..9]); 977 offset = 9; 978 } 979 return t; 980 } 981 982 ubyte[] packLCS(void[] a) pure nothrow 983 { 984 size_t offset; 985 ubyte[] t = packLength(a.length, offset); 986 if (t[0]) 987 t[offset..$] = (cast(ubyte[]) a)[0..$]; 988 return t; 989 } 990 991 992 debug(MYSQL_INTEGRATION_TESTS) 993 unittest 994 { 995 static void testLCB(string parseLCBFunc)(bool shouldConsume) 996 { 997 ubyte[] buf = [ 0xde, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x01, 0x00 ]; 998 ubyte[] bufCopy; 999 1000 bufCopy = buf; 1001 LCB lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1002 assert(lcb.value == 0xde && !lcb.isNull && lcb.totalBytes == 1); 1003 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1004 1005 buf[0] = 251; 1006 bufCopy = buf; 1007 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1008 assert(lcb.value == 0 && lcb.isNull && lcb.totalBytes == 1); 1009 //TODO: This test seems to fail for consumeIfComplete, need to investigate. 1010 // Don't know if fixing it might cause a problem, or if I simple misunderstood 1011 // the function's intent. 1012 if(parseLCBFunc != "consumeIfComplete") 1013 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1014 1015 buf[0] = 252; 1016 bufCopy = buf; 1017 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1018 assert(lcb.value == 0xbbcc && !lcb.isNull && lcb.totalBytes == 3); 1019 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1020 1021 buf[0] = 253; 1022 bufCopy = buf; 1023 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1024 assert(lcb.value == 0xaabbcc && !lcb.isNull && lcb.totalBytes == 4); 1025 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1026 1027 buf[0] = 254; 1028 bufCopy = buf; 1029 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1030 assert(lcb.value == 0x5566778899aabbcc && !lcb.isNull && lcb.totalBytes == 9); 1031 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1032 } 1033 1034 //TODO: Merge 'consumeIfComplete(T:LCB)' and 'decode(T:LCB)', they do 1035 // basically the same thing, only one consumes input and the other 1036 // doesn't. Just want a better idea of where/how/why they're both 1037 // used, and maybe more tests, before I go messing with them. 1038 testLCB!"consumeIfComplete"(true); 1039 testLCB!"decode"(false); 1040 } 1041 1042 debug(MYSQL_INTEGRATION_TESTS) 1043 unittest 1044 { 1045 ubyte[] buf; 1046 ubyte[] bufCopy; 1047 1048 buf.length = 0x2000200; 1049 buf[] = '\x01'; 1050 buf[0] = 250; 1051 buf[1] = '<'; 1052 buf[249] = '!'; 1053 buf[250] = '>'; 1054 bufCopy = buf; 1055 string x = consume!LCS(bufCopy); 1056 assert(x.length == 250 && x[0] == '<' && x[249] == '>'); 1057 1058 buf[] = '\x01'; 1059 buf[0] = 252; 1060 buf[1] = 0xff; 1061 buf[2] = 0xff; 1062 buf[3] = '<'; 1063 buf[0x10000] = '*'; 1064 buf[0x10001] = '>'; 1065 bufCopy = buf; 1066 x = consume!LCS(bufCopy); 1067 assert(x.length == 0xffff && x[0] == '<' && x[0xfffe] == '>'); 1068 1069 buf[] = '\x01'; 1070 buf[0] = 253; 1071 buf[1] = 0xff; 1072 buf[2] = 0xff; 1073 buf[3] = 0xff; 1074 buf[4] = '<'; 1075 buf[0x1000001] = '*'; 1076 buf[0x1000002] = '>'; 1077 bufCopy = buf; 1078 x = consume!LCS(bufCopy); 1079 assert(x.length == 0xffffff && x[0] == '<' && x[0xfffffe] == '>'); 1080 1081 buf[] = '\x01'; 1082 buf[0] = 254; 1083 buf[1] = 0xff; 1084 buf[2] = 0x00; 1085 buf[3] = 0x00; 1086 buf[4] = 0x02; 1087 buf[5] = 0x00; 1088 buf[6] = 0x00; 1089 buf[7] = 0x00; 1090 buf[8] = 0x00; 1091 buf[9] = '<'; 1092 buf[0x2000106] = '!'; 1093 buf[0x2000107] = '>'; 1094 bufCopy = buf; 1095 x = consume!LCS(bufCopy); 1096 assert(x.length == 0x20000ff && x[0] == '<' && x[0x20000fe] == '>'); 1097 } 1098 1099 /// Set packet length and number. It's important that the length of packet has 1100 /// already been set to the final state as its length is used 1101 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber) pure nothrow 1102 in 1103 { 1104 // packet should include header, and possibly data 1105 assert(packet.length >= 4); 1106 } 1107 body 1108 { 1109 auto dataLength = packet.length - 4; // don't include header in calculated size 1110 assert(dataLength <= uint.max); 1111 packet.setPacketHeader(packetNumber, cast(uint)dataLength); 1112 } 1113 1114 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber, uint dataLength) pure nothrow 1115 in 1116 { 1117 // packet should include header 1118 assert(packet.length >= 4); 1119 // Length is always a 24-bit int 1120 assert(dataLength <= 0xffff_ffff_ffff); 1121 } 1122 body 1123 { 1124 dataLength.packInto!(uint, true)(packet); 1125 packet[3] = packetNumber; 1126 }