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 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 31 TimeDiff td; 32 uint l = a[0]; 33 enforce!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 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 70 td.minutes = parse!ubyte(s); 71 enforce!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 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 89 uint l = a[0]; 90 enforce!MYXProtocol(l == 0 || l == 5 || l == 8 || l == 12, "Bad Time length in binary row."); 91 enforce!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 enforce!MYXProtocol(tod.hour <= 24 && tod.hour >= 0, "Time column value is in time difference form"); 113 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 114 tod.minute = parse!ubyte(s); 115 enforce!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 MySQLDate toDate(in ubyte[] a) pure 159 { 160 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 161 if (a[0] == 0) 162 return MySQLDate(0,0,0); 163 164 enforce!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 MySQLDate(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 MySQLDate toDate(string s) 180 { 181 int year = parse!(ushort)(s); 182 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 183 int month = parse!(ubyte)(s); 184 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 185 int day = parse!(ubyte)(s); 186 return MySQLDate(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 MySQLDateTime toDateTime(in ubyte[] a) pure 229 { 230 enforce!MYXProtocol(a.length, "Supplied byte array is zero length"); 231 if (a[0] == 0) 232 return MySQLDateTime(); //TODO: Is this right?? 233 234 enforce!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 MySQLDateTime dt; 239 if (a[0] == 4) 240 { 241 dt = MySQLDateTime(year, month, day); 242 } 243 else 244 { 245 enforce!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 = MySQLDateTime(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 MySQLDateTime toDateTime(string s) 263 { 264 int year = parse!(ushort)(s); 265 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 266 int month = parse!(ubyte)(s); 267 enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`); 268 int day = parse!(ubyte)(s); 269 enforce!MYXProtocol(s.skipOver(" "), `Expected: " "`); 270 int hour = parse!(ubyte)(s); 271 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 272 int minute = parse!(ubyte)(s); 273 enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`); 274 int second = parse!(ubyte)(s); 275 return MySQLDateTime(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 MySQLDateTime 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 enforce!MYXProtocol(year >= 1970 && year < 2039, "Date/time out of range for 2 bit timestamp"); 301 enforce!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 MySQLDateTime(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 enforce!MYXProtocol(packet.length, "Supplied byte array is zero length"); 410 uint length = packet.front; 411 enforce!MYXProtocol(length == 0 || length == 5 || length == 8 || length == 12, "Bad Time length in binary row."); 412 enforce!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 MySQLDate consume(T:MySQLDate, 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 MySQLDateTime consume(T:MySQLDateTime, 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 MySQLDateTime(); //TODO: Is this right?? 446 447 enforce!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 enforce!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 MySQLDateTime(year, month, day, hour, minute, second); 463 } 464 465 /+ 466 // Make sure the dummy functions below actually do get chosen by the compiler 467 // and correctly halt compilation. 468 void foo() 469 { 470 ubyte[] pak; 471 auto a = consume!Date(pak); 472 auto b = consume!DateTime(pak); 473 auto c = consumeIfComplete!Date(pak, false, false); 474 auto d = consumeIfComplete!DateTime(pak, false, false); 475 476 }+/ 477 // Make sure consume doesn't get called with Date/DateTime, 478 // it should only get called with MySQLDate/MySQLDateTime 479 Date consume(T:Date, ubyte N=T.sizeof)(ref ubyte[] packet) pure 480 { 481 static assert(false, "consume!Date"); 482 } 483 DateTime consume(T:DateTime, ubyte N=T.sizeof)(ref ubyte[] packet) pure 484 { 485 static assert(false, "consume!DateTime"); 486 } 487 488 @property bool hasEnoughBytes(T, ubyte N=T.sizeof)(in ubyte[] packet) pure 489 in 490 { 491 static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes"); 492 } 493 body 494 { 495 return packet.length >= N; 496 } 497 498 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow 499 if(isIntegral!T) 500 in 501 { 502 static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot decode integral value. Invalid size: "~N.stringof); 503 static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes"); 504 assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof); 505 } 506 body 507 { 508 T value = 0; 509 static if(N == 8) // 64 bit 510 { 511 value |= cast(T)(packet[7]) << (8*7); 512 value |= cast(T)(packet[6]) << (8*6); 513 value |= cast(T)(packet[5]) << (8*5); 514 value |= cast(T)(packet[4]) << (8*4); 515 } 516 static if(N >= 4) // 32 bit 517 { 518 value |= cast(T)(packet[3]) << (8*3); 519 } 520 static if(N >= 3) // 24 bit 521 { 522 value |= cast(T)(packet[2]) << (8*2); 523 } 524 static if(N >= 2) // 16 bit 525 { 526 value |= cast(T)(packet[1]) << (8*1); 527 } 528 static if(N >= 1) // 8 bit 529 { 530 value |= cast(T)(packet[0]) << (8*0); 531 } 532 return value; 533 } 534 535 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow 536 if(isIntegral!T) 537 in 538 { 539 static assert(N == 1 || N == 2 || N == 3 || N == 4 || N == 8, "Cannot consume integral value. Invalid size: "~N.stringof); 540 static assert(T.sizeof >= N, T.stringof~" not large enough to store "~to!string(N)~" bytes"); 541 assert(packet.hasEnoughBytes!(T,N), "packet not long enough to contain all bytes needed for "~T.stringof); 542 } 543 body 544 { 545 // The uncommented line triggers a template deduction error, 546 // so we need to store a temporary first 547 // could the problem be method chaining? 548 //return packet.consume(N).decode!(T, N)(); 549 auto bytes = packet.consume(N); 550 return bytes.decode!(T, N)(); 551 } 552 553 554 T myto(T)(string value) 555 { 556 static if(is(T == MySQLDateTime)) 557 return toDateTime(value); 558 else static if(is(T == MySQLDate)) 559 return toDate(value); 560 else static if(is(T == TimeOfDay)) 561 return toTimeOfDay(value); 562 // Make sure myto doesn't get called with Date/DateTime, 563 // it should only get called with MySQLDate/MySQLDateTime 564 else static if(is(T == Date)) 565 static assert(false, "myto!Date"); 566 else static if(is(T == DateTime)) 567 static assert(false, "myto!DateTime"); 568 else 569 return to!T(value); 570 } 571 572 T decode(T, ubyte N=T.sizeof)(in ubyte[] packet) pure nothrow 573 if(isFloatingPoint!T) 574 in 575 { 576 static assert((is(T == float) && N == float.sizeof) 577 || is(T == double) && N == double.sizeof); 578 } 579 body 580 { 581 T result = 0; 582 (cast(ubyte*)&result)[0..T.sizeof] = packet[0..T.sizeof]; 583 return result; 584 } 585 586 T consume(T, ubyte N=T.sizeof)(ref ubyte[] packet) pure nothrow 587 if(isFloatingPoint!T) 588 in 589 { 590 static assert((is(T == float) && N == float.sizeof) 591 || is(T == double) && N == double.sizeof); 592 } 593 body 594 { 595 return packet.consume(T.sizeof).decode!T(); 596 } 597 598 599 SQLValue consumeBinaryValueIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool unsigned) 600 { 601 SQLValue result; 602 603 // Length of DateTime packet is NOT DateTime.sizeof, it can be 1, 5 or 8 bytes 604 static if(is(T==DateTime)) 605 result.isIncomplete = packet.length < 1; 606 else 607 result.isIncomplete = packet.length < N; 608 609 // isNull should have been handled by the caller as the binary format uses a 610 // null bitmap, and we don't have access to that information at this point 611 assert(!result.isNull); 612 if(!result.isIncomplete) 613 { 614 // only integral types is unsigned 615 static if(isIntegral!T) 616 { 617 if(unsigned) 618 result.value = packet.consume!(Unsigned!T)(); 619 else 620 result.value = packet.consume!(Signed!T)(); 621 } 622 else 623 { 624 // TODO: DateTime values etc might be incomplete! 625 result.value = packet.consume!(T, N)(); 626 } 627 } 628 return result; 629 } 630 631 SQLValue consumeNonBinaryValueIfComplete(T)(ref ubyte[] packet, bool unsigned) 632 { 633 SQLValue result; 634 auto lcb = packet.decode!LCB(); 635 result.isIncomplete = lcb.isIncomplete || packet.length < (lcb.value+lcb.totalBytes); 636 result.isNull = lcb.isNull; 637 if(!result.isIncomplete) 638 { 639 // The packet has all the data we need, so we'll remove the LCB 640 // and convert the data 641 packet.skip(lcb.totalBytes); 642 assert(packet.length >= lcb.value); 643 auto value = cast(string) packet.consume(cast(size_t)lcb.value); 644 645 if(!result.isNull) 646 { 647 assert(!result.isIncomplete); 648 assert(!result.isNull); 649 static if(isIntegral!T) 650 { 651 if(unsigned) 652 result.value = to!(Unsigned!T)(value); 653 else 654 result.value = to!(Signed!T)(value); 655 } 656 else 657 { 658 static if(isArray!T) 659 { 660 // to!() crashes when trying to convert empty strings 661 // to arrays, so we have this hack to just store any 662 // empty array in those cases 663 if(!value.length) 664 result.value = T.init; 665 else 666 result.value = cast(T)value.dup; 667 668 } 669 else 670 { 671 // TODO: DateTime values etc might be incomplete! 672 result.value = myto!T(value); 673 } 674 } 675 } 676 } 677 return result; 678 } 679 680 SQLValue consumeIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, bool binary, bool unsigned) 681 { 682 return binary 683 ? packet.consumeBinaryValueIfComplete!(T, N)(unsigned) 684 : packet.consumeNonBinaryValueIfComplete!T(unsigned); 685 } 686 687 // Make sure consumeIfComplete doesn't get called with Date/DateTime, 688 // it should only get called with MySQLDate/MySQLDateTime 689 SQLValue consumeIfComplete(T)(ref ubyte[] packet, bool binary, bool unsigned) 690 if(is(T==Date)) 691 { 692 static assert(false, "consumeIfComplete!Date"); 693 } 694 SQLValue consumeIfComplete(T)(ref ubyte[] packet, bool binary, bool unsigned) 695 if(is(T==DateTime)) 696 { 697 static assert(false, "consumeIfComplete!DateTime"); 698 } 699 700 SQLValue consumeIfComplete()(ref ubyte[] packet, SQLType sqlType, bool binary, bool unsigned, ushort charSet) 701 { 702 switch(sqlType) 703 { 704 default: assert(false, "Unsupported SQL type "~to!string(sqlType)); 705 case SQLType.NULL: 706 SQLValue result; 707 result.isIncomplete = false; 708 result.isNull = true; 709 return result; 710 case SQLType.TINY: 711 return packet.consumeIfComplete!byte(binary, unsigned); 712 case SQLType.SHORT: 713 return packet.consumeIfComplete!short(binary, unsigned); 714 case SQLType.INT24: 715 return packet.consumeIfComplete!(int, 3)(binary, unsigned); 716 case SQLType.INT: 717 return packet.consumeIfComplete!int(binary, unsigned); 718 case SQLType.LONGLONG: 719 return packet.consumeIfComplete!long(binary, unsigned); 720 case SQLType.FLOAT: 721 return packet.consumeIfComplete!float(binary, unsigned); 722 case SQLType.DOUBLE: 723 return packet.consumeIfComplete!double(binary, unsigned); 724 case SQLType.TIMESTAMP: 725 return packet.consumeIfComplete!MySQLDateTime(binary, unsigned); 726 case SQLType.TIME: 727 return packet.consumeIfComplete!TimeOfDay(binary, unsigned); 728 case SQLType.YEAR: 729 return packet.consumeIfComplete!ushort(binary, unsigned); 730 case SQLType.DATE: 731 return packet.consumeIfComplete!MySQLDate(binary, unsigned); 732 case SQLType.DATETIME: 733 return packet.consumeIfComplete!MySQLDateTime(binary, unsigned); 734 case SQLType.VARCHAR: 735 case SQLType.ENUM: 736 case SQLType.SET: 737 case SQLType.VARSTRING: 738 case SQLType.STRING: 739 case SQLType.NEWDECIMAL: 740 return packet.consumeIfComplete!string(false, unsigned); 741 case SQLType.TINYBLOB: 742 case SQLType.MEDIUMBLOB: 743 case SQLType.BLOB: 744 case SQLType.LONGBLOB: 745 case SQLType.BIT: // Yes, BIT. See note below. 746 747 // TODO: This line should work. Why doesn't it? 748 //return packet.consumeIfComplete!(ubyte[])(binary, unsigned); 749 750 auto lcb = packet.consumeIfComplete!LCB(); 751 assert(!lcb.isIncomplete); 752 SQLValue result; 753 result.isIncomplete = false; 754 result.isNull = lcb.isNull; 755 if(result.isNull) 756 { 757 // TODO: consumeIfComplete!LCB should be adjusted to do 758 // this itself, but not until I'm certain that nothing 759 // is reliant on the current behavior. 760 packet.popFront(); // LCB length 761 } 762 else 763 { 764 auto data = packet.consume(cast(size_t)lcb.value); 765 if(charSet == 0x3F) // CharacterSet == binary 766 result.value = data; // BLOB-ish 767 else 768 result.value = cast(string)data; // TEXT-ish 769 } 770 771 // Type BIT is treated as a length coded binary (like a BLOB or VARCHAR), 772 // not like an integral type. So convert the binary data to a bool. 773 // See: http://dev.mysql.com/doc/internals/en/binary-protocol-value.html 774 if(sqlType == SQLType.BIT) 775 { 776 enforce!MYXProtocol(result.value.length == 1, 777 "Expected BIT to arrive as an LCB with length 1, but got length "~to!string(result.value.length)); 778 779 result.value = result.value[0] == 1; 780 } 781 782 return result; 783 } 784 } 785 786 /++ 787 Extract number of bytes used for this LCB 788 789 Returns the number of bytes required to store this LCB 790 791 See_Also: $(LINK http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements) 792 793 Returns: 0 if it's a null value, or number of bytes in other cases 794 +/ 795 byte getNumLCBBytes(in ubyte lcbHeader) pure nothrow 796 { 797 switch(lcbHeader) 798 { 799 case 251: return 0; // null 800 case 0: .. case 250: return 1; // 8-bit 801 case 252: return 2; // 16-bit 802 case 253: return 3; // 24-bit 803 case 254: return 8; // 64-bit 804 805 case 255: 806 default: 807 assert(0); 808 } 809 assert(0); 810 } 811 812 813 /++ 814 Decodes a Length Coded Binary from a packet 815 816 See_Also: struct `mysql.protocol.extra_types.LCB` 817 818 Params: 819 packet = A packet that starts with a LCB. The bytes is popped off 820 iff the packet is complete. See `mysql.protocol.extra_types.LCB`. 821 822 Returns: A decoded LCB value 823 +/ 824 T consumeIfComplete(T:LCB)(ref ubyte[] packet) pure nothrow 825 in 826 { 827 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 828 } 829 body 830 { 831 auto lcb = packet.decodeLCBHeader(); 832 if(lcb.isNull || lcb.isIncomplete) 833 return lcb; 834 835 if(lcb.numBytes > 1) 836 { 837 // We know it's complete, so we have to start consuming the LCB 838 // Single byte values doesn't have a length 839 packet.popFront(); // LCB length 840 } 841 842 assert(packet.length >= lcb.numBytes); 843 844 lcb.value = packet.consume!ulong(lcb.numBytes); 845 return lcb; 846 } 847 848 LCB decodeLCBHeader(in ubyte[] packet) pure nothrow 849 in 850 { 851 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 852 } 853 body 854 { 855 LCB lcb; 856 lcb.numBytes = getNumLCBBytes(packet.front); 857 if(lcb.numBytes == 0) 858 { 859 lcb.isNull = true; 860 return lcb; 861 } 862 863 assert(!lcb.isNull); 864 // -1 for LCB length as we haven't popped it off yet 865 lcb.isIncomplete = (lcb.numBytes > 1) && (packet.length-1 < lcb.numBytes); 866 if(lcb.isIncomplete) 867 { 868 // Not enough bytes to store data. We don't remove any data, and expect 869 // the caller to check isIncomplete and take action to fetch more data 870 // and call this method at a later time 871 return lcb; 872 } 873 874 assert(!lcb.isIncomplete); 875 return lcb; 876 } 877 878 /++ 879 Decodes a Length Coded Binary from a packet 880 881 See_Also: struct `mysql.protocol.extra_types.LCB` 882 883 Params: 884 packet = A packet that starts with a LCB. See `mysql.protocol.extra_types.LCB`. 885 886 Returns: A decoded LCB value 887 +/ 888 LCB decode(T:LCB)(in ubyte[] packet) pure nothrow 889 in 890 { 891 assert(packet.length >= 1, "packet has to include at least the LCB length byte"); 892 } 893 body 894 { 895 auto lcb = packet.decodeLCBHeader(); 896 if(lcb.isNull || lcb.isIncomplete) 897 return lcb; 898 assert(packet.length >= lcb.totalBytes); 899 900 if(lcb.numBytes == 0) 901 lcb.value = 0; 902 else if(lcb.numBytes == 1) 903 lcb.value = packet.decode!ulong(lcb.numBytes); 904 else 905 { 906 // Skip the throwaway byte that indicated "at least 2 more bytes coming" 907 lcb.value = packet[1..$].decode!ulong(lcb.numBytes); 908 } 909 910 return lcb; 911 } 912 913 /++ 914 Parse Length Coded String 915 916 See_Also: $(LINK http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements) 917 +/ 918 string consume(T:LCS)(ref ubyte[] packet) pure 919 in 920 { 921 assert(packet.length >= 1, "LCS packet needs to store at least the LCB header"); 922 } 923 body 924 { 925 auto lcb = packet.consumeIfComplete!LCB(); 926 assert(!lcb.isIncomplete); 927 if(lcb.isNull) 928 return null; 929 enforce!MYXProtocol(lcb.value <= uint.max, "Protocol Length Coded String is too long"); 930 return cast(string)packet.consume(cast(size_t)lcb.value).idup; 931 } 932 933 /++ 934 Skips over n items, advances the array, and return the newly advanced array 935 to allow method chaining. 936 +/ 937 T[] skip(T)(ref T[] array, size_t n) pure nothrow 938 in 939 { 940 assert(n <= array.length); 941 } 942 body 943 { 944 array = array[n..$]; 945 return array; 946 } 947 948 /++ 949 Converts a value into a sequence of bytes and fills the supplied array. 950 951 Params: 952 IsInt24 = If only the most significant 3 bytes from the value should be used. 953 value = The value to add to array. 954 array = The array we should add the values for. It has to be large enough, 955 and the values are packed starting index 0. 956 +/ 957 void packInto(T, bool IsInt24 = false)(T value, ubyte[] array) pure nothrow 958 in 959 { 960 static if(IsInt24) 961 assert(array.length >= 3); 962 else 963 assert(array.length >= T.sizeof, "Not enough space to unpack "~T.stringof); 964 } 965 body 966 { 967 static if(T.sizeof >= 1) 968 { 969 array[0] = cast(ubyte) (value >> 8*0) & 0xff; 970 } 971 static if(T.sizeof >= 2) 972 { 973 array[1] = cast(ubyte) (value >> 8*1) & 0xff; 974 } 975 static if(!IsInt24) 976 { 977 static if(T.sizeof >= 4) 978 { 979 array[2] = cast(ubyte) (value >> 8*2) & 0xff; 980 array[3] = cast(ubyte) (value >> 8*3) & 0xff; 981 } 982 static if(T.sizeof >= 8) 983 { 984 array[4] = cast(ubyte) (value >> 8*4) & 0xff; 985 array[5] = cast(ubyte) (value >> 8*5) & 0xff; 986 array[6] = cast(ubyte) (value >> 8*6) & 0xff; 987 array[7] = cast(ubyte) (value >> 8*7) & 0xff; 988 } 989 } 990 else 991 { 992 array[2] = cast(ubyte) (value >> 8*2) & 0xff; 993 } 994 } 995 996 ubyte[] packLength(size_t l, out size_t offset) pure nothrow 997 out(result) 998 { 999 assert(result.length >= 1); 1000 } 1001 body 1002 { 1003 ubyte[] t; 1004 if (!l) 1005 { 1006 t.length = 1; 1007 t[0] = 0; 1008 } 1009 else if (l <= 250) 1010 { 1011 t.length = 1+l; 1012 t[0] = cast(ubyte) l; 1013 offset = 1; 1014 } 1015 else if (l <= 0xffff) // 16-bit 1016 { 1017 t.length = 3+l; 1018 t[0] = 252; 1019 packInto(cast(ushort)l, t[1..3]); 1020 offset = 3; 1021 } 1022 else if (l < 0xffffff) // 24-bit 1023 { 1024 t.length = 4+l; 1025 t[0] = 253; 1026 packInto!(uint, true)(cast(uint)l, t[1..4]); 1027 offset = 4; 1028 } 1029 else // 64-bit 1030 { 1031 ulong u = cast(ulong) l; 1032 t.length = 9+l; 1033 t[0] = 254; 1034 u.packInto(t[1..9]); 1035 offset = 9; 1036 } 1037 return t; 1038 } 1039 1040 ubyte[] packLCS(void[] a) pure nothrow 1041 { 1042 size_t offset; 1043 ubyte[] t = packLength(a.length, offset); 1044 if (t[0]) 1045 t[offset..$] = (cast(ubyte[]) a)[0..$]; 1046 return t; 1047 } 1048 1049 @("testLCB") 1050 debug(MYSQLN_TESTS) 1051 unittest 1052 { 1053 static void testLCB(string parseLCBFunc)(bool shouldConsume) 1054 { 1055 ubyte[] buf = [ 0xde, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x01, 0x00 ]; 1056 ubyte[] bufCopy; 1057 1058 bufCopy = buf; 1059 LCB lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1060 assert(lcb.value == 0xde && !lcb.isNull && lcb.totalBytes == 1); 1061 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1062 1063 buf[0] = 251; 1064 bufCopy = buf; 1065 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1066 assert(lcb.value == 0 && lcb.isNull && lcb.totalBytes == 1); 1067 //TODO: This test seems to fail for consumeIfComplete, need to investigate. 1068 // Don't know if fixing it might cause a problem, or if I simple misunderstood 1069 // the function's intent. 1070 if(parseLCBFunc != "consumeIfComplete") 1071 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1072 1073 buf[0] = 252; 1074 bufCopy = buf; 1075 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1076 assert(lcb.value == 0xbbcc && !lcb.isNull && lcb.totalBytes == 3); 1077 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1078 1079 buf[0] = 253; 1080 bufCopy = buf; 1081 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1082 assert(lcb.value == 0xaabbcc && !lcb.isNull && lcb.totalBytes == 4); 1083 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1084 1085 buf[0] = 254; 1086 bufCopy = buf; 1087 lcb = mixin(parseLCBFunc~"!LCB(bufCopy)"); 1088 assert(lcb.value == 0x5566778899aabbcc && !lcb.isNull && lcb.totalBytes == 9); 1089 assert(bufCopy.length == buf.length - (shouldConsume? lcb.totalBytes : 0)); 1090 } 1091 1092 //TODO: Merge 'consumeIfComplete(T:LCB)' and 'decode(T:LCB)', they do 1093 // basically the same thing, only one consumes input and the other 1094 // doesn't. Just want a better idea of where/how/why they're both 1095 // used, and maybe more tests, before I go messing with them. 1096 testLCB!"consumeIfComplete"(true); 1097 testLCB!"decode"(false); 1098 } 1099 1100 @("consume!LCS") 1101 debug(MYSQLN_TESTS) 1102 unittest 1103 { 1104 ubyte[] buf; 1105 ubyte[] bufCopy; 1106 1107 buf.length = 0x2000200; 1108 buf[] = '\x01'; 1109 buf[0] = 250; 1110 buf[1] = '<'; 1111 buf[249] = '!'; 1112 buf[250] = '>'; 1113 bufCopy = buf; 1114 string x = consume!LCS(bufCopy); 1115 assert(x.length == 250 && x[0] == '<' && x[249] == '>'); 1116 1117 buf[] = '\x01'; 1118 buf[0] = 252; 1119 buf[1] = 0xff; 1120 buf[2] = 0xff; 1121 buf[3] = '<'; 1122 buf[0x10000] = '*'; 1123 buf[0x10001] = '>'; 1124 bufCopy = buf; 1125 x = consume!LCS(bufCopy); 1126 assert(x.length == 0xffff && x[0] == '<' && x[0xfffe] == '>'); 1127 1128 buf[] = '\x01'; 1129 buf[0] = 253; 1130 buf[1] = 0xff; 1131 buf[2] = 0xff; 1132 buf[3] = 0xff; 1133 buf[4] = '<'; 1134 buf[0x1000001] = '*'; 1135 buf[0x1000002] = '>'; 1136 bufCopy = buf; 1137 x = consume!LCS(bufCopy); 1138 assert(x.length == 0xffffff && x[0] == '<' && x[0xfffffe] == '>'); 1139 1140 buf[] = '\x01'; 1141 buf[0] = 254; 1142 buf[1] = 0xff; 1143 buf[2] = 0x00; 1144 buf[3] = 0x00; 1145 buf[4] = 0x02; 1146 buf[5] = 0x00; 1147 buf[6] = 0x00; 1148 buf[7] = 0x00; 1149 buf[8] = 0x00; 1150 buf[9] = '<'; 1151 buf[0x2000106] = '!'; 1152 buf[0x2000107] = '>'; 1153 bufCopy = buf; 1154 x = consume!LCS(bufCopy); 1155 assert(x.length == 0x20000ff && x[0] == '<' && x[0x20000fe] == '>'); 1156 } 1157 1158 /// Set packet length and number. It's important that the length of packet has 1159 /// already been set to the final state as its length is used. 1160 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber) pure nothrow 1161 in 1162 { 1163 // packet should include header, and possibly data 1164 assert(packet.length >= 4); 1165 } 1166 body 1167 { 1168 auto dataLength = packet.length - 4; // don't include header in calculated size 1169 assert(dataLength <= uint.max); 1170 packet.setPacketHeader(packetNumber, cast(uint)dataLength); 1171 } 1172 1173 void setPacketHeader(ref ubyte[] packet, ubyte packetNumber, uint dataLength) pure nothrow 1174 in 1175 { 1176 // packet should include header 1177 assert(packet.length >= 4); 1178 // Length is always a 24-bit int 1179 assert(dataLength <= 0xffff_ffff_ffff); 1180 } 1181 body 1182 { 1183 dataLength.packInto!(uint, true)(packet); 1184 packet[3] = packetNumber; 1185 }