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