diff --git a/broadcast/asymmetric.go b/broadcast/asymmetric.go index 33050a3a507834c7d810843ea4311da0f819844c..79fc4b4d0988319d6f9e0380fa6bbb4cd6d038ff 100644 --- a/broadcast/asymmetric.go +++ b/broadcast/asymmetric.go @@ -23,7 +23,7 @@ const ( ) // MaxAsymmetricPayloadSize returns the maximum size for an asymmetric broadcast payload -func (bc *broadcastClient) MaxAsymmetricPayloadSize() int { +func (bc *broadcastClient) maxAsymmetricPayload() int { return bc.maxParts() * bc.channel.MaxAsymmetricPayloadSize() } @@ -42,9 +42,9 @@ func (bc *broadcastClient) BroadcastAsymmetric(pk multicastRSA.PrivateKey, paylo return 0, ephemeral.Id{}, errors.New(errNetworkHealth) } - if len(payload) != bc.MaxAsymmetricPayloadSize() { + if len(payload) != bc.maxAsymmetricPayload() { return 0, ephemeral.Id{}, - errors.Errorf(errPayloadSize, len(payload), bc.MaxAsymmetricPayloadSize()) + errors.Errorf(errPayloadSize, len(payload), bc.maxAsymmetricPayload()) } numParts := bc.maxParts() diff --git a/broadcast/asymmetric_test.go b/broadcast/asymmetric_test.go index 681911a3fc9d68e9a9030afa93d6cb0e9a4d0038..2df1c2eb3d5c46de22c0c530b01f08ab97e6cd94 100644 --- a/broadcast/asymmetric_test.go +++ b/broadcast/asymmetric_test.go @@ -73,7 +73,7 @@ func Test_asymmetricClient_Smoke(t *testing.T) { // Send broadcast from each client for i := range clients { - payload := make([]byte, clients[i].MaxAsymmetricPayloadSize()) + payload := make([]byte, clients[i].MaxPayloadSize()) copy(payload, fmt.Sprintf("Hello from client %d of %d.", i, len(clients))) @@ -112,7 +112,7 @@ func Test_asymmetricClient_Smoke(t *testing.T) { clients[i].Stop() } - payload := make([]byte, clients[0].MaxAsymmetricPayloadSize()) + payload := make([]byte, clients[0].MaxPayloadSize()) copy(payload, "This message should not get through.") // Start waiting on channels and error if anything is received diff --git a/broadcast/broadcastClient.go b/broadcast/broadcastClient.go index 1c15e00e991dd4af8f03ac0c5d85ac6fb1610587..54555a3875db03af4331438e0dcb92922ad3aa06 100644 --- a/broadcast/broadcastClient.go +++ b/broadcast/broadcastClient.go @@ -98,3 +98,14 @@ func (bc *broadcastClient) verifyID() bool { } return bc.channel.ReceptionID.Cmp(gen) } + +func (bc *broadcastClient) MaxPayloadSize() int { + switch bc.param.Method { + case Symmetric: + return bc.maxSymmetricPayload() + case Asymmetric: + return bc.maxAsymmetricPayload() + default: + return -1 + } +} diff --git a/broadcast/interface.go b/broadcast/interface.go index aca75fd4668df29debee77984bd6d31450e3aa54..2c7d9e33820ce78a1532b693ace989fa7ea386f2 100644 --- a/broadcast/interface.go +++ b/broadcast/interface.go @@ -26,11 +26,8 @@ type ListenerFunc func(payload []byte, receptionID receptionID.EphemeralIdentity, round rounds.Round) type Channel interface { - // MaxSymmetricPayloadSize returns the maximum size for a symmetric broadcast payload. - MaxSymmetricPayloadSize() int - - // MaxAsymmetricPayloadSize returns the maximum size for an asymmetric broadcast payload. - MaxAsymmetricPayloadSize() int + // MaxPayloadSize returns the maximum size for a broadcast payload. Different math depending on broadcast method. + MaxPayloadSize() int // Get returns the underlying crypto.Channel Get() crypto.Channel @@ -41,7 +38,7 @@ type Channel interface { id.Round, ephemeral.Id, error) // BroadcastAsymmetric broadcasts an asymmetric payload to the channel. The payload size must be - // equal to MaxPayloadSize. + // equal to MaxPayloadSize & private key for channel must be passed in BroadcastAsymmetric(pk multicastRSA.PrivateKey, payload []byte, cMixParams cmix.CMIXParams) ( id.Round, ephemeral.Id, error) diff --git a/broadcast/symmetric.go b/broadcast/symmetric.go index 6d1dd8d18b7787cf7aca798632b907237fbf06f1..ca0c528c588b184a72dc788a2fa83c0423ca5268 100644 --- a/broadcast/symmetric.go +++ b/broadcast/symmetric.go @@ -30,7 +30,7 @@ const ( ) // MaxSymmetricPayloadSize returns the maximum size for a broadcasted payload. -func (bc *broadcastClient) MaxSymmetricPayloadSize() int { +func (bc *broadcastClient) maxSymmetricPayload() int { return bc.net.GetMaxMessageLength() } @@ -48,9 +48,9 @@ func (bc *broadcastClient) Broadcast(payload []byte, cMixParams cmix.CMIXParams) return 0, ephemeral.Id{}, errors.New(errNetworkHealth) } - if len(payload) != bc.MaxSymmetricPayloadSize() { + if len(payload) != bc.maxSymmetricPayload() { return 0, ephemeral.Id{}, - errors.Errorf(errPayloadSize, len(payload), bc.MaxSymmetricPayloadSize()) + errors.Errorf(errPayloadSize, len(payload), bc.maxSymmetricPayload()) } // Encrypt payload diff --git a/cmd/broadcast.go b/cmd/broadcast.go new file mode 100644 index 0000000000000000000000000000000000000000..a629b25c12fabb163370ce77393250293afac92c --- /dev/null +++ b/cmd/broadcast.go @@ -0,0 +1,284 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + jww "github.com/spf13/jwalterweatherman" + "github.com/spf13/viper" + "gitlab.com/elixxir/client/broadcast" + "gitlab.com/elixxir/client/cmix" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + crypto "gitlab.com/elixxir/crypto/broadcast" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/utils" + "os" + "time" +) + +// singleCmd is the single-use subcommand that allows for sending and responding +// to single-use messages. +var broadcastCmd = &cobra.Command{ + Use: "broadcast", + Short: "Send broadcast messages", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + client := initClient() + + // Write user contact to file + user := client.GetUser() + jww.INFO.Printf("User: %s", user.ReceptionID) + jww.INFO.Printf("User Transmission: %s", user.TransmissionID) + writeContact(user.GetContact()) + + err := client.StartNetworkFollower(5 * time.Second) + if err != nil { + jww.FATAL.Panicf("%+v", err) + } + + // Wait until connected or crash on timeout + connected := make(chan bool, 10) + client.GetNetworkInterface().AddHealthCallback( + func(isconnected bool) { + connected <- isconnected + }) + waitUntilConnected(connected) + + /* Set up underlying crypto broadcast.Channel */ + var channel *crypto.Channel + var pk *rsa.PrivateKey + keyPath := viper.GetString("keyPath") + path, err := utils.ExpandPath(viper.GetString("chanPath")) + if utils.Exists(path) { + // Load symmetric from path + cBytes, err := utils.ReadFile(path) + if err != nil { + jww.FATAL.Panicf("Failed to read channel from file at %s: %+v", path, err) + } + channel, err = crypto.UnmarshalChannel(cBytes) + if err != nil { + jww.FATAL.Panicf("Failed to unmarshal channel data %+v: %+v", cBytes, err) + } + } else { + // Load in broadcast channel info + name := viper.GetString("name") + desc := viper.GetString("description") + if name == "" { + jww.FATAL.Panicf("Name cannot be empty") + } else if desc == "" { + jww.FATAL.Panicf("description cannot be empty") + } + + var channel *crypto.Channel + if viper.GetBool("new") { + // Create a new broadcast channel + channel, pk, err = crypto.NewChannel(name, desc, client.GetRng().GetStream()) + if err != nil { + // TODO + } + + if keyPath != "" { + err = utils.WriteFile(path, rsa.CreatePrivateKeyPem(pk), os.ModePerm, os.ModeDir) + if err != nil { + // TODO + } + } else { + fmt.Printf("Private key generated for channel: %+v", rsa.CreatePrivateKeyPem(pk)) + } + } else { + // Read rest of info from config & build object manually + pubKeyBytes := []byte(viper.GetString("rsaPub")) + pubKey, err := rsa.LoadPublicKeyFromPem(pubKeyBytes) + if err != nil { + // TODO + } + salt := []byte(viper.GetString("salt")) + + rid, err := crypto.NewChannelID(name, desc, salt, pubKeyBytes) + if err != nil { + // TODO + } + + channel = &crypto.Channel{ + ReceptionID: rid, + Name: name, + Description: desc, + Salt: salt, + RsaPubKey: pubKey, + } + + // Load key if it's there + if keyPath != "" { + if ep, err := utils.ExpandPath(keyPath); err == nil { + keyBytes, err := utils.ReadFile(ep) + if err != nil { + // TODO + } + pk, err = rsa.LoadPrivateKeyFromPem(keyBytes) + if err != nil { + // TODO + } + } else { + // TODO + } + + } + } + + // Save channel to disk + cBytes, err := channel.Marshal() + if err != nil { + // TODO + } + // Write to file if there + if path != "" { + err = utils.WriteFile(path, cBytes, os.ModePerm, os.ModeDir) + if err != nil { + // TODO + } + } else { + fmt.Printf("Channel marshalled: %+v", cBytes) + } + } + + /* Broadcast client setup */ + + // Create receiver callback + receiveChan := make(chan []byte, 100) + cb := func(payload []byte, + receptionID receptionID.EphemeralIdentity, round rounds.Round) { + jww.INFO.Printf("Received symmetric message from %s over round %d", receptionID, round) + receiveChan <- payload + } + + // Select broadcast method + var method broadcast.Method + symmetric := viper.GetBool("symmetric") + asymmetric := viper.GetBool("asymmetric") + if symmetric && asymmetric { + jww.FATAL.Panicf("Cannot simultaneously broadcast symmetric & asymmetric") + } + if symmetric { + method = broadcast.Symmetric + } else if asymmetric { + method = broadcast.Asymmetric + } + + // Connect to broadcast channel + bcl, err := broadcast.NewBroadcastChannel(*channel, cb, client.GetNetworkInterface(), client.GetRng(), broadcast.Param{Method: method}) + + /* Create properly sized broadcast message */ + message := viper.GetString("broadcast") + fmt.Println(message) + var broadcastMessage []byte + if message != "" { + broadcastMessage, err = broadcast.NewSizedBroadcast(bcl.MaxPayloadSize(), []byte(message)) + if err != nil { + jww.ERROR.Printf("Failed to create sized broadcast: %+v", err) + } + + } + + /* Broadcast message to the channel */ + switch method { + case broadcast.Symmetric: + rid, eid, err := bcl.Broadcast(broadcastMessage, cmix.GetDefaultCMIXParams()) + if err != nil { + jww.ERROR.Printf("Failed to send symmetric broadcast message: %+v", err) + } + jww.INFO.Printf("Sent symmetric broadcast message to %s over round %d", eid, rid) + case broadcast.Asymmetric: + if pk == nil { + jww.FATAL.Panicf("CANNOT SEND ASYMMETRIC BROADCAST WITHOUT PRIVATE KEY") + } + rid, eid, err := bcl.BroadcastAsymmetric(pk, broadcastMessage, cmix.GetDefaultCMIXParams()) + if err != nil { + jww.ERROR.Printf("Failed to send asymmetric broadcast message: %+v", err) + } + jww.INFO.Printf("Sent asymmetric broadcast message to %s over round %d", eid, rid) + default: + jww.WARN.Printf("Unknown broadcast type (this should not happen)") + } + + /* Receive broadcast messages over the channel */ + waitSecs := viper.GetUint("waitTimeout") + expectedCnt := viper.GetUint("receiveCount") + waitTimeout := time.Duration(waitSecs) * time.Second + receivedCount := uint(0) + done := false + for !done && expectedCnt != 0 { + timeout := time.NewTimer(waitTimeout) + select { + case receivedPayload := <-receiveChan: + receivedCount++ + receivedBroadcast, err := broadcast.DecodeSizedBroadcast(receivedPayload) + if err != nil { + jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err) + continue + } + fmt.Printf("Symmetric broadcast message %d/%d received: %s\n", receivedCount, expectedCnt, string(receivedBroadcast)) + if receivedCount == expectedCnt { + done = true + } + case <-timeout.C: + fmt.Println("Timed out") + jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) + done = true + } + } + + jww.INFO.Printf("Received %d/%d Messages!", receivedCount, expectedCnt) + bcl.Stop() + err = client.StopNetworkFollower() + if err != nil { + jww.WARN.Printf( + "Failed to cleanly close threads: %+v\n", + err) + } + }, +} + +func init() { + // Single-use subcommand options + broadcastCmd.Flags().StringP("name", "", "", + "Symmetric channel name") + _ = viper.BindPFlag("name", broadcastCmd.Flags().Lookup("name")) + + broadcastCmd.Flags().StringP("rsaPub", "", "", + "Symmetric channel rsa pub key") + _ = viper.BindPFlag("rsaPub", broadcastCmd.Flags().Lookup("rsaPub")) + + broadcastCmd.Flags().StringP("salt", "", "", + "Symmetric channel salt") + _ = viper.BindPFlag("salt", broadcastCmd.Flags().Lookup("salt")) + + broadcastCmd.Flags().StringP("description", "", "", + "Symmetric channel description") + _ = viper.BindPFlag("description", broadcastCmd.Flags().Lookup("description")) + + broadcastCmd.Flags().StringP("chanPath", "", "", + "Symmetric channel output path") + _ = viper.BindPFlag("chanPath", broadcastCmd.Flags().Lookup("chanPath")) + + broadcastCmd.Flags().StringP("keyPath", "", "", + "Symmetric channel private key output path") + _ = viper.BindPFlag("keyPath", broadcastCmd.Flags().Lookup("keyPath")) + + broadcastCmd.Flags().BoolP("new", "", false, + "Create new symmetric channel") + _ = viper.BindPFlag("new", broadcastCmd.Flags().Lookup("new")) + + broadcastCmd.Flags().StringP("broadcast", "", "", + "Message to send via symmetric broadcast") + _ = viper.BindPFlag("broadcast", broadcastCmd.Flags().Lookup("broadcast")) + + broadcastCmd.Flags().BoolP("symmetric", "", false, + "Set broadcast method to symmetric") + _ = viper.BindPFlag("symmetric", broadcastCmd.Flags().Lookup("symmetric")) + + broadcastCmd.Flags().BoolP("asymmetric", "", false, + "Set broadcast method to asymmetric") + _ = viper.BindPFlag("asymmetric", broadcastCmd.Flags().Lookup("asymmetric")) + + rootCmd.AddCommand(broadcastCmd) +} diff --git a/cmd/symmetric.go b/cmd/symmetric.go deleted file mode 100644 index a2b9305939def37ab8dc9471e5cebf6432db80bf..0000000000000000000000000000000000000000 --- a/cmd/symmetric.go +++ /dev/null @@ -1,210 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" - "gitlab.com/elixxir/client/broadcast" - "gitlab.com/elixxir/client/cmix" - "gitlab.com/elixxir/client/cmix/identity/receptionID" - "gitlab.com/elixxir/client/cmix/rounds" - crypto "gitlab.com/elixxir/crypto/broadcast" - "gitlab.com/xx_network/crypto/signature/rsa" - "gitlab.com/xx_network/primitives/utils" - "os" - "time" -) - -// singleCmd is the single-use subcommand that allows for sending and responding -// to single-use messages. -var symmetricCmd = &cobra.Command{ - Use: "symmetric", - Short: "Send symmetric broadcast messages", - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - client := initClient() - - // Write user contact to file - user := client.GetUser() - jww.INFO.Printf("User: %s", user.ReceptionID) - jww.INFO.Printf("User Transmission: %s", user.TransmissionID) - writeContact(user.GetContact()) - - err := client.StartNetworkFollower(5 * time.Second) - if err != nil { - jww.FATAL.Panicf("%+v", err) - } - - // Wait until connected or crash on timeout - connected := make(chan bool, 10) - client.GetNetworkInterface().AddHealthCallback( - func(isconnected bool) { - connected <- isconnected - }) - waitUntilConnected(connected) - - // Create new symmetric or load from path if exists - path, err := utils.ExpandPath(viper.GetString("path")) - var symmetric *crypto.Symmetric - if utils.Exists(path) { - // Load symmetric from path - symmBytes, err := utils.ReadFile(path) - if err != nil { - - } - symmetric, err = crypto.UnmarshalSymmetric(symmBytes) - if err != nil { - - } - } else { - // New symmetric - name := viper.GetString("name") - desc := viper.GetString("description") - if name == "" { - jww.FATAL.Panicf("Name cannot be empty") - } else if desc == "" { - jww.FATAL.Panicf("description cannot be empty") - } - - var pubKey *rsa.PublicKey - var salt, pubKeyBytes []byte - if viper.GetBool("new") { - privKey, err := rsa.GenerateKey(client.GetRng().GetStream(), rsa.DefaultRSABitLen) - if err != nil { - - } - pubKey = privKey.GetPublic() - pubKeyBytes = rsa.CreatePublicKeyPem(pubKey) - } else { - pubKeyBytes := []byte(viper.GetString("rsaPub")) - pubKey, err = rsa.LoadPublicKeyFromPem(pubKeyBytes) - if err != nil { - - } - salt = []byte(viper.GetString("salt")) - } - - rid, err := crypto.NewSymmetricID(name, desc, salt, pubKeyBytes) - if err != nil { - - } - - symmetric = &crypto.Symmetric{ - ReceptionID: rid, - Name: name, - Description: desc, - Salt: salt, - RsaPubKey: pubKey, - } - - symmBytes, err := symmetric.Marshal() - if err != nil { - - } - // Write to file if there - if path != "" { - err = utils.WriteFile(path, symmBytes, os.ModePerm, os.ModeDir) - if err != nil { - - } - } else { - fmt.Printf("Symmetric marshalled: %+v", symmBytes) - } - } - - // Create receiver callback - receiveChan := make(chan []byte, 100) - cb := func(payload []byte, - receptionID receptionID.EphemeralIdentity, round rounds.Round) { - jww.INFO.Printf("Received symmetric message from %s over round %d", receptionID, round) - receiveChan <- payload - } - - // Connect to symmetric broadcast channel - scl := broadcast.NewSymmetricClient(*symmetric, cb, client.GetNetworkInterface(), client.GetRng()) - message := viper.GetString("broadcast") - fmt.Println(message) - // Send a broadcast over the channel - if message != "" { - broadcastMessage, err := broadcast.NewSizedBroadcast(scl.MaxPayloadSize(), []byte(message)) - if err != nil { - jww.ERROR.Printf("Failed to create sized broadcast: %+v", err) - } - rid, eid, err := scl.Broadcast(broadcastMessage, cmix.GetDefaultCMIXParams()) - if err != nil { - jww.ERROR.Printf("Failed to send symmetric broadcast message: %+v", err) - } - jww.INFO.Printf("Sent symmetric broadcast message to %s over round %d", eid, rid) - } - - // Receive messages over the channel - waitSecs := viper.GetUint("waitTimeout") - expectedCnt := viper.GetUint("receiveCount") - waitTimeout := time.Duration(waitSecs) * time.Second - receivedCount := uint(0) - done := false - for !done && expectedCnt != 0 { - timeout := time.NewTimer(waitTimeout) - select { - case receivedPayload := <-receiveChan: - receivedCount++ - receivedBroadcast, err := broadcast.DecodeSizedBroadcast(receivedPayload) - if err != nil { - jww.ERROR.Printf("Failed to decode sized broadcast: %+v", err) - continue - } - fmt.Printf("Symmetric broadcast message %d/%d received: %s\n", receivedCount, expectedCnt, string(receivedBroadcast)) - if receivedCount == expectedCnt { - done = true - } - case <-timeout.C: - fmt.Println("Timed out") - jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) - done = true - } - } - - jww.INFO.Printf("Received %d/%d Messages!", receivedCount, expectedCnt) - scl.Stop() - err = client.StopNetworkFollower() - if err != nil { - jww.WARN.Printf( - "Failed to cleanly close threads: %+v\n", - err) - } - }, -} - -func init() { - // Single-use subcommand options - symmetricCmd.Flags().StringP("name", "", "", - "Symmetric channel name") - _ = viper.BindPFlag("name", symmetricCmd.Flags().Lookup("name")) - - symmetricCmd.Flags().StringP("rsaPub", "", "", - "Symmetric channel rsa pub key") - _ = viper.BindPFlag("rsaPub", symmetricCmd.Flags().Lookup("rsaPub")) - - symmetricCmd.Flags().StringP("salt", "", "", - "Symmetric channel salt") - _ = viper.BindPFlag("salt", symmetricCmd.Flags().Lookup("salt")) - - symmetricCmd.Flags().StringP("description", "", "", - "Symmetric channel description") - _ = viper.BindPFlag("description", symmetricCmd.Flags().Lookup("description")) - - symmetricCmd.Flags().StringP("path", "", "", - "Symmetric channel output path") - _ = viper.BindPFlag("path", symmetricCmd.Flags().Lookup("path")) - - symmetricCmd.Flags().BoolP("new", "", false, - "Create new symmetric channel") - _ = viper.BindPFlag("new", symmetricCmd.Flags().Lookup("new")) - - symmetricCmd.Flags().StringP("broadcast", "", "", - "Message to send via symmetric broadcast") - _ = viper.BindPFlag("broadcast", symmetricCmd.Flags().Lookup("broadcast")) - - rootCmd.AddCommand(symmetricCmd) -}