github.com/evdatsion/aphelion-dpos-bft@v0.32.1/abci/cmd/abci-cli/abci-cli.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "strings" 11 12 "github.com/spf13/cobra" 13 14 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 15 "github.com/evdatsion/aphelion-dpos-bft/libs/log" 16 17 abcicli "github.com/evdatsion/aphelion-dpos-bft/abci/client" 18 "github.com/evdatsion/aphelion-dpos-bft/abci/example/code" 19 "github.com/evdatsion/aphelion-dpos-bft/abci/example/counter" 20 "github.com/evdatsion/aphelion-dpos-bft/abci/example/kvstore" 21 "github.com/evdatsion/aphelion-dpos-bft/abci/server" 22 servertest "github.com/evdatsion/aphelion-dpos-bft/abci/tests/server" 23 "github.com/evdatsion/aphelion-dpos-bft/abci/types" 24 "github.com/evdatsion/aphelion-dpos-bft/abci/version" 25 "github.com/evdatsion/aphelion-dpos-bft/crypto/merkle" 26 ) 27 28 // client is a global variable so it can be reused by the console 29 var ( 30 client abcicli.Client 31 logger log.Logger 32 ) 33 34 // flags 35 var ( 36 // global 37 flagAddress string 38 flagAbci string 39 flagVerbose bool // for the println output 40 flagLogLevel string // for the logger 41 42 // query 43 flagPath string 44 flagHeight int 45 flagProve bool 46 47 // counter 48 flagSerial bool 49 50 // kvstore 51 flagPersist string 52 ) 53 54 var RootCmd = &cobra.Command{ 55 Use: "abci-cli", 56 Short: "the ABCI CLI tool wraps an ABCI client", 57 Long: "the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers", 58 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 59 60 switch cmd.Use { 61 case "counter", "kvstore": // for the examples apps, don't pre-run 62 return nil 63 case "version": // skip running for version command 64 return nil 65 } 66 67 if logger == nil { 68 allowLevel, err := log.AllowLevel(flagLogLevel) 69 if err != nil { 70 return err 71 } 72 logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), allowLevel) 73 } 74 if client == nil { 75 var err error 76 client, err = abcicli.NewClient(flagAddress, flagAbci, false) 77 if err != nil { 78 return err 79 } 80 client.SetLogger(logger.With("module", "abci-client")) 81 if err := client.Start(); err != nil { 82 return err 83 } 84 } 85 return nil 86 }, 87 } 88 89 // Structure for data passed to print response. 90 type response struct { 91 // generic abci response 92 Data []byte 93 Code uint32 94 Info string 95 Log string 96 97 Query *queryResponse 98 } 99 100 type queryResponse struct { 101 Key []byte 102 Value []byte 103 Height int64 104 Proof *merkle.Proof 105 } 106 107 func Execute() error { 108 addGlobalFlags() 109 addCommands() 110 return RootCmd.Execute() 111 } 112 113 func addGlobalFlags() { 114 RootCmd.PersistentFlags().StringVarP(&flagAddress, "address", "", "tcp://0.0.0.0:26658", "address of application socket") 115 RootCmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "either socket or grpc") 116 RootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "print the command and results as if it were a console session") 117 RootCmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level") 118 } 119 120 func addQueryFlags() { 121 queryCmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "path to prefix query with") 122 queryCmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "height to query the blockchain at") 123 queryCmd.PersistentFlags().BoolVarP(&flagProve, "prove", "", false, "whether or not to return a merkle proof of the query result") 124 } 125 126 func addCounterFlags() { 127 counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "enforce incrementing (serial) transactions") 128 } 129 130 func addKVStoreFlags() { 131 kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database") 132 } 133 134 func addCommands() { 135 RootCmd.AddCommand(batchCmd) 136 RootCmd.AddCommand(consoleCmd) 137 RootCmd.AddCommand(echoCmd) 138 RootCmd.AddCommand(infoCmd) 139 RootCmd.AddCommand(setOptionCmd) 140 RootCmd.AddCommand(deliverTxCmd) 141 RootCmd.AddCommand(checkTxCmd) 142 RootCmd.AddCommand(commitCmd) 143 RootCmd.AddCommand(versionCmd) 144 RootCmd.AddCommand(testCmd) 145 addQueryFlags() 146 RootCmd.AddCommand(queryCmd) 147 148 // examples 149 addCounterFlags() 150 RootCmd.AddCommand(counterCmd) 151 addKVStoreFlags() 152 RootCmd.AddCommand(kvstoreCmd) 153 } 154 155 var batchCmd = &cobra.Command{ 156 Use: "batch", 157 Short: "run a batch of abci commands against an application", 158 Long: `run a batch of abci commands against an application 159 160 This command is run by piping in a file containing a series of commands 161 you'd like to run: 162 163 abci-cli batch < example.file 164 165 where example.file looks something like: 166 167 set_option serial on 168 check_tx 0x00 169 check_tx 0xff 170 deliver_tx 0x00 171 check_tx 0x00 172 deliver_tx 0x01 173 deliver_tx 0x04 174 info 175 `, 176 Args: cobra.ExactArgs(0), 177 RunE: func(cmd *cobra.Command, args []string) error { 178 return cmdBatch(cmd, args) 179 }, 180 } 181 182 var consoleCmd = &cobra.Command{ 183 Use: "console", 184 Short: "start an interactive ABCI console for multiple commands", 185 Long: `start an interactive ABCI console for multiple commands 186 187 This command opens an interactive console for running any of the other commands 188 without opening a new connection each time 189 `, 190 Args: cobra.ExactArgs(0), 191 ValidArgs: []string{"echo", "info", "set_option", "deliver_tx", "check_tx", "commit", "query"}, 192 RunE: func(cmd *cobra.Command, args []string) error { 193 return cmdConsole(cmd, args) 194 }, 195 } 196 197 var echoCmd = &cobra.Command{ 198 Use: "echo", 199 Short: "have the application echo a message", 200 Long: "have the application echo a message", 201 Args: cobra.ExactArgs(1), 202 RunE: func(cmd *cobra.Command, args []string) error { 203 return cmdEcho(cmd, args) 204 }, 205 } 206 var infoCmd = &cobra.Command{ 207 Use: "info", 208 Short: "get some info about the application", 209 Long: "get some info about the application", 210 Args: cobra.ExactArgs(0), 211 RunE: func(cmd *cobra.Command, args []string) error { 212 return cmdInfo(cmd, args) 213 }, 214 } 215 var setOptionCmd = &cobra.Command{ 216 Use: "set_option", 217 Short: "set an option on the application", 218 Long: "set an option on the application", 219 Args: cobra.ExactArgs(2), 220 RunE: func(cmd *cobra.Command, args []string) error { 221 return cmdSetOption(cmd, args) 222 }, 223 } 224 225 var deliverTxCmd = &cobra.Command{ 226 Use: "deliver_tx", 227 Short: "deliver a new transaction to the application", 228 Long: "deliver a new transaction to the application", 229 Args: cobra.ExactArgs(1), 230 RunE: func(cmd *cobra.Command, args []string) error { 231 return cmdDeliverTx(cmd, args) 232 }, 233 } 234 235 var checkTxCmd = &cobra.Command{ 236 Use: "check_tx", 237 Short: "validate a transaction", 238 Long: "validate a transaction", 239 Args: cobra.ExactArgs(1), 240 RunE: func(cmd *cobra.Command, args []string) error { 241 return cmdCheckTx(cmd, args) 242 }, 243 } 244 245 var commitCmd = &cobra.Command{ 246 Use: "commit", 247 Short: "commit the application state and return the Merkle root hash", 248 Long: "commit the application state and return the Merkle root hash", 249 Args: cobra.ExactArgs(0), 250 RunE: func(cmd *cobra.Command, args []string) error { 251 return cmdCommit(cmd, args) 252 }, 253 } 254 255 var versionCmd = &cobra.Command{ 256 Use: "version", 257 Short: "print ABCI console version", 258 Long: "print ABCI console version", 259 Args: cobra.ExactArgs(0), 260 RunE: func(cmd *cobra.Command, args []string) error { 261 fmt.Println(version.Version) 262 return nil 263 }, 264 } 265 266 var queryCmd = &cobra.Command{ 267 Use: "query", 268 Short: "query the application state", 269 Long: "query the application state", 270 Args: cobra.ExactArgs(1), 271 RunE: func(cmd *cobra.Command, args []string) error { 272 return cmdQuery(cmd, args) 273 }, 274 } 275 276 var counterCmd = &cobra.Command{ 277 Use: "counter", 278 Short: "ABCI demo example", 279 Long: "ABCI demo example", 280 Args: cobra.ExactArgs(0), 281 RunE: func(cmd *cobra.Command, args []string) error { 282 return cmdCounter(cmd, args) 283 }, 284 } 285 286 var kvstoreCmd = &cobra.Command{ 287 Use: "kvstore", 288 Short: "ABCI demo example", 289 Long: "ABCI demo example", 290 Args: cobra.ExactArgs(0), 291 RunE: func(cmd *cobra.Command, args []string) error { 292 return cmdKVStore(cmd, args) 293 }, 294 } 295 296 var testCmd = &cobra.Command{ 297 Use: "test", 298 Short: "run integration tests", 299 Long: "run integration tests", 300 Args: cobra.ExactArgs(0), 301 RunE: func(cmd *cobra.Command, args []string) error { 302 return cmdTest(cmd, args) 303 }, 304 } 305 306 // Generates new Args array based off of previous call args to maintain flag persistence 307 func persistentArgs(line []byte) []string { 308 309 // generate the arguments to run from original os.Args 310 // to maintain flag arguments 311 args := os.Args 312 args = args[:len(args)-1] // remove the previous command argument 313 314 if len(line) > 0 { // prevents introduction of extra space leading to argument parse errors 315 args = append(args, strings.Split(string(line), " ")...) 316 } 317 return args 318 } 319 320 //-------------------------------------------------------------------------------- 321 322 func compose(fs []func() error) error { 323 if len(fs) == 0 { 324 return nil 325 } else { 326 err := fs[0]() 327 if err == nil { 328 return compose(fs[1:]) 329 } else { 330 return err 331 } 332 } 333 } 334 335 func cmdTest(cmd *cobra.Command, args []string) error { 336 return compose( 337 []func() error{ 338 func() error { return servertest.InitChain(client) }, 339 func() error { return servertest.SetOption(client, "serial", "on") }, 340 func() error { return servertest.Commit(client, nil) }, 341 func() error { return servertest.DeliverTx(client, []byte("abc"), code.CodeTypeBadNonce, nil) }, 342 func() error { return servertest.Commit(client, nil) }, 343 func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeOK, nil) }, 344 func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1}) }, 345 func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeBadNonce, nil) }, 346 func() error { return servertest.DeliverTx(client, []byte{0x01}, code.CodeTypeOK, nil) }, 347 func() error { return servertest.DeliverTx(client, []byte{0x00, 0x02}, code.CodeTypeOK, nil) }, 348 func() error { return servertest.DeliverTx(client, []byte{0x00, 0x03}, code.CodeTypeOK, nil) }, 349 func() error { return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x04}, code.CodeTypeOK, nil) }, 350 func() error { 351 return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil) 352 }, 353 func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}) }, 354 }) 355 } 356 357 func cmdBatch(cmd *cobra.Command, args []string) error { 358 bufReader := bufio.NewReader(os.Stdin) 359 for { 360 361 line, more, err := bufReader.ReadLine() 362 if more { 363 return errors.New("Input line is too long") 364 } else if err == io.EOF { 365 break 366 } else if len(line) == 0 { 367 continue 368 } else if err != nil { 369 return err 370 } 371 372 cmdArgs := persistentArgs(line) 373 if err := muxOnCommands(cmd, cmdArgs); err != nil { 374 return err 375 } 376 fmt.Println() 377 } 378 return nil 379 } 380 381 func cmdConsole(cmd *cobra.Command, args []string) error { 382 for { 383 fmt.Printf("> ") 384 bufReader := bufio.NewReader(os.Stdin) 385 line, more, err := bufReader.ReadLine() 386 if more { 387 return errors.New("Input is too long") 388 } else if err != nil { 389 return err 390 } 391 392 pArgs := persistentArgs(line) 393 if err := muxOnCommands(cmd, pArgs); err != nil { 394 return err 395 } 396 } 397 } 398 399 func muxOnCommands(cmd *cobra.Command, pArgs []string) error { 400 if len(pArgs) < 2 { 401 return errors.New("expecting persistent args of the form: abci-cli [command] <...>") 402 } 403 404 // TODO: this parsing is fragile 405 args := []string{} 406 for i := 0; i < len(pArgs); i++ { 407 arg := pArgs[i] 408 409 // check for flags 410 if strings.HasPrefix(arg, "-") { 411 // if it has an equal, we can just skip 412 if strings.Contains(arg, "=") { 413 continue 414 } 415 // if its a boolean, we can just skip 416 _, err := cmd.Flags().GetBool(strings.TrimLeft(arg, "-")) 417 if err == nil { 418 continue 419 } 420 421 // otherwise, we need to skip the next one too 422 i += 1 423 continue 424 } 425 426 // append the actual arg 427 args = append(args, arg) 428 } 429 var subCommand string 430 var actualArgs []string 431 if len(args) > 1 { 432 subCommand = args[1] 433 } 434 if len(args) > 2 { 435 actualArgs = args[2:] 436 } 437 cmd.Use = subCommand // for later print statements ... 438 439 switch strings.ToLower(subCommand) { 440 case "check_tx": 441 return cmdCheckTx(cmd, actualArgs) 442 case "commit": 443 return cmdCommit(cmd, actualArgs) 444 case "deliver_tx": 445 return cmdDeliverTx(cmd, actualArgs) 446 case "echo": 447 return cmdEcho(cmd, actualArgs) 448 case "info": 449 return cmdInfo(cmd, actualArgs) 450 case "query": 451 return cmdQuery(cmd, actualArgs) 452 case "set_option": 453 return cmdSetOption(cmd, actualArgs) 454 default: 455 return cmdUnimplemented(cmd, pArgs) 456 } 457 } 458 459 func cmdUnimplemented(cmd *cobra.Command, args []string) error { 460 msg := "unimplemented command" 461 462 if len(args) > 0 { 463 msg += fmt.Sprintf(" args: [%s]", strings.Join(args, " ")) 464 } 465 printResponse(cmd, args, response{ 466 Code: codeBad, 467 Log: msg, 468 }) 469 470 fmt.Println("Available commands:") 471 fmt.Printf("%s: %s\n", echoCmd.Use, echoCmd.Short) 472 fmt.Printf("%s: %s\n", infoCmd.Use, infoCmd.Short) 473 fmt.Printf("%s: %s\n", checkTxCmd.Use, checkTxCmd.Short) 474 fmt.Printf("%s: %s\n", deliverTxCmd.Use, deliverTxCmd.Short) 475 fmt.Printf("%s: %s\n", queryCmd.Use, queryCmd.Short) 476 fmt.Printf("%s: %s\n", commitCmd.Use, commitCmd.Short) 477 fmt.Printf("%s: %s\n", setOptionCmd.Use, setOptionCmd.Short) 478 fmt.Println("Use \"[command] --help\" for more information about a command.") 479 480 return nil 481 } 482 483 // Have the application echo a message 484 func cmdEcho(cmd *cobra.Command, args []string) error { 485 msg := "" 486 if len(args) > 0 { 487 msg = args[0] 488 } 489 res, err := client.EchoSync(msg) 490 if err != nil { 491 return err 492 } 493 printResponse(cmd, args, response{ 494 Data: []byte(res.Message), 495 }) 496 return nil 497 } 498 499 // Get some info from the application 500 func cmdInfo(cmd *cobra.Command, args []string) error { 501 var version string 502 if len(args) == 1 { 503 version = args[0] 504 } 505 res, err := client.InfoSync(types.RequestInfo{Version: version}) 506 if err != nil { 507 return err 508 } 509 printResponse(cmd, args, response{ 510 Data: []byte(res.Data), 511 }) 512 return nil 513 } 514 515 const codeBad uint32 = 10 516 517 // Set an option on the application 518 func cmdSetOption(cmd *cobra.Command, args []string) error { 519 if len(args) < 2 { 520 printResponse(cmd, args, response{ 521 Code: codeBad, 522 Log: "want at least arguments of the form: <key> <value>", 523 }) 524 return nil 525 } 526 527 key, val := args[0], args[1] 528 _, err := client.SetOptionSync(types.RequestSetOption{Key: key, Value: val}) 529 if err != nil { 530 return err 531 } 532 printResponse(cmd, args, response{Log: "OK (SetOption doesn't return anything.)"}) // NOTE: Nothing to show... 533 return nil 534 } 535 536 // Append a new tx to application 537 func cmdDeliverTx(cmd *cobra.Command, args []string) error { 538 if len(args) == 0 { 539 printResponse(cmd, args, response{ 540 Code: codeBad, 541 Log: "want the tx", 542 }) 543 return nil 544 } 545 txBytes, err := stringOrHexToBytes(args[0]) 546 if err != nil { 547 return err 548 } 549 res, err := client.DeliverTxSync(types.RequestDeliverTx{Tx: txBytes}) 550 if err != nil { 551 return err 552 } 553 printResponse(cmd, args, response{ 554 Code: res.Code, 555 Data: res.Data, 556 Info: res.Info, 557 Log: res.Log, 558 }) 559 return nil 560 } 561 562 // Validate a tx 563 func cmdCheckTx(cmd *cobra.Command, args []string) error { 564 if len(args) == 0 { 565 printResponse(cmd, args, response{ 566 Code: codeBad, 567 Info: "want the tx", 568 }) 569 return nil 570 } 571 txBytes, err := stringOrHexToBytes(args[0]) 572 if err != nil { 573 return err 574 } 575 res, err := client.CheckTxSync(types.RequestCheckTx{Tx: txBytes}) 576 if err != nil { 577 return err 578 } 579 printResponse(cmd, args, response{ 580 Code: res.Code, 581 Data: res.Data, 582 Info: res.Info, 583 Log: res.Log, 584 }) 585 return nil 586 } 587 588 // Get application Merkle root hash 589 func cmdCommit(cmd *cobra.Command, args []string) error { 590 res, err := client.CommitSync() 591 if err != nil { 592 return err 593 } 594 printResponse(cmd, args, response{ 595 Data: res.Data, 596 }) 597 return nil 598 } 599 600 // Query application state 601 func cmdQuery(cmd *cobra.Command, args []string) error { 602 if len(args) == 0 { 603 printResponse(cmd, args, response{ 604 Code: codeBad, 605 Info: "want the query", 606 Log: "", 607 }) 608 return nil 609 } 610 queryBytes, err := stringOrHexToBytes(args[0]) 611 if err != nil { 612 return err 613 } 614 615 resQuery, err := client.QuerySync(types.RequestQuery{ 616 Data: queryBytes, 617 Path: flagPath, 618 Height: int64(flagHeight), 619 Prove: flagProve, 620 }) 621 if err != nil { 622 return err 623 } 624 printResponse(cmd, args, response{ 625 Code: resQuery.Code, 626 Info: resQuery.Info, 627 Log: resQuery.Log, 628 Query: &queryResponse{ 629 Key: resQuery.Key, 630 Value: resQuery.Value, 631 Height: resQuery.Height, 632 Proof: resQuery.Proof, 633 }, 634 }) 635 return nil 636 } 637 638 func cmdCounter(cmd *cobra.Command, args []string) error { 639 app := counter.NewCounterApplication(flagSerial) 640 logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) 641 642 // Start the listener 643 srv, err := server.NewServer(flagAddress, flagAbci, app) 644 if err != nil { 645 return err 646 } 647 srv.SetLogger(logger.With("module", "abci-server")) 648 if err := srv.Start(); err != nil { 649 return err 650 } 651 652 // Stop upon receiving SIGTERM or CTRL-C. 653 cmn.TrapSignal(logger, func() { 654 // Cleanup 655 srv.Stop() 656 }) 657 658 // Run forever. 659 select {} 660 } 661 662 func cmdKVStore(cmd *cobra.Command, args []string) error { 663 logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) 664 665 // Create the application - in memory or persisted to disk 666 var app types.Application 667 if flagPersist == "" { 668 app = kvstore.NewKVStoreApplication() 669 } else { 670 app = kvstore.NewPersistentKVStoreApplication(flagPersist) 671 app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) 672 } 673 674 // Start the listener 675 srv, err := server.NewServer(flagAddress, flagAbci, app) 676 if err != nil { 677 return err 678 } 679 srv.SetLogger(logger.With("module", "abci-server")) 680 if err := srv.Start(); err != nil { 681 return err 682 } 683 684 // Stop upon receiving SIGTERM or CTRL-C. 685 cmn.TrapSignal(logger, func() { 686 // Cleanup 687 srv.Stop() 688 }) 689 690 // Run forever. 691 select {} 692 } 693 694 //-------------------------------------------------------------------------------- 695 696 func printResponse(cmd *cobra.Command, args []string, rsp response) { 697 698 if flagVerbose { 699 fmt.Println(">", cmd.Use, strings.Join(args, " ")) 700 } 701 702 // Always print the status code. 703 if rsp.Code == types.CodeTypeOK { 704 fmt.Printf("-> code: OK\n") 705 } else { 706 fmt.Printf("-> code: %d\n", rsp.Code) 707 708 } 709 710 if len(rsp.Data) != 0 { 711 // Do no print this line when using the commit command 712 // because the string comes out as gibberish 713 if cmd.Use != "commit" { 714 fmt.Printf("-> data: %s\n", rsp.Data) 715 } 716 fmt.Printf("-> data.hex: 0x%X\n", rsp.Data) 717 } 718 if rsp.Log != "" { 719 fmt.Printf("-> log: %s\n", rsp.Log) 720 } 721 722 if rsp.Query != nil { 723 fmt.Printf("-> height: %d\n", rsp.Query.Height) 724 if rsp.Query.Key != nil { 725 fmt.Printf("-> key: %s\n", rsp.Query.Key) 726 fmt.Printf("-> key.hex: %X\n", rsp.Query.Key) 727 } 728 if rsp.Query.Value != nil { 729 fmt.Printf("-> value: %s\n", rsp.Query.Value) 730 fmt.Printf("-> value.hex: %X\n", rsp.Query.Value) 731 } 732 if rsp.Query.Proof != nil { 733 fmt.Printf("-> proof: %#v\n", rsp.Query.Proof) 734 } 735 } 736 } 737 738 // NOTE: s is interpreted as a string unless prefixed with 0x 739 func stringOrHexToBytes(s string) ([]byte, error) { 740 if len(s) > 2 && strings.ToLower(s[:2]) == "0x" { 741 b, err := hex.DecodeString(s[2:]) 742 if err != nil { 743 err = fmt.Errorf("Error decoding hex argument: %s", err.Error()) 744 return nil, err 745 } 746 return b, nil 747 } 748 749 if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") { 750 err := fmt.Errorf("Invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s) 751 return nil, err 752 } 753 754 return []byte(s[1 : len(s)-1]), nil 755 }