1 module mysql.connection; 2 3 4 import std.algorithm; 5 import std.array; 6 import std.functional; 7 import std.string; 8 import std.traits; 9 10 public import mysql.exception; 11 import mysql.packet; 12 import mysql.protocol; 13 public import mysql.type; 14 15 16 immutable CapabilityFlags DefaultClientCaps = CapabilityFlags.CLIENT_LONG_PASSWORD | CapabilityFlags.CLIENT_LONG_FLAG | 17 CapabilityFlags.CLIENT_CONNECT_WITH_DB | CapabilityFlags.CLIENT_PROTOCOL_41 | CapabilityFlags.CLIENT_SECURE_CONNECTION; 18 19 20 private struct ConnectionSettings { 21 CapabilityFlags caps = DefaultClientCaps; 22 23 const(char)[] host; 24 const(char)[] user; 25 const(char)[] pwd; 26 const(char)[] db; 27 ushort port = 3306; 28 } 29 30 31 private struct ConnectionStatus { 32 CapabilityFlags caps = cast(CapabilityFlags)0; 33 34 ulong affected = 0; 35 ulong insertID = 0; 36 ushort flags = 0; 37 ushort error = 0; 38 ushort warnings = 0; 39 } 40 41 42 private struct ServerInfo { 43 const(char)[] versionString; 44 ubyte protocol; 45 ubyte charSet; 46 ushort status; 47 uint connection; 48 uint caps; 49 } 50 51 52 struct PreparedStatement { 53 package: 54 uint id; // todo: investigate if it's really necessary to close statements explicitly 55 uint params; 56 } 57 58 59 struct Connection(SocketType) { 60 void connect(string connectionString) { 61 connectionSettings(connectionString); 62 connect(); 63 } 64 65 void connect(const(char)[] host, ushort port, const(char)[] user, const(char)[] pwd, const(char)[] db, CapabilityFlags caps = DefaultClientCaps) { 66 settings_.host = host; 67 settings_.user = user; 68 settings_.pwd = pwd; 69 settings_.db = db; 70 settings_.port = port; 71 settings_.caps = caps | CapabilityFlags.CLIENT_LONG_PASSWORD | CapabilityFlags.CLIENT_PROTOCOL_41; 72 73 connect(); 74 } 75 76 void use(const(char)[] db) { 77 send(Commands.COM_INIT_DB, db); 78 eatStatus(retrieve()); 79 } 80 81 void ping() { 82 send(Commands.COM_PING); 83 eatStatus(retrieve()); 84 } 85 86 void refresh() { 87 send(Commands.COM_REFRESH); 88 eatStatus(retrieve()); 89 } 90 91 void reset() { 92 send(Commands.COM_RESET_CONNECTION); 93 eatStatus(retrieve()); 94 } 95 96 const(char)[] statistics() { 97 send(Commands.COM_STATISTICS); 98 99 auto answer = retrieve(); 100 return answer.eat!(const(char)[])(answer.remaining); 101 } 102 103 auto prepare(const(char)[] sql) { 104 send(Commands.COM_STMT_PREPARE, sql); 105 106 auto answer = retrieve(); 107 108 if (answer.peek!ubyte != StatusPackets.OK_Packet) 109 check(answer); 110 111 answer.expect!ubyte(0); 112 113 auto id = answer.eat!uint; 114 auto columns = answer.eat!ushort; 115 auto params = answer.eat!ushort; 116 answer.expect!ubyte(0); 117 118 auto warnings = answer.eat!ushort; 119 120 if (params) { 121 MySQLColumn def; 122 foreach (i; 0..params) 123 columnDef(retrieve(), Commands.COM_STMT_PREPARE, def); 124 125 skipEOF(retrieve()); 126 } 127 128 if (columns) { 129 MySQLColumn def; 130 foreach (i; 0..columns) 131 columnDef(retrieve(), Commands.COM_STMT_PREPARE, def); 132 133 skipEOF(retrieve()); 134 } 135 136 return PreparedStatement(id, params); 137 } 138 139 void execute(Args...)(const(char)[] stmt, Args args) { 140 scope(failure) disconnect(); 141 142 auto id = prepare(stmt); 143 execute(id, args); 144 close(id); 145 } 146 147 void begin() { 148 if (status_.flags & StatusFlags.SERVER_STATUS_IN_TRANS) 149 throw new MySQLErrorException("MySQL does not support nested transactions - commit or rollback before starting a new transaction"); 150 151 query("start transaction"); 152 153 assert(status_.flags & StatusFlags.SERVER_STATUS_IN_TRANS); 154 } 155 156 void commit() { 157 if ((status_.flags & StatusFlags.SERVER_STATUS_IN_TRANS) == 0) 158 throw new MySQLErrorException("No active transaction"); 159 160 query("commit"); 161 162 assert((status_.flags & StatusFlags.SERVER_STATUS_IN_TRANS) == 0); 163 } 164 165 void rollback() { 166 if (connected) { 167 if ((status_.flags & StatusFlags.SERVER_STATUS_IN_TRANS) == 0) 168 throw new MySQLErrorException("No active transaction"); 169 170 query("rollback"); 171 172 assert((status_.flags & StatusFlags.SERVER_STATUS_IN_TRANS) == 0); 173 } 174 } 175 176 @property bool inTransaction() const { 177 return connected && (status_.flags & StatusFlags.SERVER_STATUS_IN_TRANS); 178 } 179 180 void execute(Args...)(PreparedStatement stmt, Args args) { 181 scope(failure) disconnect(); 182 183 ensureConnected(); 184 185 seq_ = 0; 186 auto packet = OutputPacket(&out_); 187 packet.put!ubyte(Commands.COM_STMT_EXECUTE); 188 packet.put!uint(stmt.id); 189 packet.put!ubyte(Cursors.CURSOR_TYPE_READ_ONLY); 190 packet.put!uint(1); 191 192 static if (args.length == 0) { 193 enum shouldDiscard = true; 194 } else { 195 enum shouldDiscard = !isCallable!(args[args.length - 1]); 196 } 197 198 enum argCount = shouldDiscard ? args.length : (args.length - 1); 199 200 if (!argCount && stmt.params) 201 throw new MySQLErrorException("Wrong number of parameters for query"); 202 203 static if (argCount) { 204 enum NullsCapacity = 128; // must be power of 2 205 ubyte[NullsCapacity >> 3] nulls; 206 size_t bitsOut = 0; 207 size_t indexArg = 0; 208 foreach(i, arg; args[0..argCount]) { 209 const auto index = (indexArg >> 3) & (NullsCapacity - 1); 210 const auto bit = indexArg & 7; 211 212 static if (is(typeof(arg) == typeof(null))) { 213 nulls[index] = nulls[index] | (1 << bit); 214 ++indexArg; 215 } else static if (isArray!(typeof(arg)) && !isSomeString!(typeof(arg))) { 216 indexArg += arg.length; 217 } else { 218 ++indexArg; 219 } 220 221 if ((i == argCount - 1) || ((indexArg - bitsOut) >= NullsCapacity)) { 222 while (true) { 223 auto bits = min(indexArg - bitsOut, NullsCapacity); 224 packet.put(nulls[0..(bits + 7) >> 3]); 225 nulls[] = 0; 226 bitsOut += bits; 227 228 if ((indexArg - bitsOut) < NullsCapacity) 229 break; 230 } 231 } 232 } 233 packet.put!ubyte(1); 234 235 if (indexArg != stmt.params) 236 throw new MySQLErrorException("Wrong number of parameters for query"); 237 238 foreach (arg; args[0..argCount]) 239 putValueType(packet, arg); 240 241 foreach (arg; args[0..argCount]) { 242 static if (!is(typeof(arg) == typeof(null))) { 243 putValue(packet, arg); 244 } 245 } 246 } 247 248 packet.finalize(seq_); 249 ++seq_; 250 251 socket_.write(packet.get()); 252 253 auto answer = retrieve(); 254 if (isStatus(answer)) { 255 eatStatus(answer); 256 } else { 257 static if (!shouldDiscard) { 258 resultSet(answer, stmt.id, Commands.COM_STMT_EXECUTE, args[args.length - 1]); 259 } else { 260 discardAll(answer, Commands.COM_STMT_EXECUTE); 261 } 262 } 263 } 264 265 void close(PreparedStatement stmt) { 266 uint[1] data = [ stmt.id ]; 267 send(Commands.COM_STMT_CLOSE, data); 268 } 269 270 @property ulong insertID() { 271 return cast(size_t)status_.insertID; 272 } 273 274 @property ulong affected() { 275 return cast(size_t)status_.affected; 276 } 277 278 @property size_t warnings() { 279 return status_.warnings; 280 } 281 282 @property size_t error() { 283 return status_.error; 284 } 285 286 @property const(char)[] status() const { 287 return info_; 288 } 289 290 @property bool connected() const { 291 return socket_.connected; 292 } 293 294 void disconnect() { 295 socket_.close(); 296 } 297 298 ~this() { 299 disconnect(); 300 } 301 302 private: 303 void connect() { 304 socket_.connect(settings_.host, settings_.port); 305 306 seq_ = 0; 307 eatHandshake(retrieve()); 308 } 309 310 void send(T)(Commands cmd, T[] data) { 311 send(cmd, cast(ubyte*)data.ptr, data.length * T.sizeof); 312 } 313 314 void send(Commands cmd, ubyte* data = null, size_t length = 0) { 315 if(!socket_.connected) 316 connect(); 317 318 seq_ = 0; 319 auto header = OutputPacket(&out_); 320 header.put!ubyte(cmd); 321 header.finalize(seq_, length); 322 ++seq_; 323 324 socket_.write(header.get()); 325 if (length) 326 socket_.write(data[0..length]); 327 } 328 329 void query(const(char)[] sql) { 330 send(Commands.COM_QUERY, sql); 331 332 auto answer = retrieve(); 333 if (isStatus(answer)) 334 eatStatus(answer); 335 } 336 337 void ensureConnected() { 338 if(!socket_.connected) 339 connect(); 340 } 341 342 bool isStatus(InputPacket packet) { 343 auto id = packet.peek!ubyte; 344 switch (id) { 345 case StatusPackets.ERR_Packet: 346 case StatusPackets.OK_Packet: 347 return 1; 348 default: 349 return false; 350 } 351 } 352 353 void check(InputPacket packet) { 354 auto id = packet.peek!ubyte; 355 switch (id) { 356 case StatusPackets.ERR_Packet: 357 case StatusPackets.OK_Packet: 358 eatStatus(packet); 359 break; 360 default: 361 break; 362 } 363 } 364 365 InputPacket retrieve() { 366 scope(failure) disconnect(); 367 368 ubyte[4] header; 369 socket_.read(header); 370 371 auto len = header[0] | (header[1] << 8) | (header[2] << 16); 372 auto seq = header[3]; 373 374 if (seq != seq_) 375 throw new MySQLConnectionException("Out of order packet received"); 376 377 ++seq_; 378 379 in_.length = len; 380 socket_.read(in_); 381 382 if (in_.length != len) 383 throw new MySQLConnectionException("Wrong number of bytes read"); 384 385 return InputPacket(&in_); 386 } 387 388 void eatHandshake(InputPacket packet) { 389 scope(failure) disconnect(); 390 391 server_.protocol = packet.eat!ubyte; 392 server_.versionString = packet.eat!(const(char)[])(packet.countUntil(0, true)); 393 packet.skip(1); 394 395 server_.connection = packet.eat!uint; 396 397 const auto authLengthStart = 8; 398 size_t authLength = authLengthStart; 399 400 ubyte[256] auth; 401 auth[0..authLength] = packet.eat!(ubyte[])(authLength); 402 403 packet.expect!ubyte(0); 404 405 server_.caps = packet.eat!ushort; 406 407 if (!packet.empty) { 408 server_.charSet = packet.eat!ubyte; 409 server_.status = packet.eat!ushort; 410 server_.caps |= packet.eat!ushort << 16; 411 server_.caps |= CapabilityFlags.CLIENT_LONG_PASSWORD; 412 413 if ((server_.caps & CapabilityFlags.CLIENT_PROTOCOL_41) == 0) 414 throw new MySQLProtocolException("Server doesn't support protocol v4.1"); 415 416 if (server_.caps & CapabilityFlags.CLIENT_SECURE_CONNECTION) { 417 packet.skip(1); 418 } else { 419 packet.expect!ubyte(0); 420 } 421 422 packet.skip(10); 423 424 authLength += packet.countUntil(0, true); 425 if (authLength > auth.length) 426 throw new MySQLConnectionException("Bad packet format"); 427 428 auth[authLengthStart..authLength] = packet.eat!(ubyte[])(authLength - authLengthStart); 429 430 packet.expect!ubyte(0); 431 } 432 433 ubyte[20] token; 434 { 435 import std.digest.sha; 436 437 auto pass = sha1Of(cast(const(ubyte)[])settings_.pwd); 438 token = sha1Of(pass); 439 440 SHA1 sha1; 441 sha1.start(); 442 sha1.put(auth[0..authLength]); 443 sha1.put(token); 444 token = sha1.finish(); 445 446 foreach (i; 0..20) 447 token[i] = token[i] ^ pass[i]; 448 } 449 450 status_.caps = cast(CapabilityFlags)(settings_.caps & server_.caps); 451 452 auto reply = OutputPacket(&out_); 453 reply.reserve(64 + settings_.user.length + settings_.pwd.length + settings_.db.length); 454 455 reply.put!uint(status_.caps); 456 reply.put!uint(1); 457 reply.put!ubyte(33); 458 reply.fill(0, 23); 459 460 reply.put(settings_.user); 461 reply.put!ubyte(0); 462 463 if (settings_.pwd.length) { 464 if (status_.caps & CapabilityFlags.CLIENT_SECURE_CONNECTION) { 465 reply.put!ubyte(token.length); 466 reply.put(token); 467 } else { 468 reply.put(token); 469 reply.put!ubyte(0); 470 } 471 } else { 472 reply.put!ubyte(0); 473 } 474 475 if (settings_.db.length && (status_.caps & CapabilityFlags.CLIENT_CONNECT_WITH_DB)) { 476 reply.put(settings_.db); 477 reply.put!ubyte(0); 478 } 479 480 reply.finalize(seq_); 481 ++seq_; 482 483 socket_.write(reply.get()); 484 485 eatStatus(retrieve()); 486 } 487 488 void eatStatus(InputPacket packet) { 489 auto id = packet.eat!ubyte; 490 491 switch (id) { 492 case StatusPackets.OK_Packet: 493 status_.error = 0; 494 status_.affected = packet.eatLenEnc(); 495 status_.insertID = packet.eatLenEnc(); 496 status_.flags = packet.eat!ushort; 497 status_.warnings = packet.eat!ushort; 498 499 if (status_.caps & CapabilityFlags.CLIENT_SESSION_TRACK) { 500 info(packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc())); 501 packet.skip(1); 502 503 if (status_.flags & StatusFlags.SERVER_SESSION_STATE_CHANGED) { 504 packet.skip(cast(size_t)packet.eatLenEnc()); 505 packet.skip(1); 506 } 507 } else if (!packet.empty) { 508 auto len = cast(size_t)packet.eatLenEnc(); 509 info(packet.eat!(const(char)[])(min(len, packet.remaining))); 510 } 511 break; 512 case StatusPackets.EOF_Packet: 513 status_.warnings = packet.eat!ushort; 514 status_.flags = packet.eat!ushort; 515 info([]); 516 break; 517 case StatusPackets.ERR_Packet: 518 status_.flags = 0; 519 status_.error = packet.eat!ushort; 520 packet.skip(6); 521 info(packet.eat!(const(char)[])(packet.remaining)); 522 523 switch(status_.error) { 524 case ErrorCodes.ER_DUP_ENTRY_WITH_KEY_NAME: 525 case ErrorCodes.ER_DUP_ENTRY: 526 throw new MySQLDuplicateEntryException(cast(string)info_); 527 default: 528 throw new MySQLErrorException(cast(string)info_); 529 } 530 default: 531 throw new MySQLProtocolException("Unexpected packet format"); 532 } 533 } 534 535 void info(const(char)[] value) { 536 info_.length = value.length; 537 info_[0..$] = value; 538 } 539 540 void columnDef(InputPacket packet, Commands cmd, ref MySQLColumn def) { 541 auto catalog = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()); 542 auto schema = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()); 543 auto table = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()); 544 auto org_table = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()); 545 def.name = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()).idup; // todo: fix allocation 546 auto org_name = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()); 547 auto next_length = cast(size_t)packet.eatLenEnc(); 548 auto char_set = packet.eat!ushort; 549 def.length = packet.eat!uint; 550 def.type = cast(ColumnTypes)packet.eat!ubyte; 551 def.flags = packet.eat!ushort; 552 def.decimals = packet.eat!ubyte; 553 554 packet.expect!ushort(0); 555 556 if (cmd == Commands.COM_FIELD_LIST) { 557 auto default_values = packet.eat!(const(char)[])(cast(size_t)packet.eatLenEnc()); 558 } 559 } 560 561 auto columnDefs(size_t count, Commands cmd) { 562 header_.length = count; 563 foreach (i; 0..count) 564 columnDef(retrieve(), cmd, header_[i]); 565 return header_; 566 } 567 568 void resultSetRow(InputPacket packet, Commands cmd, MySQLHeader header, MySQLRow row) { 569 assert(row.length == header.length); 570 571 packet.expect!ubyte(0); 572 auto nulls = packet.eat!(ubyte[])((header.length + 2 + 7) >> 3); 573 foreach (i, column; header) { 574 const auto index = (i + 2) >> 3; // bit offset of 2 575 const auto bit = (i + 2) & 7; 576 577 if ((nulls[index] & (1 << bit)) == 0) { 578 row.set(i, eatValue(packet, column)); 579 } else { 580 row.nullify(i); 581 } 582 } 583 assert(packet.empty); 584 } 585 586 bool callHandler(RowHandler)(RowHandler handler, size_t i, MySQLHeader header, MySQLRow row) if ((ParameterTypeTuple!(RowHandler).length == 1) && is(ParameterTypeTuple!(RowHandler)[0] == MySQLRow)) { 587 static if (is(ReturnType!(RowHandler) == void)) { 588 handler(row); 589 return true; 590 } else { 591 return handler(row); // return type must be bool 592 } 593 } 594 595 bool callHandler(RowHandler)(RowHandler handler, size_t i, MySQLHeader header, MySQLRow row) if ((ParameterTypeTuple!(RowHandler).length == 2) && isNumeric!(ParameterTypeTuple!(RowHandler)[0]) && is(ParameterTypeTuple!(RowHandler)[1] == MySQLRow)) { 596 static if (is(ReturnType!(RowHandler) == void)) { 597 handler(cast(ParameterTypeTuple!(RowHandler)[0])i, row); 598 return true; 599 } else { 600 return handler(cast(ParameterTypeTuple!(RowHandler)[0])i, row); // return type must be bool 601 } 602 } 603 604 bool callHandler(RowHandler)(RowHandler handler, size_t i, MySQLHeader header, MySQLRow row) if ((ParameterTypeTuple!(RowHandler).length == 2) && is(ParameterTypeTuple!(RowHandler)[0] == MySQLHeader) && is(ParameterTypeTuple!(RowHandler)[1] == MySQLRow)) { 605 static if (is(ReturnType!(RowHandler) == void)) { 606 handler(header, row); 607 return true; 608 } else { 609 return handler(header, row); // return type must be bool 610 } 611 } 612 613 bool callHandler(RowHandler)(RowHandler handler, size_t i, MySQLHeader header, MySQLRow row) if ((ParameterTypeTuple!(RowHandler).length == 3) && isNumeric!(ParameterTypeTuple!(RowHandler)[0]) && is(ParameterTypeTuple!(RowHandler)[1] == MySQLHeader) && is(ParameterTypeTuple!(RowHandler)[2] == MySQLRow)) { 614 static if (is(ReturnType!(RowHandler) == void)) { 615 handler(i, header, row); 616 return true; 617 } else { 618 return handler(i, header, row); // return type must be bool 619 } 620 } 621 622 void resultSet(RowHandler)(InputPacket packet, uint stmt, Commands cmd, RowHandler handler) { 623 auto columns = cast(size_t)packet.eatLenEnc(); 624 auto header = columnDefs(columns, cmd); 625 row_.length = columns; 626 row_.header(header); 627 628 size_t index = 0; 629 auto statusFlags = skipEOF(retrieve()); 630 if (statusFlags & StatusFlags.SERVER_STATUS_CURSOR_EXISTS) { 631 uint[2] data = [ stmt, 4096 ]; // todo: make setting - rows per fetch 632 while (statusFlags & (StatusFlags.SERVER_STATUS_CURSOR_EXISTS | StatusFlags.SERVER_MORE_RESULTS_EXISTS)) { 633 send(Commands.COM_STMT_FETCH, data); 634 635 auto answer = retrieve(); 636 if (answer.peek!ubyte == StatusPackets.ERR_Packet) 637 check(answer); 638 639 auto row = answer.empty ? retrieve() : answer; 640 while (true) { 641 if (row.peek!ubyte == StatusPackets.EOF_Packet) { 642 statusFlags = skipEOF(row); 643 break; 644 } 645 646 resultSetRow(row, Commands.COM_STMT_FETCH, header, row_); 647 if (!callHandler(handler, index++, header, row_)) { 648 discardUntilEOF(retrieve()); 649 statusFlags = 0; 650 break; 651 } 652 row = retrieve(); 653 } 654 } 655 } else { 656 auto row = retrieve(); 657 while (true) { 658 if (row.peek!ubyte == StatusPackets.EOF_Packet) { 659 eatStatus(row); 660 break; 661 } 662 663 resultSetRow(row, cmd, header, row_); 664 if (!callHandler(handler, index++, header, row_)) { 665 discardUntilEOF(retrieve()); 666 break; 667 } 668 669 row = retrieve(); 670 } 671 } 672 } 673 674 void discardAll(InputPacket packet, Commands cmd) { 675 auto columns = cast(size_t)packet.eatLenEnc(); 676 auto defs = columnDefs(columns, cmd); 677 678 auto statusFlags = skipEOF(retrieve()); 679 if ((statusFlags & StatusFlags.SERVER_STATUS_CURSOR_EXISTS) == 0) { 680 while (true) { 681 auto row = retrieve(); 682 if (row.peek!ubyte == StatusPackets.EOF_Packet) { 683 eatStatus(row); 684 break; 685 } 686 } 687 } 688 } 689 690 void discardUntilEOF(InputPacket packet) { 691 if (packet.peek!ubyte == StatusPackets.EOF_Packet) { 692 eatStatus(packet); 693 return; 694 } else { 695 while (true) { 696 if (packet.peek!ubyte == StatusPackets.EOF_Packet) { 697 eatStatus(packet); 698 break; 699 } 700 packet = retrieve(); 701 } 702 } 703 } 704 705 auto skipEOF(InputPacket packet) { 706 auto id = packet.eat!ubyte; 707 if (id != StatusPackets.EOF_Packet) 708 throw new MySQLProtocolException("Unexpected packet format"); 709 710 packet.skip(2); 711 return packet.eat!ushort(); 712 } 713 714 void connectionSettings(const(char)[] connectionString) { 715 import std.conv; 716 717 auto remaining = connectionString; 718 719 auto indexValue = remaining.indexOf("="); 720 while (!remaining.empty) { 721 auto indexValueEnd = remaining.indexOf(";", indexValue); 722 if (indexValueEnd <= 0) 723 indexValueEnd = remaining.length; 724 725 auto name = strip(remaining[0..indexValue]); 726 auto value = strip(remaining[indexValue+1..indexValueEnd]); 727 728 switch (name) { 729 case "host": 730 settings_.host = value; 731 break; 732 case "user": 733 settings_.user = value; 734 break; 735 case "pwd": 736 settings_.pwd = value; 737 break; 738 case "db": 739 settings_.db = value; 740 break; 741 case "port": 742 settings_.port = to!ushort(value); 743 break; 744 default: 745 throw new MySQLException("Bad connection string: " ~ cast(string)connectionString); 746 } 747 748 if (indexValueEnd == remaining.length) 749 return; 750 751 remaining = remaining[indexValueEnd+1..$]; 752 indexValue = remaining.indexOf("="); 753 } 754 755 throw new MySQLException("Bad connection string: " ~ cast(string)connectionString); 756 } 757 758 SocketType socket_; 759 MySQLHeader header_; 760 MySQLRow row_; 761 char[] info_; 762 ubyte[] in_; 763 ubyte[] out_; 764 ubyte seq_ = 0; 765 766 ConnectionStatus status_; 767 ConnectionSettings settings_; 768 ServerInfo server_; 769 }