diff --git a/bindings/fileTransfer.go b/bindings/fileTransfer.go new file mode 100644 index 0000000000000000000000000000000000000000..9b8b1da61c12064bf2e5cbe4ff26bb7a0a99d792 --- /dev/null +++ b/bindings/fileTransfer.go @@ -0,0 +1,338 @@ +package bindings + +import ( + "encoding/json" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/catalog" + "gitlab.com/elixxir/client/fileTransfer" + ftCrypto "gitlab.com/elixxir/crypto/fileTransfer" + "gitlab.com/xx_network/primitives/id" + "time" +) + +/* File Transfer Structs and Interfaces */ + +// FileTransfer object is a bindings-layer struct which wraps a fileTransfer.FileTransfer interface +type FileTransfer struct { + ft fileTransfer.FileTransfer + e2eCl *E2e +} + +// ReceivedFile is a public struct which represents the contents of an incoming file +// Example JSON: +// { +// "TransferID":"B4Z9cwU18beRoGbk5xBjbcd5Ryi9ZUFA2UBvi8FOHWo=", // ID of the incoming transfer for receiving +// "SenderID":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", // ID of sender of incoming file +// "Preview":"aXQncyBtZSBhIHByZXZpZXc=", // Preview of the incoming file +// "Name":"testfile.txt", // Name of incoming file +// "Type":"text file", // Incoming file type +// "Size":2048 // Incoming file size +// } +type ReceivedFile struct { + TransferID []byte + SenderID []byte + Preview []byte + Name string + Type string + Size int +} + +// FileSend is a public struct which represents a file to be transferred +// { +// "Name":"testfile.txt", // File name +// "Type":"text file", // File type +// "Preview":"aXQncyBtZSBhIHByZXZpZXc=", // Preview of contents +// "Contents":"VGhpcyBpcyB0aGUgZnVsbCBjb250ZW50cyBvZiB0aGUgZmlsZSBpbiBieXRlcw==" // Full contents of the file +// } +type FileSend struct { + Name string + Type string + Preview []byte + Contents []byte +} + +// Progress is a public struct which represents the progress of an in-progress file transfer +// Example JSON: +// {"Completed":false, // Status of transfer (true if done) +// "Transmitted":128, // Bytes transferred so far +// "Total":2048, // Total size of file +// "Err":null // Error status (if any) +// } +type Progress struct { + Completed bool + Transmitted int + Total int + Err error +} + +// ReceiveFileCallback is a bindings-layer interface which is called when a file is received +// Accepts the result of calling json.Marshal on a ReceivedFile struct +type ReceiveFileCallback interface { + Callback(payload []byte, err error) +} + +// FileTransferSentProgressCallback is a bindings-layer interface which is called with the progress of a sending file +// Accepts the result of calling json.Marshal on a Progress struct & a FilePartTracker interface +type FileTransferSentProgressCallback interface { + Callback(payload []byte, t *FilePartTracker, err error) +} + +// FileTransferReceiveProgressCallback is a bindings-layer interface which is called with the progress of a received file +// Accepts the result of calling json.Marshal on a Progress struct & a FilePartTracker interface +type FileTransferReceiveProgressCallback interface { + Callback(payload []byte, t *FilePartTracker, err error) +} + +/* Main functions */ + +// InitFileTransfer creates a bindings-level File Transfer manager +// Accepts e2e client ID and marshalled params JSON +func InitFileTransfer(e2eID int, paramsJSON []byte) (*FileTransfer, error) { + + // Get bindings client from singleton + e2eCl, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + // Client info + myID := e2eCl.api.GetReceptionIdentity().ID + rng := e2eCl.api.GetRng() + + params, err := parseFileTransferParams(paramsJSON) + if err != nil { + return nil, err + } + + // Create file transfer manager + m, err := fileTransfer.NewManager(params, myID, + e2eCl.api.GetCmix(), e2eCl.api.GetStorage(), rng) + + // Add file transfer processes to client services tracking + err = e2eCl.api.AddService(m.StartProcesses) + if err != nil { + return nil, err + } + + // Return wrapped manager + return &FileTransfer{ft: m, e2eCl: e2eCl}, nil +} + +// Send is the bindings-level function for sending a File +// Accepts: +// FileSend JSON payload +// Marshalled recipient ID +// Marshalled e2e Params JSON +// Number of retries allowed +// Limit on duration between retries +// FileTransferSentProgressCallback interface +func (f *FileTransfer) Send(payload, recipientID, paramsJSON []byte, retry float32, + period string, callback FileTransferSentProgressCallback) ([]byte, error) { + // Unmarshal recipient ID + recipient, err := id.Unmarshal(recipientID) + if err != nil { + return nil, err + } + + // Parse duration to time.Duration + p, err := time.ParseDuration(period) + + // Wrap transfer progress callback to be passed to fileTransfer layer + cb := func(completed bool, arrived, total uint16, + st fileTransfer.SentTransfer, t fileTransfer.FilePartTracker, err error) { + prog := &Progress{ + Completed: completed, + Transmitted: int(arrived), + Total: int(total), + Err: err, + } + pm, err := json.Marshal(prog) + callback.Callback(pm, &FilePartTracker{t}, err) + } + + // Unmarshal payload + fs := &FileSend{} + err = json.Unmarshal(payload, fs) + if err != nil { + return nil, err + } + + sendNew := func(transferInfo []byte) error { + resp, err := f.e2eCl.SendE2E(int(catalog.NewFileTransfer), recipientID, transferInfo, paramsJSON) + if err != nil { + return err + } + jww.INFO.Printf("New file transfer message sent: %s", resp) + return nil + } + + // Send file + ftID, err := f.ft.Send(recipient, fs.Name, fs.Type, fs.Contents, retry, fs.Preview, cb, p, sendNew) + if err != nil { + return nil, err + } + + // Return Transfer ID + return ftID.Bytes(), nil +} + +// Receive returns the full file on the completion of the transfer. +// It deletes internal references to the data and unregisters any attached +// progress callback. Returns an error if the transfer is not complete, the +// full file cannot be verified, or if the transfer cannot be found. +// +// Receive can only be called once the progress callback returns that the +// file transfer is complete. +func (f *FileTransfer) Receive(tidBytes []byte) ([]byte, error) { + tid := ftCrypto.UnmarshalTransferID(tidBytes) + return f.ft.Receive(&tid) +} + +// CloseSend deletes a file from the internal storage once a transfer has +// completed or reached the retry limit. Returns an error if the transfer +// has not run out of retries. +// +// This function should be called once a transfer completes or errors out +// (as reported by the progress callback). +func (f *FileTransfer) CloseSend(tidBytes []byte) error { + tid := ftCrypto.UnmarshalTransferID(tidBytes) + return f.ft.CloseSend(&tid) +} + +/* Callback registration functions */ + +// RegisterSentProgressCallback allows for the registration of a callback to +// track the progress of an individual sent file transfer. +// SentProgressCallback is auto registered on Send; this function should be +// called when resuming clients or registering extra callbacks. +// Accepts ID of the transfer, callback for transfer progress, +// and period between retries +func (f *FileTransfer) RegisterSentProgressCallback(tidBytes []byte, + callback FileTransferSentProgressCallback, period string) error { + cb := func(completed bool, arrived, total uint16, + st fileTransfer.SentTransfer, t fileTransfer.FilePartTracker, err error) { + prog := &Progress{ + Completed: completed, + Transmitted: int(arrived), + Total: int(total), + Err: err, + } + pm, err := json.Marshal(prog) + callback.Callback(pm, &FilePartTracker{t}, err) + } + p, err := time.ParseDuration(period) + if err != nil { + return err + } + tid := ftCrypto.UnmarshalTransferID(tidBytes) + + return f.ft.RegisterSentProgressCallback(&tid, cb, p) +} + +// RegisterReceivedProgressCallback allows for the registration of a +// callback to track the progress of an individual received file transfer. +// This should be done when a new transfer is received on the +// ReceiveCallback. +// Accepts ID of the transfer, callback for transfer progress and period between retries +func (f *FileTransfer) RegisterReceivedProgressCallback(tidBytes []byte, callback FileTransferReceiveProgressCallback, period string) error { + cb := func(completed bool, received, total uint16, + rt fileTransfer.ReceivedTransfer, t fileTransfer.FilePartTracker, err error) { + prog := &Progress{ + Completed: completed, + Transmitted: int(received), + Total: int(total), + Err: err, + } + pm, err := json.Marshal(prog) + callback.Callback(pm, &FilePartTracker{t}, err) + } + p, err := time.ParseDuration(period) + if err != nil { + return err + } + tid := ftCrypto.UnmarshalTransferID(tidBytes) + return f.ft.RegisterReceivedProgressCallback(&tid, cb, p) +} + +/* Utility Functions */ + +func (f *FileTransfer) MaxFileNameLen() int { + return f.ft.MaxFileNameLen() +} + +func (f *FileTransfer) MaxFileTypeLen() int { + return f.ft.MaxFileTypeLen() +} + +func (f *FileTransfer) MaxFileSize() int { + return f.ft.MaxFileSize() +} + +func (f *FileTransfer) MaxPreviewSize() int { + return f.ft.MaxPreviewSize() +} + +//////////////////////////////////////////////////////////////////////////////// +// File Part Tracker // +//////////////////////////////////////////////////////////////////////////////// + +// FilePartTracker contains the interfaces.FilePartTracker. +type FilePartTracker struct { + m fileTransfer.FilePartTracker +} + +// GetPartStatus returns the status of the file part with the given part number. +// The possible values for the status are: +// 0 = unsent +// 1 = sent (sender has sent a part, but it has not arrived) +// 2 = arrived (sender has sent a part, and it has arrived) +// 3 = received (receiver has received a part) +func (fpt FilePartTracker) GetPartStatus(partNum int) int { + return int(fpt.m.GetPartStatus(uint16(partNum))) +} + +// GetNumParts returns the total number of file parts in the transfer. +func (fpt FilePartTracker) GetNumParts() int { + return int(fpt.m.GetNumParts()) +} + +//////////////////////////////////////////////////////////////////////////////// +// Event Reporter // +//////////////////////////////////////////////////////////////////////////////// + +// EventReport is a public struct which represents the contents of an event report +// Example JSON: +// {"Priority":1, +// "Category":"Test Events", +// "EventType":"Ping", +// "Details":"This is an example of an event report" +// } +type EventReport struct { + Priority int + Category string + EventType string + Details string +} + +// ReporterFunc is a bindings-layer interface which receives info from the Event Manager +// Accepts result of json.Marshal on an EventReport object +type ReporterFunc interface { + Report(payload []byte, err error) +} + +// reporter is the internal struct to match the event.Reporter interface +type reporter struct { + r ReporterFunc +} + +// Report matches the event.Reporter interface, wraps the info in an EventReport struct +// and passes the marshalled struct to the internal callback +func (r *reporter) Report(priority int, category, evtType, details string) { + rep := &EventReport{ + Priority: priority, + Category: category, + EventType: evtType, + Details: details, + } + r.r.Report(json.Marshal(rep)) +} diff --git a/bindings/fileTransfer_test.go b/bindings/fileTransfer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a89967891505ea911384361924a746d9094260ff --- /dev/null +++ b/bindings/fileTransfer_test.go @@ -0,0 +1,59 @@ +package bindings + +import ( + "encoding/json" + "gitlab.com/elixxir/crypto/fileTransfer" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "testing" +) + +func TestFileTransfer_inputs(t *testing.T) { + fs := &FileSend{ + Name: "testfile.txt", + Type: "text file", + Preview: []byte("it's me a preview"), + Contents: []byte("This is the full contents of the file in bytes"), + } + fsm, _ := json.Marshal(fs) + t.Log("FileSend example json:") + t.Log(string(fsm)) + t.Log("\n") + + tid, _ := fileTransfer.NewTransferID(csprng.NewSystemRNG()) + sid := id.NewIdFromString("zezima", id.User, t) + rf := &ReceivedFile{ + TransferID: tid.Bytes(), + SenderID: sid.Marshal(), + Preview: []byte("it's me a preview"), + Name: "testfile.txt", + Type: "text file", + Size: 2048, + } + rfm, _ := json.Marshal(rf) + t.Log("ReceivedFile example json:") + t.Log(string(rfm)) + t.Log("\n") + + p := &Progress{ + Completed: false, + Transmitted: 128, + Total: 2048, + Err: nil, + } + pm, _ := json.Marshal(p) + t.Log("Progress example json:") + t.Log(string(pm)) + t.Log("\n") + + er := &EventReport{ + Priority: 1, + Category: "Test Events", + EventType: "Ping", + Details: "This is an example of an event report", + } + erm, _ := json.Marshal(er) + t.Log("EventReport example json:") + t.Log(string(erm)) + t.Log("\n") +} diff --git a/bindings/params.go b/bindings/params.go index 860c1fbcd1077201c2d285d84af37881fe54e8eb..cc7883b30c673f6166eca4a1d865481a24554fe0 100644 --- a/bindings/params.go +++ b/bindings/params.go @@ -11,6 +11,8 @@ package bindings import ( jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/fileTransfer" + "gitlab.com/elixxir/client/single" "gitlab.com/elixxir/client/xxdk" ) @@ -38,6 +40,40 @@ func GetDefaultE2EParams() []byte { return data } +// GetDefaultFileTransferParams returns a JSON serialized object with all the +// File transfer parameters and their default values. Call this function and modify +// the json to change file transfer settings. +func GetDefaultFileTransferParams() []byte { + defaultParams := fileTransfer.DefaultParams() + data, err := defaultParams.MarshalJSON() + if err != nil { + jww.FATAL.Panicf("Unexpected error: %+v", err) + } + return data +} + +// GetDefaultSingleUseParams returns a JSON serialized object with all the +// single use parameters and their default values. Call this function and modify +// the json to change single use settings. +func GetDefaultSingleUseParams() []byte { + defaultParams := single.GetDefaultRequestParams() + data, err := defaultParams.MarshalJSON() + if err != nil { + jww.FATAL.Panicf("Unexpected error: %+v", err) + } + return data +} + +func parseSingleUseParams(data []byte) (single.RequestParams, error) { + p := &single.RequestParams{} + return *p, p.UnmarshalJSON(data) +} + +func parseFileTransferParams(data []byte) (fileTransfer.Params, error) { + p := &fileTransfer.Params{} + return *p, p.UnmarshalJSON(data) +} + func parseCMixParams(data []byte) (xxdk.CMIXParams, error) { p := &xxdk.CMIXParams{} err := p.Unmarshal(data) diff --git a/bindings/restlike.go b/bindings/restlike.go index be0bd004f7bce59111e3c347ceecb7b5eab5bfcf..4884098085771853832a9104bf0715975f06db72 100644 --- a/bindings/restlike.go +++ b/bindings/restlike.go @@ -8,7 +8,6 @@ package bindings import ( "encoding/json" - "gitlab.com/elixxir/client/e2e" "gitlab.com/elixxir/client/restlike" "gitlab.com/elixxir/client/restlike/connect" @@ -34,7 +33,7 @@ type RestlikeMessage struct { // RestlikeRequest performs a normal restlike request // request - marshalled RestlikeMessage // Returns marshalled result RestlikeMessage -func RestlikeRequest(clientID int, connectionID int, request []byte) ([]byte, error) { +func RestlikeRequest(clientID, connectionID int, request []byte) ([]byte, error) { paramsJSON := GetDefaultE2EParams() cl, err := cmixTrackerSingleton.get(clientID) diff --git a/bindings/restlikeSingle.go b/bindings/restlikeSingle.go new file mode 100644 index 0000000000000000000000000000000000000000..eb7d34beeb115d531015b4c06e026fe96b0e5d02 --- /dev/null +++ b/bindings/restlikeSingle.go @@ -0,0 +1,92 @@ +package bindings + +import ( + "encoding/json" + "gitlab.com/elixxir/client/restlike" + "gitlab.com/elixxir/client/restlike/single" + "gitlab.com/elixxir/crypto/contact" +) + +// RestlikeCallback is the public function type bindings can use to make an asynchronous restlike request +// It accepts a json marshalled restlike.Message and an error (the results of calling json.Marshal on the message) +type RestlikeCallback interface { + Callback([]byte, error) +} + +// RequestRestLike sends a restlike request to a given contact +// Accepts marshalled contact object as recipient, marshalled RestlikeMessage and params JSON +// Returns json marshalled restlike.Message & error +func RequestRestLike(e2eID int, recipient, request, paramsJSON []byte) ([]byte, error) { + c, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + req := single.Request{ + Net: c.api.GetCmix(), + Rng: c.api.GetRng().GetStream(), + E2eGrp: c.api.GetStorage().GetE2EGroup(), + } + + message := &RestlikeMessage{} + err = json.Unmarshal(request, message) + + recipientContact, err := contact.Unmarshal(recipient) + if err != nil { + return nil, err + } + + params, err := parseSingleUseParams(paramsJSON) + if err != nil { + return nil, err + } + + resp, err := req.Request(recipientContact, restlike.Method(message.Method), restlike.URI(message.URI), + message.Content, &restlike.Headers{ + Headers: message.Headers, + Version: 0, + }, params) + if err != nil { + return nil, err + } + return json.Marshal(resp) +} + +// AsyncRequestRestLike sends an asynchronous restlike request to a given contact +// Accepts e2e client ID, marshalled contact object as recipient, +// marshalled RestlikeMessage, marshalled Params json, and a RestlikeCallback +// Returns an error, and the RestlikeCallback will be called with the results +// of json marshalling the response when received +func AsyncRequestRestLike(e2eID int, recipient, request, paramsJSON []byte, cb RestlikeCallback) error { + c, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return err + } + req := single.Request{ + Net: c.api.GetCmix(), + Rng: c.api.GetRng().GetStream(), + E2eGrp: c.api.GetStorage().GetE2EGroup(), + } + + message := &RestlikeMessage{} + err = json.Unmarshal(request, message) + + recipientContact, err := contact.Unmarshal(recipient) + if err != nil { + return err + } + + rlcb := func(message *restlike.Message) { + cb.Callback(json.Marshal(message)) + } + + params, err := parseSingleUseParams(paramsJSON) + if err != nil { + return err + } + + return req.AsyncRequest(recipientContact, restlike.Method(message.Method), restlike.URI(message.URI), + message.Content, &restlike.Headers{ + Headers: message.Headers, + Version: 0, + }, rlcb, params) +} diff --git a/bindings/single.go b/bindings/single.go new file mode 100644 index 0000000000000000000000000000000000000000..048edda8aa7b56fa9122740c1791986a05e2bb9f --- /dev/null +++ b/bindings/single.go @@ -0,0 +1,180 @@ +package bindings + +import ( + "encoding/json" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/elixxir/client/cmix/rounds" + "gitlab.com/elixxir/client/single" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/xx_network/primitives/id" +) + +/* PUBLIC WRAPPER METHODS */ + +// TransmitSingleUse accepts a marshalled recipient contact object, tag, payload, params JSON, SingleUseResponse callback func & a +// Client. Transmits payload to recipient via single use +func TransmitSingleUse(e2eID int, recipient []byte, tag string, payload, paramsJSON []byte, responseCB SingleUseResponse) ([]byte, error) { + e2eCl, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + recipientContact, err := contact.Unmarshal(recipient) + if err != nil { + return nil, err + } + + rcb := &singleUseResponse{response: responseCB} + + params, err := parseSingleUseParams(paramsJSON) + if err != nil { + return nil, err + } + + rids, eid, err := single.TransmitRequest(recipientContact, tag, payload, rcb, params, e2eCl.api.GetCmix(), e2eCl.api.GetRng().GetStream(), e2eCl.api.GetStorage().GetE2EGroup()) + + if err != nil { + return nil, err + } + sr := SingleUseSendReport{ + EphID: eid.EphId.Int64(), + ReceptionID: eid.Source.Marshal(), + RoundsList: makeRoundsList(rids), + } + return json.Marshal(sr) +} + +// Listen starts a single use listener on a given tag using the passed in client and SingleUseCallback func +func Listen(e2eID int, tag string, cb SingleUseCallback) (StopFunc, error) { + e2eCl, err := e2eTrackerSingleton.get(e2eID) + if err != nil { + return nil, err + } + + listener := singleUseListener{scb: cb} + dhpk, err := e2eCl.api.GetReceptionIdentity().GetDHKeyPrivate() + if err != nil { + return nil, err + } + l := single.Listen(tag, e2eCl.api.GetReceptionIdentity().ID, dhpk, e2eCl.api.GetCmix(), e2eCl.api.GetStorage().GetE2EGroup(), listener) + return l.Stop, nil +} + +// JSON Types + +// SingleUseSendReport is the bindings struct used to represent information returned by single.TransmitRequest +// +// Example json marshalled struct: +// {"Rounds":[1,5,9], +// "EphID":{"EphId":[0,0,0,0,0,0,3,89], +// "Source":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"}} +type SingleUseSendReport struct { + RoundsList + ReceptionID []byte + EphID int64 +} + +// SingleUseResponseReport is the bindings struct used to represent information passed +// to the single.Response callback interface in response to single.TransmitRequest +// +// Example json marshalled struct: +// {"Rounds":[1,5,9], +// "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==", +// "ReceptionID":{"EphId":[0,0,0,0,0,0,3,89], +// "Source":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"}, +// "Err":null} +type SingleUseResponseReport struct { + RoundsList + Payload []byte + ReceptionID []byte + EphID int64 + Err error +} + +// SingleUseCallbackReport is the bindings struct used to represent single use messages +// received by a callback passed into single.Listen +// +// Example json marshalled struct: +// {"Rounds":[1,5,9], +// "Payload":"rSuPD35ELWwm5KTR9ViKIz/r1YGRgXIl5792SF8o8piZzN6sT4Liq4rUU/nfOPvQEjbfWNh/NYxdJ72VctDnWw==", +// "Partner":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", +// "EphID":{"EphId":[0,0,0,0,0,0,3,89], +// "Source":"emV6aW1hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD"}} +type SingleUseCallbackReport struct { + RoundsList + Payload []byte + Partner *id.ID + EphID int64 + ReceptionID []byte +} + +// Function types + +// StopFunc is the function to stop a listener returned to the bindings layer when one is started +type StopFunc func() + +// SingleUseCallback func is passed into Listen and called when messages are received +// Accepts a SingleUseCallbackReport marshalled to json +type SingleUseCallback interface { + Callback(callbackReport []byte, err error) +} + +// SingleUseResponse is the public facing callback func passed by bindings clients into TransmitSingleUse +// Accepts a SingleUseResponseReport marshalled to json +type SingleUseResponse interface { + Callback(responseReport []byte, err error) +} + +/* CALLBACK WRAPPERS */ + +/* listener struct */ + +// singleUseListener is the internal struct used to wrap a SingleUseCallback func, +// which matches the single.Receiver interface +type singleUseListener struct { + scb SingleUseCallback +} + +// Callback is called whenever a single use message is heard by the listener, and translates the info to +//a SingleUseCallbackReport which is marshalled & passed to bindings +func (sl singleUseListener) Callback(req *single.Request, eid receptionID.EphemeralIdentity, rl []rounds.Round) { + var rids []id.Round + for _, r := range rl { + rids = append(rids, r.ID) + } + + // Todo: what other info from req needs to get to bindings + scr := SingleUseCallbackReport{ + Payload: req.GetPayload(), + RoundsList: makeRoundsList(rids), + Partner: req.GetPartner(), + EphID: eid.EphId.Int64(), + ReceptionID: eid.Source.Marshal(), + } + + sl.scb.Callback(json.Marshal(scr)) +} + +/* response struct */ + +// singleUseResponse is the private struct backing SingleUseResponse, which subscribes to the single.Response interface +type singleUseResponse struct { + response SingleUseResponse +} + +// Callback builds a SingleUseSendReport & passes the json marshalled version into the callback +func (sr singleUseResponse) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, + rounds []rounds.Round, err error) { + var rids []id.Round + for _, r := range rounds { + rids = append(rids, r.ID) + } + sendReport := SingleUseResponseReport{ + RoundsList: makeRoundsList(rids), + ReceptionID: receptionID.Source.Marshal(), + EphID: receptionID.EphId.Int64(), + Payload: payload, + Err: err, + } + sr.response.Callback(json.Marshal(&sendReport)) +} diff --git a/bindings/single_test.go b/bindings/single_test.go new file mode 100644 index 0000000000000000000000000000000000000000..807abe161b998bb259a5c9b757368746850e97bf --- /dev/null +++ b/bindings/single_test.go @@ -0,0 +1,67 @@ +package bindings + +import ( + "encoding/json" + "gitlab.com/elixxir/client/cmix/identity/receptionID" + "gitlab.com/xx_network/crypto/csprng" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/id/ephemeral" + "testing" + "time" +) + +func TestSingleUseJsonMarshals(t *testing.T) { + rids := []id.Round{1, 5, 9} + rl := makeRoundsList(rids) + rid := id.NewIdFromString("zezima", id.User, t) + eid, _, _, err := ephemeral.GetId(rid, 16, time.Now().UnixNano()) + if err != nil { + t.Fatalf("Failed to generate ephemeral id: %+v", err) + } + ephId := receptionID.EphemeralIdentity{ + EphId: eid, + Source: rid, + } + payload := make([]byte, 64) + rng := csprng.NewSystemRNG() + rng.Read(payload) + sendReport := SingleUseSendReport{ + RoundsList: rl, + EphID: ephId.EphId.Int64(), + ReceptionID: ephId.Source.Marshal(), + } + srm, err := json.Marshal(sendReport) + if err != nil { + t.Errorf("Failed to marshal send report to JSON: %+v", err) + } else { + t.Logf("Marshalled send report:\n%s\n", string(srm)) + } + + responseReport := SingleUseResponseReport{ + RoundsList: rl, + Payload: payload, + ReceptionID: ephId.Source.Marshal(), + EphID: ephId.EphId.Int64(), + Err: nil, + } + rrm, err := json.Marshal(responseReport) + if err != nil { + t.Errorf("Failed to marshal response report to JSON: %+v", err) + } else { + t.Logf("Marshalled response report:\n%s\n", string(rrm)) + } + + callbackReport := SingleUseCallbackReport{ + RoundsList: rl, + Payload: payload, + Partner: rid, + EphID: ephId.EphId.Int64(), + ReceptionID: ephId.Source.Marshal(), + } + crm, err := json.Marshal(callbackReport) + if err != nil { + t.Errorf("Failed to marshal callback report to JSON: %+v", err) + } else { + t.Logf("Marshalled callback report:\n%s\n", string(crm)) + } +}