github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/cmd/lit-af/lit-af.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/chzyer/readline"
    11  	"github.com/fatih/color"
    12  	flags "github.com/jessevdk/go-flags"
    13  	"github.com/mit-dci/lit/crypto/koblitz"
    14  	"github.com/mit-dci/lit/litrpc"
    15  	"github.com/mit-dci/lit/lnutil"
    16  	"github.com/mit-dci/lit/logging"
    17  )
    18  
    19  /*
    20  Lit-AF
    21  
    22  The Lit Advanced Functionality interface.
    23  This is a text mode interface to lit.  It connects over jsonrpc to the a lit
    24  node and tells that lit node what to do.  The lit node also responds so that
    25  lit-af can tell what's going on.
    26  
    27  lit-gtk does most of the same things with a gtk interface, but there will be
    28  some yet-undefined advanced functionality only available in lit-af.
    29  
    30  May end up using termbox-go
    31  
    32  */
    33  
    34  const (
    35  	litHomeDirName     = ".lit"
    36  	historyFilename    = "lit-af.history"
    37  	defaultKeyFileName = "privkey.hex"
    38  )
    39  
    40  type Command struct {
    41  	Format           string
    42  	Description      string
    43  	ShortDescription string
    44  }
    45  
    46  type litAfClient struct {
    47  	RPCClient *litrpc.LndcRpcClient
    48  }
    49  
    50  type litAfConfig struct {
    51  	Con        string `long:"con" description:"host to connect to in the form of [<lnadr>@][<host>][:<port>]"`
    52  	LitHomeDir string `long:"litHomeDir" description:"directory to save settings"`
    53  	Port       string `long:"autoListenPort" description:"port that the lit is listening to."`
    54  	Tracker    string `long:"tracker" description:"service to use for looking up node addresses"`
    55  	LogLevel   []bool `short:"v" description:"Set verbosity level to verbose (-v), very verbose (-vv) or very very verbose (-vvv)"`
    56  }
    57  
    58  var (
    59  	defaultCon            = "2448"
    60  	defaultLitHomeDirName = filepath.Join(os.Getenv("HOME"), litHomeDirName)
    61  	defaultTracker        = "http://hubris.media.mit.edu:46580"
    62  )
    63  
    64  // newConfigParser returns a new command line flags parser.
    65  func newConfigParser(conf *litAfConfig, options flags.Options) *flags.Parser {
    66  	parser := flags.NewParser(conf, options)
    67  	return parser
    68  }
    69  
    70  func (lc *litAfClient) litAfSetup(conf litAfConfig) {
    71  
    72  	var err error
    73  
    74  	preParser := newConfigParser(&conf, flags.HelpFlag)
    75  	_, err = preParser.ParseArgs(os.Args) // parse the cli
    76  	if err != nil {
    77  		logging.Fatal(err)
    78  	}
    79  	logLevel := 0
    80  	if len(conf.LogLevel) == 1 { // -v
    81  		logLevel = 1
    82  	} else if len(conf.LogLevel) == 2 { // -vv
    83  		logLevel = 2
    84  	} else if len(conf.LogLevel) >= 3 { // -vvv
    85  		logLevel = 3
    86  	}
    87  	logging.SetLogLevel(logLevel) // defaults to zero
    88  
    89  	// create home directory if it does not exist
    90  	_, err = os.Stat(conf.LitHomeDir)
    91  	if os.IsNotExist(err) {
    92  		os.Mkdir(conf.LitHomeDir, 0700)
    93  	}
    94  
    95  	adr, host, port := lnutil.ParseAdrStringWithPort(conf.Con)
    96  
    97  	if len(conf.Port) > 0 {
    98  		custom_port, err := strconv.ParseUint(conf.Port, 10, 32)
    99  		if err != nil {
   100  			logging.Fatal(err.Error())
   101  		}
   102  		port = uint32(custom_port)
   103  	}
   104  
   105  	logging.Infof("Adr: %s, Host: %s, Port: %d, LitHomeDir: %s", adr, host, port, conf.LitHomeDir)
   106  
   107  	if litrpc.LndcRpcCanConnectLocallyWithHomeDir(conf.LitHomeDir) && adr == "" && (host == "localhost" || host == "127.0.0.1") {
   108  		// con parameter was not passed.
   109  		lc.RPCClient, err = litrpc.NewLocalLndcRpcClientWithHomeDirAndPort(conf.LitHomeDir, port)
   110  		if err != nil {
   111  			logging.Fatal(err.Error())
   112  		}
   113  	} else {
   114  		// con parameter passed.
   115  		if !lnutil.LitAdrOK(adr) {
   116  			logging.Fatal("lit address passed in -con parameter is not valid")
   117  		}
   118  
   119  		keyFilePath := filepath.Join(conf.LitHomeDir, "lit-af-key.hex")
   120  		privKey, err := lnutil.ReadKeyFile(keyFilePath)
   121  		if err != nil {
   122  			logging.Fatal(err.Error())
   123  		}
   124  		key, _ := koblitz.PrivKeyFromBytes(koblitz.S256(), privKey[:])
   125  
   126  		if adr != "" && strings.HasPrefix(adr, "ln1") && host == "" {
   127  			ipv4, _, err := lnutil.Lookup(adr, conf.Tracker, "")
   128  			if err != nil {
   129  				logging.Fatalf("Error looking up address on the tracker: %s", err)
   130  			} else {
   131  				adr = fmt.Sprintf("%s@%s", adr, ipv4)
   132  			}
   133  		} else {
   134  			adr = fmt.Sprintf("%s@%s:%d", adr, host, port)
   135  		}
   136  
   137  		lc.RPCClient, err = litrpc.NewLndcRpcClient(adr, key)
   138  		if err != nil {
   139  			logging.Fatal(err.Error())
   140  		}
   141  	}
   142  }
   143  
   144  // for now just testing how to connect and get messages back and forth
   145  func main() {
   146  
   147  	var err error
   148  	lc := new(litAfClient)
   149  	conf := litAfConfig{
   150  		Con:        defaultCon,
   151  		LitHomeDir: defaultLitHomeDirName,
   152  		Tracker:    defaultTracker,
   153  	}
   154  	lc.litAfSetup(conf) // setup lit-af to start
   155  
   156  	rl, err := readline.NewEx(&readline.Config{
   157  		Prompt:       lnutil.Prompt("lit-af") + lnutil.White("# "),
   158  		HistoryFile:  filepath.Join(conf.LitHomeDir, historyFilename),
   159  		AutoComplete: lc.NewAutoCompleter(),
   160  	})
   161  	if err != nil {
   162  		logging.Fatal(err)
   163  	}
   164  	defer rl.Close()
   165  
   166  	// main shell loop
   167  	for {
   168  		// setup reader with max 4K input chars
   169  		msg, err := rl.Readline()
   170  		if err != nil {
   171  			break
   172  		}
   173  		msg = strings.TrimSpace(msg)
   174  		if len(msg) == 0 {
   175  			continue
   176  		}
   177  		rl.SaveHistory(msg)
   178  
   179  		cmdslice := strings.Fields(msg)                         // chop input up on whitespace
   180  		fmt.Fprintf(color.Output, "entered command: %s\n", msg) // immediate feedback
   181  
   182  		err = lc.Shellparse(cmdslice)
   183  		if err != nil { // only error should be user exit
   184  			logging.Fatal(err)
   185  		}
   186  	}
   187  }
   188  
   189  func (lc *litAfClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
   190  	return lc.RPCClient.Call(serviceMethod, args, reply)
   191  }