From 1f920df8e96a0fa3eeb62cb1051020add7559ba2 Mon Sep 17 00:00:00 2001 From: Jono Wenger <jono@elixxir.io> Date: Tue, 16 Aug 2022 14:23:10 -0700 Subject: [PATCH] Implement backup.go --- main.go | 7 +- wasm/authenticatedConnection.go | 3 +- wasm/authenticatedConnection_test.go | 2 + wasm/backup.go | 172 +++++++++++++++++++++++++++ wasm/backup_test.go | 34 ++++++ wasm/cmix.go | 10 +- wasm/connect.go | 16 +-- wasm/e2e.go | 8 +- 8 files changed, 232 insertions(+), 20 deletions(-) create mode 100644 wasm/backup.go create mode 100644 wasm/backup_test.go diff --git a/main.go b/main.go index 2bbdef83..930e10f0 100644 --- a/main.go +++ b/main.go @@ -74,9 +74,14 @@ func main() { js.Global().Set("NewDummyTrafficManager", js.FuncOf(wasm.NewDummyTrafficManager)) - // bindings/broadcast + // bindings/broadcast.go js.Global().Set("NewBroadcastChannel", js.FuncOf(wasm.NewBroadcastChannel)) + // bindings/backup.go + js.Global().Set("NewCmixFromBackup", js.FuncOf(wasm.NewCmixFromBackup)) + js.Global().Set("InitializeBackup", js.FuncOf(wasm.InitializeBackup)) + js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup)) + <-make(chan bool) os.Exit(0) } diff --git a/wasm/authenticatedConnection.go b/wasm/authenticatedConnection.go index 9742895e..dceea6f0 100644 --- a/wasm/authenticatedConnection.go +++ b/wasm/authenticatedConnection.go @@ -39,10 +39,9 @@ func (ac *AuthenticatedConnection) IsAuthenticated(js.Value, []js.Value) interfa // ConnectWithAuthentication is called by the client (i.e., the one establishing // connection with the server). Once a connect.Connection has been established // with the server, it then authenticates their identity to the server. -// accepts a marshalled ReceptionIdentity and contact.Contact object // // Parameters: -// - args[0] - ID of E2e object in tracker (int) +// - args[0] - ID of E2e object in tracker (int). // - args[1] - marshalled recipient [contact.Contact] (Uint8Array). // - args[3] - JSON of [xxdk.E2EParams] (Uint8Array). // diff --git a/wasm/authenticatedConnection_test.go b/wasm/authenticatedConnection_test.go index e700d3f9..68555c15 100644 --- a/wasm/authenticatedConnection_test.go +++ b/wasm/authenticatedConnection_test.go @@ -5,6 +5,8 @@ // LICENSE file // //////////////////////////////////////////////////////////////////////////////// +//go:build js && wasm + package wasm import ( diff --git a/wasm/backup.go b/wasm/backup.go new file mode 100644 index 00000000..cfa426ef --- /dev/null +++ b/wasm/backup.go @@ -0,0 +1,172 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package wasm + +import ( + "gitlab.com/elixxir/client/bindings" + "syscall/js" +) + +//////////////////////////////////////////////////////////////////////////////// +// Structs and Interfaces // +//////////////////////////////////////////////////////////////////////////////// + +// Backup wraps the [bindings.Backup] object so its methods can be wrapped to be +// Javascript compatible. +type Backup struct { + api *bindings.Backup +} + +// newBackupJS creates a new Javascript compatible object +// (map[string]interface{}) that matches the Backup structure. +func newBackupJS(api *bindings.Backup) map[string]interface{} { + b := Backup{api} + backupMap := map[string]interface{}{ + "StopBackup": js.FuncOf(b.StopBackup), + "IsBackupRunning": js.FuncOf(b.IsBackupRunning), + "AddJson": js.FuncOf(b.AddJson), + } + + return backupMap +} + +// updateBackupFunc wraps Javascript callbacks to adhere to the +// [bindings.UpdateBackupFunc] interface. +type updateBackupFunc struct { + updateBackup func(args ...interface{}) js.Value +} + +func (ubf *updateBackupFunc) UpdateBackup(encryptedBackup []byte) { + ubf.updateBackup(CopyBytesToJS(encryptedBackup)) +} + +//////////////////////////////////////////////////////////////////////////////// +// Client functions // +//////////////////////////////////////////////////////////////////////////////// + +// NewCmixFromBackup initializes a new e2e storage from an encrypted +// backup. Users of this function should delete the storage directory on error. +// Users of this function should call LoadCmix as normal once this call succeeds. +// +// Parameters: +// - args[0] - JSON of the NDF (string). +// - args[1] - storage directory (string). +// - args[2] - backup passphrase (string). +// - args[3] - session password (Uint8Array). +// - args[4] - backup file contents (Uint8Array). +// +// Returns: +// - JSON of [bindings.BackupReport] (Uint8Array). +// - throws a TypeError if creating Cmix from backup fails. +func NewCmixFromBackup(_ js.Value, args []js.Value) interface{} { + ndfJSON := args[0].String() + storageDir := args[1].String() + backupPassphrase := args[2].String() + sessionPassword := CopyBytesToGo(args[3]) + backupFileContents := CopyBytesToGo(args[4]) + + report, err := bindings.NewCmixFromBackup(ndfJSON, storageDir, + backupPassphrase, sessionPassword, backupFileContents) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return CopyBytesToJS(report) +} + +//////////////////////////////////////////////////////////////////////////////// +// Backup functions // +//////////////////////////////////////////////////////////////////////////////// + +// InitializeBackup creates a bindings-layer Backup object. +// +// Parameters: +// - args[0] - ID of E2e object in tracker (int). +// - args[1] - ID of UserDiscovery object in tracker (int). +// - args[2] - backup passphrase provided by the user (string). Used to decrypt +// backup. +// - args[3] - the callback to be called when a backup is triggered. Must be +// Javascript object that has functions that implement the +// [bindings.UpdateBackupFunc] interface. +// +// Returns: +// - Javascript representation of the Backup object +// - Throws a TypeError if initializing the Backup fails. +func InitializeBackup(_ js.Value, args []js.Value) interface{} { + cb := &updateBackupFunc{args[3].Get("UpdateBackup").Invoke} + api, err := bindings.InitializeBackup( + args[0].Int(), args[1].Int(), args[2].String(), cb) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return newBackupJS(api) +} + +// ResumeBackup resumes the backup processes with a new callback. +// Call this function only when resuming a backup that has already been +// initialized or to replace the callback. +// To start the backup for the first time or to use a new password, use +// InitializeBackup. +// +// Parameters: +// - args[0] - ID of E2e object in tracker (int). +// - args[1] - ID of UserDiscovery object in tracker (int). +// - args[2] - the callback to be called when a backup is triggered. Must be +// Javascript object that has functions that implement the +// [bindings.UpdateBackupFunc] interface. This will replace any callback that +// has been passed into InitializeBackup. +// +// Returns: +// - Javascript representation of the Backup object +// - Throws a TypeError if initializing the Backup fails. +func ResumeBackup(_ js.Value, args []js.Value) interface{} { + cb := &updateBackupFunc{args[2].Get("UpdateBackup").Invoke} + api, err := bindings.ResumeBackup(args[0].Int(), args[1].Int(), cb) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return newBackupJS(api) +} + +// StopBackup stops the backup processes and deletes the user's password from +// storage. To enable backups again, call InitializeBackup. +// +// Returns: +// - Throws a TypeError if stopping the backup fails. +func (b *Backup) StopBackup(js.Value, []js.Value) interface{} { + err := b.api.StopBackup() + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return nil +} + +// IsBackupRunning returns true if the backup has been initialized and is +// running. Returns false if it has been stopped. +// +// Returns: +// - boolean +func (b *Backup) IsBackupRunning(js.Value, []js.Value) interface{} { + return b.api.IsBackupRunning() +} + +// AddJson stores the argument within the Backup structure. +// +// Parameters: +// - args[0] - JSON to store (string). +func (b *Backup) AddJson(_ js.Value, args []js.Value) interface{} { + b.api.AddJson(args[0].String()) + return nil +} diff --git a/wasm/backup_test.go b/wasm/backup_test.go new file mode 100644 index 00000000..0ec31c9b --- /dev/null +++ b/wasm/backup_test.go @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package wasm + +import ( + "gitlab.com/elixxir/client/bindings" + "reflect" + "testing" +) + +// Tests that the map representing Backup returned by newBackupJS contains all +// of the methods on Backup. +func Test_newBackupJS(t *testing.T) { + buType := reflect.TypeOf(&Backup{}) + + b := newBackupJS(&bindings.Backup{}) + if len(b) != buType.NumMethod() { + t.Errorf("Backup JS object does not have all methods."+ + "\nexpected: %d\nreceived: %d", buType.NumMethod(), len(b)) + } + + for i := 0; i < buType.NumMethod(); i++ { + method := buType.Method(i) + + if _, exists := b[method.Name]; !exists { + t.Errorf("Method %s does not exist.", method.Name) + } + } +} diff --git a/wasm/cmix.go b/wasm/cmix.go index 3c26acd0..489d9f4c 100644 --- a/wasm/cmix.go +++ b/wasm/cmix.go @@ -66,13 +66,13 @@ func newCmixJS(api *bindings.Cmix) map[string]interface{} { // Users of this function should delete the storage directory on error. // // Parameters: -// - args[0] - NDF JSON (string) -// - args[1] - storage directory path (string) -// - args[2] - password used for storage (Uint8Array) -// - args[3] - registration code (string) +// - args[0] - NDF JSON (string). +// - args[1] - storage directory path (string). +// - args[2] - password used for storage (Uint8Array). +// - args[3] - registration code (string). // // Returns: -// - throws a TypeError if creating new Cmix fails +// - throws a TypeError if creating new Cmix fails. func NewCmix(_ js.Value, args []js.Value) interface{} { password := CopyBytesToGo(args[2]) diff --git a/wasm/connect.go b/wasm/connect.go index 1149fbe4..ea87b216 100644 --- a/wasm/connect.go +++ b/wasm/connect.go @@ -17,14 +17,14 @@ import ( // Connection wraps the [bindings.Connection] object so its methods can be // wrapped to be Javascript compatible. type Connection struct { - c *bindings.Connection + api *bindings.Connection } // newConnectJS creates a new Javascript compatible object // (map[string]interface{}) that matches the Connection structure. func newConnectJS(api *bindings.Connection) map[string]interface{} { c := Connection{api} - connection := map[string]interface{}{ + connectionMap := map[string]interface{}{ // connect.go "GetID": js.FuncOf(c.GetID), "SendE2E": js.FuncOf(c.SendE2E), @@ -33,7 +33,7 @@ func newConnectJS(api *bindings.Connection) map[string]interface{} { "RegisterListener": js.FuncOf(c.RegisterListener), } - return connection + return connectionMap } // GetID returns the ID for this [bindings.Connection] in the connectionTracker. @@ -41,7 +41,7 @@ func newConnectJS(api *bindings.Connection) map[string]interface{} { // Returns: // - int of the ID func (c *Connection) GetID(js.Value, []js.Value) interface{} { - return c.c.GetId() + return c.api.GetId() } // Connect performs auth key negotiation with the given recipient and returns a @@ -86,7 +86,7 @@ func (c *Cmix) Connect(_ js.Value, args []js.Value) interface{} { // cmix.WaitForRoundResult to see if the send succeeded (Uint8Array) // - throws a TypeError if sending fails func (c *Connection) SendE2E(_ js.Value, args []js.Value) interface{} { - sendReport, err := c.c.SendE2E(args[0].Int(), CopyBytesToGo(args[1])) + sendReport, err := c.api.SendE2E(args[0].Int(), CopyBytesToGo(args[1])) if err != nil { Throw(TypeError, err.Error()) return nil @@ -99,7 +99,7 @@ func (c *Connection) SendE2E(_ js.Value, args []js.Value) interface{} { // Returns: // - throws a TypeError if closing fails func (c *Connection) Close(js.Value, []js.Value) interface{} { - err := c.c.Close() + err := c.api.Close() if err != nil { Throw(TypeError, err.Error()) return nil @@ -113,7 +113,7 @@ func (c *Connection) Close(js.Value, []js.Value) interface{} { // Returns: // - bytes of the partner's [id.ID] (Uint8Array) func (c *Connection) GetPartner(js.Value, []js.Value) interface{} { - return CopyBytesToJS(c.c.GetPartner()) + return CopyBytesToJS(c.api.GetPartner()) } // listener adheres to the [bindings.Listener] interface. @@ -136,7 +136,7 @@ func (l *listener) Name() string { return l.name().String() } // Returns: // - throws a TypeError is registering the listener fails func (c *Connection) RegisterListener(_ js.Value, args []js.Value) interface{} { - err := c.c.RegisterListener(args[0].Int(), + err := c.api.RegisterListener(args[0].Int(), &listener{args[1].Get("Hear").Invoke, args[1].Get("Name").Invoke}) if err != nil { Throw(TypeError, err.Error()) diff --git a/wasm/e2e.go b/wasm/e2e.go index 4abdf18b..4a754fe7 100644 --- a/wasm/e2e.go +++ b/wasm/e2e.go @@ -80,11 +80,11 @@ func (e *E2e) GetID(js.Value, []js.Value) interface{} { // default [auth.Callbacks] will be used. // // Parameters: -// - args[0] - ID of Cmix object in tracker (int) +// - args[0] - ID of Cmix object in tracker (int). // - args[1] - Javascript object that has functions that implement the -// [bindings.AuthCallbacks] interface -// - args[2] - JSON of the [xxdk.ReceptionIdentity] object (Uint8Array) -// - args[3] - JSON of [xxdk.E2EParams] (Uint8Array) +// [bindings.AuthCallbacks] interface. +// - args[2] - JSON of the [xxdk.ReceptionIdentity] object (Uint8Array). +// - args[3] - JSON of [xxdk.E2EParams] (Uint8Array). // // Returns: // - Javascript representation of the E2e object -- GitLab