1package mssql 2 3import ( 4 "context" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "strconv" 11 "strings" 12) 13 14//go:generate stringer -type token 15 16type token byte 17 18// token ids 19const ( 20 tokenReturnStatus token = 121 // 0x79 21 tokenColMetadata token = 129 // 0x81 22 tokenOrder token = 169 // 0xA9 23 tokenError token = 170 // 0xAA 24 tokenInfo token = 171 // 0xAB 25 tokenReturnValue token = 0xAC 26 tokenLoginAck token = 173 // 0xad 27 tokenRow token = 209 // 0xd1 28 tokenNbcRow token = 210 // 0xd2 29 tokenEnvChange token = 227 // 0xE3 30 tokenSSPI token = 237 // 0xED 31 tokenDone token = 253 // 0xFD 32 tokenDoneProc token = 254 33 tokenDoneInProc token = 255 34) 35 36// done flags 37// https://msdn.microsoft.com/en-us/library/dd340421.aspx 38const ( 39 doneFinal = 0 40 doneMore = 1 41 doneError = 2 42 doneInxact = 4 43 doneCount = 0x10 44 doneAttn = 0x20 45 doneSrvError = 0x100 46) 47 48// ENVCHANGE types 49// http://msdn.microsoft.com/en-us/library/dd303449.aspx 50const ( 51 envTypDatabase = 1 52 envTypLanguage = 2 53 envTypCharset = 3 54 envTypPacketSize = 4 55 envSortId = 5 56 envSortFlags = 6 57 envSqlCollation = 7 58 envTypBeginTran = 8 59 envTypCommitTran = 9 60 envTypRollbackTran = 10 61 envEnlistDTC = 11 62 envDefectTran = 12 63 envDatabaseMirrorPartner = 13 64 envPromoteTran = 15 65 envTranMgrAddr = 16 66 envTranEnded = 17 67 envResetConnAck = 18 68 envStartedInstanceName = 19 69 envRouting = 20 70) 71 72// COLMETADATA flags 73// https://msdn.microsoft.com/en-us/library/dd357363.aspx 74const ( 75 colFlagNullable = 1 76 // TODO implement more flags 77) 78 79// interface for all tokens 80type tokenStruct interface{} 81 82type orderStruct struct { 83 ColIds []uint16 84} 85 86type doneStruct struct { 87 Status uint16 88 CurCmd uint16 89 RowCount uint64 90 errors []Error 91} 92 93func (d doneStruct) isError() bool { 94 return d.Status&doneError != 0 || len(d.errors) > 0 95} 96 97func (d doneStruct) getError() Error { 98 if len(d.errors) > 0 { 99 return d.errors[len(d.errors)-1] 100 } else { 101 return Error{Message: "Request failed but didn't provide reason"} 102 } 103} 104 105type doneInProcStruct doneStruct 106 107var doneFlags2str = map[uint16]string{ 108 doneFinal: "final", 109 doneMore: "more", 110 doneError: "error", 111 doneInxact: "inxact", 112 doneCount: "count", 113 doneAttn: "attn", 114 doneSrvError: "srverror", 115} 116 117func doneFlags2Str(flags uint16) string { 118 strs := make([]string, 0, len(doneFlags2str)) 119 for flag, tag := range doneFlags2str { 120 if flags&flag != 0 { 121 strs = append(strs, tag) 122 } 123 } 124 return strings.Join(strs, "|") 125} 126 127// ENVCHANGE stream 128// http://msdn.microsoft.com/en-us/library/dd303449.aspx 129func processEnvChg(sess *tdsSession) { 130 size := sess.buf.uint16() 131 r := &io.LimitedReader{R: sess.buf, N: int64(size)} 132 for { 133 var err error 134 var envtype uint8 135 err = binary.Read(r, binary.LittleEndian, &envtype) 136 if err == io.EOF { 137 return 138 } 139 if err != nil { 140 badStreamPanic(err) 141 } 142 switch envtype { 143 case envTypDatabase: 144 sess.database, err = readBVarChar(r) 145 if err != nil { 146 badStreamPanic(err) 147 } 148 _, err = readBVarChar(r) 149 if err != nil { 150 badStreamPanic(err) 151 } 152 case envTypLanguage: 153 // currently ignored 154 // new value 155 if _, err = readBVarChar(r); err != nil { 156 badStreamPanic(err) 157 } 158 // old value 159 if _, err = readBVarChar(r); err != nil { 160 badStreamPanic(err) 161 } 162 case envTypCharset: 163 // currently ignored 164 // new value 165 if _, err = readBVarChar(r); err != nil { 166 badStreamPanic(err) 167 } 168 // old value 169 if _, err = readBVarChar(r); err != nil { 170 badStreamPanic(err) 171 } 172 case envTypPacketSize: 173 packetsize, err := readBVarChar(r) 174 if err != nil { 175 badStreamPanic(err) 176 } 177 _, err = readBVarChar(r) 178 if err != nil { 179 badStreamPanic(err) 180 } 181 packetsizei, err := strconv.Atoi(packetsize) 182 if err != nil { 183 badStreamPanicf("Invalid Packet size value returned from server (%s): %s", packetsize, err.Error()) 184 } 185 sess.buf.ResizeBuffer(packetsizei) 186 case envSortId: 187 // currently ignored 188 // new value 189 if _, err = readBVarChar(r); err != nil { 190 badStreamPanic(err) 191 } 192 // old value, should be 0 193 if _, err = readBVarChar(r); err != nil { 194 badStreamPanic(err) 195 } 196 case envSortFlags: 197 // currently ignored 198 // new value 199 if _, err = readBVarChar(r); err != nil { 200 badStreamPanic(err) 201 } 202 // old value, should be 0 203 if _, err = readBVarChar(r); err != nil { 204 badStreamPanic(err) 205 } 206 case envSqlCollation: 207 // currently ignored 208 var collationSize uint8 209 err = binary.Read(r, binary.LittleEndian, &collationSize) 210 if err != nil { 211 badStreamPanic(err) 212 } 213 214 // SQL Collation data should contain 5 bytes in length 215 if collationSize != 5 { 216 badStreamPanicf("Invalid SQL Collation size value returned from server: %d", collationSize) 217 } 218 219 // 4 bytes, contains: LCID ColFlags Version 220 var info uint32 221 err = binary.Read(r, binary.LittleEndian, &info) 222 if err != nil { 223 badStreamPanic(err) 224 } 225 226 // 1 byte, contains: sortID 227 var sortID uint8 228 err = binary.Read(r, binary.LittleEndian, &sortID) 229 if err != nil { 230 badStreamPanic(err) 231 } 232 233 // old value, should be 0 234 if _, err = readBVarChar(r); err != nil { 235 badStreamPanic(err) 236 } 237 case envTypBeginTran: 238 tranid, err := readBVarByte(r) 239 if len(tranid) != 8 { 240 badStreamPanicf("invalid size of transaction identifier: %d", len(tranid)) 241 } 242 sess.tranid = binary.LittleEndian.Uint64(tranid) 243 if err != nil { 244 badStreamPanic(err) 245 } 246 if sess.logFlags&logTransaction != 0 { 247 sess.log.Printf("BEGIN TRANSACTION %x\n", sess.tranid) 248 } 249 _, err = readBVarByte(r) 250 if err != nil { 251 badStreamPanic(err) 252 } 253 case envTypCommitTran, envTypRollbackTran: 254 _, err = readBVarByte(r) 255 if err != nil { 256 badStreamPanic(err) 257 } 258 _, err = readBVarByte(r) 259 if err != nil { 260 badStreamPanic(err) 261 } 262 if sess.logFlags&logTransaction != 0 { 263 if envtype == envTypCommitTran { 264 sess.log.Printf("COMMIT TRANSACTION %x\n", sess.tranid) 265 } else { 266 sess.log.Printf("ROLLBACK TRANSACTION %x\n", sess.tranid) 267 } 268 } 269 sess.tranid = 0 270 case envEnlistDTC: 271 // currently ignored 272 // new value, should be 0 273 if _, err = readBVarChar(r); err != nil { 274 badStreamPanic(err) 275 } 276 // old value 277 if _, err = readBVarChar(r); err != nil { 278 badStreamPanic(err) 279 } 280 case envDefectTran: 281 // currently ignored 282 // new value 283 if _, err = readBVarChar(r); err != nil { 284 badStreamPanic(err) 285 } 286 // old value, should be 0 287 if _, err = readBVarChar(r); err != nil { 288 badStreamPanic(err) 289 } 290 case envDatabaseMirrorPartner: 291 sess.partner, err = readBVarChar(r) 292 if err != nil { 293 badStreamPanic(err) 294 } 295 _, err = readBVarChar(r) 296 if err != nil { 297 badStreamPanic(err) 298 } 299 case envPromoteTran: 300 // currently ignored 301 // old value, should be 0 302 if _, err = readBVarChar(r); err != nil { 303 badStreamPanic(err) 304 } 305 // dtc token 306 // spec says it should be L_VARBYTE, so this code might be wrong 307 if _, err = readBVarChar(r); err != nil { 308 badStreamPanic(err) 309 } 310 case envTranMgrAddr: 311 // currently ignored 312 // old value, should be 0 313 if _, err = readBVarChar(r); err != nil { 314 badStreamPanic(err) 315 } 316 // XACT_MANAGER_ADDRESS = B_VARBYTE 317 if _, err = readBVarChar(r); err != nil { 318 badStreamPanic(err) 319 } 320 case envTranEnded: 321 // currently ignored 322 // old value, B_VARBYTE 323 if _, err = readBVarChar(r); err != nil { 324 badStreamPanic(err) 325 } 326 // should be 0 327 if _, err = readBVarChar(r); err != nil { 328 badStreamPanic(err) 329 } 330 case envResetConnAck: 331 // currently ignored 332 // old value, should be 0 333 if _, err = readBVarChar(r); err != nil { 334 badStreamPanic(err) 335 } 336 // should be 0 337 if _, err = readBVarChar(r); err != nil { 338 badStreamPanic(err) 339 } 340 case envStartedInstanceName: 341 // currently ignored 342 // old value, should be 0 343 if _, err = readBVarChar(r); err != nil { 344 badStreamPanic(err) 345 } 346 // instance name 347 if _, err = readBVarChar(r); err != nil { 348 badStreamPanic(err) 349 } 350 case envRouting: 351 // RoutingData message is: 352 // ValueLength USHORT 353 // Protocol (TCP = 0) BYTE 354 // ProtocolProperty (new port) USHORT 355 // AlternateServer US_VARCHAR 356 _, err := readUshort(r) 357 if err != nil { 358 badStreamPanic(err) 359 } 360 protocol, err := readByte(r) 361 if err != nil || protocol != 0 { 362 badStreamPanic(err) 363 } 364 newPort, err := readUshort(r) 365 if err != nil { 366 badStreamPanic(err) 367 } 368 newServer, err := readUsVarChar(r) 369 if err != nil { 370 badStreamPanic(err) 371 } 372 // consume the OLDVALUE = %x00 %x00 373 _, err = readUshort(r) 374 if err != nil { 375 badStreamPanic(err) 376 } 377 sess.routedServer = newServer 378 sess.routedPort = newPort 379 default: 380 // ignore rest of records because we don't know how to skip those 381 sess.log.Printf("WARN: Unknown ENVCHANGE record detected with type id = %d\n", envtype) 382 break 383 } 384 385 } 386} 387 388// http://msdn.microsoft.com/en-us/library/dd358180.aspx 389func parseReturnStatus(r *tdsBuffer) ReturnStatus { 390 return ReturnStatus(r.int32()) 391} 392 393func parseOrder(r *tdsBuffer) (res orderStruct) { 394 len := int(r.uint16()) 395 res.ColIds = make([]uint16, len/2) 396 for i := 0; i < len/2; i++ { 397 res.ColIds[i] = r.uint16() 398 } 399 return res 400} 401 402// https://msdn.microsoft.com/en-us/library/dd340421.aspx 403func parseDone(r *tdsBuffer) (res doneStruct) { 404 res.Status = r.uint16() 405 res.CurCmd = r.uint16() 406 res.RowCount = r.uint64() 407 return res 408} 409 410// https://msdn.microsoft.com/en-us/library/dd340553.aspx 411func parseDoneInProc(r *tdsBuffer) (res doneInProcStruct) { 412 res.Status = r.uint16() 413 res.CurCmd = r.uint16() 414 res.RowCount = r.uint64() 415 return res 416} 417 418type sspiMsg []byte 419 420func parseSSPIMsg(r *tdsBuffer) sspiMsg { 421 size := r.uint16() 422 buf := make([]byte, size) 423 r.ReadFull(buf) 424 return sspiMsg(buf) 425} 426 427type loginAckStruct struct { 428 Interface uint8 429 TDSVersion uint32 430 ProgName string 431 ProgVer uint32 432} 433 434func parseLoginAck(r *tdsBuffer) loginAckStruct { 435 size := r.uint16() 436 buf := make([]byte, size) 437 r.ReadFull(buf) 438 var res loginAckStruct 439 res.Interface = buf[0] 440 res.TDSVersion = binary.BigEndian.Uint32(buf[1:]) 441 prognamelen := buf[1+4] 442 var err error 443 if res.ProgName, err = ucs22str(buf[1+4+1 : 1+4+1+prognamelen*2]); err != nil { 444 badStreamPanic(err) 445 } 446 res.ProgVer = binary.BigEndian.Uint32(buf[size-4:]) 447 return res 448} 449 450// http://msdn.microsoft.com/en-us/library/dd357363.aspx 451func parseColMetadata72(r *tdsBuffer) (columns []columnStruct) { 452 count := r.uint16() 453 if count == 0xffff { 454 // no metadata is sent 455 return nil 456 } 457 columns = make([]columnStruct, count) 458 for i := range columns { 459 column := &columns[i] 460 column.UserType = r.uint32() 461 column.Flags = r.uint16() 462 463 // parsing TYPE_INFO structure 464 column.ti = readTypeInfo(r) 465 column.ColName = r.BVarChar() 466 } 467 return columns 468} 469 470// http://msdn.microsoft.com/en-us/library/dd357254.aspx 471func parseRow(r *tdsBuffer, columns []columnStruct, row []interface{}) { 472 for i, column := range columns { 473 row[i] = column.ti.Reader(&column.ti, r) 474 } 475} 476 477// http://msdn.microsoft.com/en-us/library/dd304783.aspx 478func parseNbcRow(r *tdsBuffer, columns []columnStruct, row []interface{}) { 479 bitlen := (len(columns) + 7) / 8 480 pres := make([]byte, bitlen) 481 r.ReadFull(pres) 482 for i, col := range columns { 483 if pres[i/8]&(1<<(uint(i)%8)) != 0 { 484 row[i] = nil 485 continue 486 } 487 row[i] = col.ti.Reader(&col.ti, r) 488 } 489} 490 491// http://msdn.microsoft.com/en-us/library/dd304156.aspx 492func parseError72(r *tdsBuffer) (res Error) { 493 length := r.uint16() 494 _ = length // ignore length 495 res.Number = r.int32() 496 res.State = r.byte() 497 res.Class = r.byte() 498 res.Message = r.UsVarChar() 499 res.ServerName = r.BVarChar() 500 res.ProcName = r.BVarChar() 501 res.LineNo = r.int32() 502 return 503} 504 505// http://msdn.microsoft.com/en-us/library/dd304156.aspx 506func parseInfo(r *tdsBuffer) (res Error) { 507 length := r.uint16() 508 _ = length // ignore length 509 res.Number = r.int32() 510 res.State = r.byte() 511 res.Class = r.byte() 512 res.Message = r.UsVarChar() 513 res.ServerName = r.BVarChar() 514 res.ProcName = r.BVarChar() 515 res.LineNo = r.int32() 516 return 517} 518 519// https://msdn.microsoft.com/en-us/library/dd303881.aspx 520func parseReturnValue(r *tdsBuffer) (nv namedValue) { 521 /* 522 ParamOrdinal 523 ParamName 524 Status 525 UserType 526 Flags 527 TypeInfo 528 CryptoMetadata 529 Value 530 */ 531 r.uint16() 532 nv.Name = r.BVarChar() 533 r.byte() 534 r.uint32() // UserType (uint16 prior to 7.2) 535 r.uint16() 536 ti := readTypeInfo(r) 537 nv.Value = ti.Reader(&ti, r) 538 return 539} 540 541func processSingleResponse(sess *tdsSession, ch chan tokenStruct, outs map[string]interface{}) { 542 defer func() { 543 if err := recover(); err != nil { 544 if sess.logFlags&logErrors != 0 { 545 sess.log.Printf("ERROR: Intercepted panic %v", err) 546 } 547 ch <- err 548 } 549 close(ch) 550 }() 551 552 packet_type, err := sess.buf.BeginRead() 553 if err != nil { 554 if sess.logFlags&logErrors != 0 { 555 sess.log.Printf("ERROR: BeginRead failed %v", err) 556 } 557 ch <- err 558 return 559 } 560 if packet_type != packReply { 561 badStreamPanic(fmt.Errorf("unexpected packet type in reply: got %v, expected %v", packet_type, packReply)) 562 } 563 var columns []columnStruct 564 errs := make([]Error, 0, 5) 565 for { 566 token := token(sess.buf.byte()) 567 if sess.logFlags&logDebug != 0 { 568 sess.log.Printf("got token %v", token) 569 } 570 switch token { 571 case tokenSSPI: 572 ch <- parseSSPIMsg(sess.buf) 573 return 574 case tokenReturnStatus: 575 returnStatus := parseReturnStatus(sess.buf) 576 ch <- returnStatus 577 case tokenLoginAck: 578 loginAck := parseLoginAck(sess.buf) 579 ch <- loginAck 580 case tokenOrder: 581 order := parseOrder(sess.buf) 582 ch <- order 583 case tokenDoneInProc: 584 done := parseDoneInProc(sess.buf) 585 if sess.logFlags&logRows != 0 && done.Status&doneCount != 0 { 586 sess.log.Printf("(%d row(s) affected)\n", done.RowCount) 587 } 588 ch <- done 589 case tokenDone, tokenDoneProc: 590 done := parseDone(sess.buf) 591 done.errors = errs 592 if sess.logFlags&logDebug != 0 { 593 sess.log.Printf("got DONE or DONEPROC status=%d", done.Status) 594 } 595 if done.Status&doneSrvError != 0 { 596 ch <- errors.New("SQL Server had internal error") 597 return 598 } 599 if sess.logFlags&logRows != 0 && done.Status&doneCount != 0 { 600 sess.log.Printf("(%d row(s) affected)\n", done.RowCount) 601 } 602 ch <- done 603 if done.Status&doneMore == 0 { 604 return 605 } 606 case tokenColMetadata: 607 columns = parseColMetadata72(sess.buf) 608 ch <- columns 609 case tokenRow: 610 row := make([]interface{}, len(columns)) 611 parseRow(sess.buf, columns, row) 612 ch <- row 613 case tokenNbcRow: 614 row := make([]interface{}, len(columns)) 615 parseNbcRow(sess.buf, columns, row) 616 ch <- row 617 case tokenEnvChange: 618 processEnvChg(sess) 619 case tokenError: 620 err := parseError72(sess.buf) 621 if sess.logFlags&logDebug != 0 { 622 sess.log.Printf("got ERROR %d %s", err.Number, err.Message) 623 } 624 errs = append(errs, err) 625 if sess.logFlags&logErrors != 0 { 626 sess.log.Println(err.Message) 627 } 628 case tokenInfo: 629 info := parseInfo(sess.buf) 630 if sess.logFlags&logDebug != 0 { 631 sess.log.Printf("got INFO %d %s", info.Number, info.Message) 632 } 633 if sess.logFlags&logMessages != 0 { 634 sess.log.Println(info.Message) 635 } 636 case tokenReturnValue: 637 nv := parseReturnValue(sess.buf) 638 if len(nv.Name) > 0 { 639 name := nv.Name[1:] // Remove the leading "@". 640 if ov, has := outs[name]; has { 641 err = scanIntoOut(name, nv.Value, ov) 642 if err != nil { 643 fmt.Println("scan error", err) 644 ch <- err 645 } 646 } 647 } 648 default: 649 badStreamPanic(fmt.Errorf("unknown token type returned: %v", token)) 650 } 651 } 652} 653 654type parseRespIter byte 655 656const ( 657 parseRespIterContinue parseRespIter = iota // Continue parsing current token. 658 parseRespIterNext // Fetch the next token. 659 parseRespIterDone // Done with parsing the response. 660) 661 662type parseRespState byte 663 664const ( 665 parseRespStateNormal parseRespState = iota // Normal response state. 666 parseRespStateCancel // Query is canceled, wait for server to confirm. 667 parseRespStateClosing // Waiting for tokens to come through. 668) 669 670type parseResp struct { 671 sess *tdsSession 672 ctxDone <-chan struct{} 673 state parseRespState 674 cancelError error 675} 676 677func (ts *parseResp) sendAttention(ch chan tokenStruct) parseRespIter { 678 if err := sendAttention(ts.sess.buf); err != nil { 679 ts.dlogf("failed to send attention signal %v", err) 680 ch <- err 681 return parseRespIterDone 682 } 683 ts.state = parseRespStateCancel 684 return parseRespIterContinue 685} 686 687func (ts *parseResp) dlog(msg string) { 688 if ts.sess.logFlags&logDebug != 0 { 689 ts.sess.log.Println(msg) 690 } 691} 692func (ts *parseResp) dlogf(f string, v ...interface{}) { 693 if ts.sess.logFlags&logDebug != 0 { 694 ts.sess.log.Printf(f, v...) 695 } 696} 697 698func (ts *parseResp) iter(ctx context.Context, ch chan tokenStruct, tokChan chan tokenStruct) parseRespIter { 699 switch ts.state { 700 default: 701 panic("unknown state") 702 case parseRespStateNormal: 703 select { 704 case tok, ok := <-tokChan: 705 if !ok { 706 ts.dlog("response finished") 707 return parseRespIterDone 708 } 709 if err, ok := tok.(net.Error); ok && err.Timeout() { 710 ts.cancelError = err 711 ts.dlog("got timeout error, sending attention signal to server") 712 return ts.sendAttention(ch) 713 } 714 // Pass the token along. 715 ch <- tok 716 return parseRespIterContinue 717 718 case <-ts.ctxDone: 719 ts.ctxDone = nil 720 ts.dlog("got cancel message, sending attention signal to server") 721 return ts.sendAttention(ch) 722 } 723 case parseRespStateCancel: // Read all responses until a DONE or error is received.Auth 724 select { 725 case tok, ok := <-tokChan: 726 if !ok { 727 ts.dlog("response finished but waiting for attention ack") 728 return parseRespIterNext 729 } 730 switch tok := tok.(type) { 731 default: 732 // Ignore all other tokens while waiting. 733 // The TDS spec says other tokens may arrive after an attention 734 // signal is sent. Ignore these tokens and continue looking for 735 // a DONE with attention confirm mark. 736 case doneStruct: 737 if tok.Status&doneAttn != 0 { 738 ts.dlog("got cancellation confirmation from server") 739 if ts.cancelError != nil { 740 ch <- ts.cancelError 741 ts.cancelError = nil 742 } else { 743 ch <- ctx.Err() 744 } 745 return parseRespIterDone 746 } 747 748 // If an error happens during cancel, pass it along and just stop. 749 // We are uncertain to receive more tokens. 750 case error: 751 ch <- tok 752 ts.state = parseRespStateClosing 753 } 754 return parseRespIterContinue 755 case <-ts.ctxDone: 756 ts.ctxDone = nil 757 ts.state = parseRespStateClosing 758 return parseRespIterContinue 759 } 760 case parseRespStateClosing: // Wait for current token chan to close. 761 if _, ok := <-tokChan; !ok { 762 ts.dlog("response finished") 763 return parseRespIterDone 764 } 765 return parseRespIterContinue 766 } 767} 768 769func processResponse(ctx context.Context, sess *tdsSession, ch chan tokenStruct, outs map[string]interface{}) { 770 ts := &parseResp{ 771 sess: sess, 772 ctxDone: ctx.Done(), 773 } 774 defer func() { 775 // Ensure any remaining error is piped through 776 // or the query may look like it executed when it actually failed. 777 if ts.cancelError != nil { 778 ch <- ts.cancelError 779 ts.cancelError = nil 780 } 781 close(ch) 782 }() 783 784 // Loop over multiple responses. 785 for { 786 ts.dlog("initiating response reading") 787 788 tokChan := make(chan tokenStruct) 789 go processSingleResponse(sess, tokChan, outs) 790 791 // Loop over multiple tokens in response. 792 tokensLoop: 793 for { 794 switch ts.iter(ctx, ch, tokChan) { 795 case parseRespIterContinue: 796 // Nothing, continue to next token. 797 case parseRespIterNext: 798 break tokensLoop 799 case parseRespIterDone: 800 return 801 } 802 } 803 } 804} 805