//////////////////////////////////////////////////////////////////////////////// // Copyright © 2020 Privategrity Corporation / // / // All rights reserved. / //////////////////////////////////////////////////////////////////////////////// // Package cmd initializes the CLI and config parsers as well as the logger. package cmd import ( "encoding/binary" "encoding/hex" "fmt" "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/interfaces/params" "gitlab.com/xx_network/primitives/id" "io/ioutil" "os" "strconv" "time" ) // Execute adds all child commands to the root command and sets flags // appropriately. This is called by main.main(). It only needs to // happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } // func setKeyParams(client *api.Client) { // jww.DEBUG.Printf("Trying to parse key parameters...") // minKeys, err := strconv.Atoi(keyParams[0]) // if err != nil { // return // } // maxKeys, err := strconv.Atoi(keyParams[1]) // if err != nil { // return // } // numRekeys, err := strconv.Atoi(keyParams[2]) // if err != nil { // return // } // ttlScalar, err := strconv.ParseFloat(keyParams[3], 64) // if err != nil { // return // } // minNumKeys, err := strconv.Atoi(keyParams[4]) // if err != nil { // return // } // jww.DEBUG.Printf("Setting key generation parameters: %d, %d, %d, %f, %d", // minKeys, maxKeys, numRekeys, ttlScalar, minNumKeys) // params := client.GetKeyParams() // params.MinKeys = uint16(minKeys) // params.MaxKeys = uint16(maxKeys) // params.NumRekeys = uint16(numRekeys) // params.TTLScalar = ttlScalar // params.MinNumKeys = uint16(minNumKeys) // } // type userSearcher struct { // foundUserChan chan []byte // } // func newUserSearcher() api.SearchCallback { // us := userSearcher{} // us.foundUserChan = make(chan []byte) // return &us // } // func (us *userSearcher) Callback(userID, pubKey []byte, err error) { // if err != nil { // jww.ERROR.Printf("Could not find searched user: %+v", err) // } else { // us.foundUserChan <- userID // } // } // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "client", Short: "Runs a client for cMix anonymous communication platform", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { initLog(viper.GetBool("verbose"), viper.GetString("log")) jww.INFO.Printf(Version()) pass := viper.GetString("password") storeDir := viper.GetString("session") regCode := viper.GetString("regcode") var client *api.Client if _, err := os.Stat(storeDir); os.IsNotExist(err) { // Load NDF ndfPath := viper.GetString("ndf") ndfJSON, err := ioutil.ReadFile(ndfPath) if err != nil { jww.FATAL.Panicf(err.Error()) } client, err = api.NewClient(string(ndfJSON), storeDir, []byte(pass), regCode) if err != nil { jww.FATAL.Panicf("%+v", err) } } else { client, err = api.LoadClient(storeDir, []byte(pass)) if err != nil { jww.FATAL.Panicf("%+v", err) } } user := client.GetUser() jww.INFO.Printf("%s", user.ID) err := client.StartNetworkFollower() if err != nil { jww.FATAL.Panicf("%+v", err) } // Wait until connected or crash on timeout connected := make(chan bool, 1) client.GetHealth().AddChannel(connected) waitTimeout := time.Duration(viper.GetUint("waitTimeout")) timeoutTimer := time.NewTimer(waitTimeout * time.Second) isConnected := false for !isConnected { select { case isConnected = <-connected: jww.INFO.Printf("health status: %v\n", isConnected) break case <-timeoutTimer.C: jww.FATAL.Panic("timeout on connection") } } // Send Messages msgBody := viper.GetString("message") recipientID := getUIDFromString(viper.GetString("destid")) msg := client.NewCMIXMessage(recipientID, []byte(msgBody)) params := params.GetDefaultCMIX() sendCnt := int(viper.GetUint("sendCount")) sendDelay := time.Duration(viper.GetUint("sendDelay")) for i := 0; i < sendCnt; i++ { fmt.Printf("Sending to %s: %s\n", recipientID, msgBody) roundID, err := client.SendCMIX(msg, params) if err != nil { jww.FATAL.Panicf("%+v", err) } jww.INFO.Printf("RoundID: %d\n", roundID) time.Sleep(sendDelay * time.Millisecond) } // Wait until message timeout or we receive enough then exit // TODO: Actually check for how many messages we've received receiveCnt := viper.GetUint("receiveCount") timeoutTimer = time.NewTimer(waitTimeout * time.Second) done := false for !done { select { case <-timeoutTimer.C: fmt.Println("Timed out!") done = true break } } fmt.Printf("Received %d", receiveCnt) }, } func getUIDFromString(idStr string) *id.ID { idBytes, err := hex.DecodeString(fmt.Sprintf("%0*d%s", 66-len(idStr), 0, idStr)) if err != nil { jww.FATAL.Panicf("%+v", err) } ID, err := id.Unmarshal(idBytes) if err != nil { jww.FATAL.Panicf("%+v", err) } return ID } func initLog(verbose bool, logPath string) { if logPath != "-" && logPath != "" { // Disable stdout output jww.SetStdoutOutput(ioutil.Discard) // Use log file logOutput, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { panic(err.Error()) } jww.SetLogOutput(logOutput) } if verbose { jww.SetStdoutThreshold(jww.LevelTrace) jww.SetLogThreshold(jww.LevelTrace) } else { jww.SetStdoutThreshold(jww.LevelInfo) jww.SetLogThreshold(jww.LevelInfo) } } func isValidUser(usr []byte) (bool, *id.ID) { if len(usr) != id.ArrIDLen { return false, nil } for _, b := range usr { if b != 0 { uid, err := id.Unmarshal(usr) if err != nil { jww.WARN.Printf("Could not unmarshal user: %s", err) return false, nil } return true, uid } } return false, nil } // init is the initialization function for Cobra which defines commands // and flags. func init() { // NOTE: The point of init() is to be declarative. // There is one init in each sub command. Do not put variable declarations // here, and ensure all the Flags are of the *P variety, unless there's a // very good reason not to have them as local params to sub command." cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. rootCmd.Flags().BoolP("verbose", "v", false, "Verbose mode for debugging") viper.BindPFlag("verbose", rootCmd.Flags().Lookup("verbose")) rootCmd.Flags().StringP("session", "s", "", "Sets the initial username and the directory for "+ "client storage") viper.BindPFlag("session", rootCmd.Flags().Lookup("session")) rootCmd.Flags().StringP("password", "p", "", "Password to the session file") viper.BindPFlag("password", rootCmd.Flags().Lookup("password")) rootCmd.Flags().StringP("ndf", "n", "ndf.json", "Path to the network definition JSON file") viper.BindPFlag("ndf", rootCmd.Flags().Lookup("ndf")) rootCmd.Flags().StringP("log", "l", "-", "Path to the log output path (- is stdout)") viper.BindPFlag("log", rootCmd.Flags().Lookup("log")) rootCmd.Flags().StringP("regcode", "", "", "Registration code (optional)") viper.BindPFlag("regcode", rootCmd.Flags().Lookup("regcode")) rootCmd.Flags().StringP("message", "m", "", "Message to send") viper.BindPFlag("message", rootCmd.Flags().Lookup("message")) rootCmd.Flags().StringP("destid", "d", "0", "ID to send message to (hexadecimal string up to 256 bits)") viper.BindPFlag("destid", rootCmd.Flags().Lookup("destid")) rootCmd.Flags().UintP("sendCount", "", 1, "The number of times to send the message") viper.BindPFlag("sendCount", rootCmd.Flags().Lookup("sendCount")) rootCmd.Flags().UintP("sendDelay", "", 500, "The delay between sending the messages in ms") viper.BindPFlag("sendDelay", rootCmd.Flags().Lookup("sendDelay")) rootCmd.Flags().UintP("receiveCount", "", 1, "How many messages we should wait for before quitting") viper.BindPFlag("receiveCount", rootCmd.Flags().Lookup("receiveCount")) rootCmd.Flags().UintP("waitTimeout", "", 15, "The number of seconds to wait for messages to arrive") viper.BindPFlag("waitTimeout", rootCmd.Flags().Lookup("waitTimeout")) // Cobra also supports local flags, which will only run // when this action is called directly. // rootCmd.Flags().StringVarP(¬ificationToken, "nbRegistration", "x", "", // "Token to register user with notification bot") // rootCmd.PersistentFlags().BoolVarP(&end2end, "end2end", "", false, // "Send messages with E2E encryption to destination user. Must have found each other via UDB first") // rootCmd.PersistentFlags().StringSliceVarP(&keyParams, "keyParams", "", // make([]string, 0), "Define key generation parameters. Pass values in comma separated list"+ // " in the following order: MinKeys,MaxKeys,NumRekeys,TTLScalar,MinNumKeys") // rootCmd.Flags().StringVarP(&searchForUser, "SearchForUser", "s", "", // "Sets the email to search for to find a user with user discovery") } // initConfig reads in config file and ENV variables if set. func initConfig() {} // returns a simple numerical id if the user is a precanned user, otherwise // returns the normal string of the userID func printIDNice(uid *id.ID) string { for index, puid := range precannedIDList { if uid.Cmp(puid) { return strconv.Itoa(index + 1) } } return uid.String() } // build a list of precanned ids to use for comparision for nicer user id output var precannedIDList = buildPrecannedIDList() func buildPrecannedIDList() []*id.ID { idList := make([]*id.ID, 40) for i := 0; i < 40; i++ { uid := new(id.ID) binary.BigEndian.PutUint64(uid[:], uint64(i+1)) uid.SetType(id.User) idList[i] = uid } return idList }