Skip to content
Snippets Groups Projects
Commit 8b048f62 authored by Jonah Husson's avatar Jonah Husson
Browse files

restlike Single Use & file transfer, fixes for single use (bindings)

parent b7cb280f
No related branches found
No related tags found
2 merge requests!510Release,!228New bindings for api2.0
package bindings
import (
"encoding/json"
"gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/client/e2e/rekey"
"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
}
// 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 uint32
}
// 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 uint16
Total uint16
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 client ID, ReceiveFileCallback and a ReporterFunc
func InitFileTransfer(clientID int, cb ReceiveFileCallback, r ReporterFunc) (*FileTransfer, error) {
// Get bindings client from singleton
c, err := clientTrackerSingleton.get(clientID)
if err != nil {
return nil, err
}
// Create the callback to wrap the ReceiveFileCallback
rcb := func(tid *ftCrypto.TransferID, fileName, fileType string,
sender *id.ID, size uint32, preview []byte) {
f := &ReceivedFile{
TransferID: tid.Bytes(),
SenderID: sender.Marshal(),
Preview: preview,
Name: fileName,
Type: fileType,
Size: size,
}
cb.Callback(json.Marshal(f))
}
// Client info
myID := c.api.GetUser().TransmissionID
kv := c.api.GetStorage().GetKV()
e2eGrp := c.api.GetStorage().GetE2EGroup()
rng := c.api.GetRng()
// Create e2e manager (should this have a singleton?)
err = e2e.Init(kv, myID, c.api.GetUser().E2eDhPrivateKey, e2eGrp, rekey.GetDefaultParams())
e2eManager, err := e2e.Load(kv, c.api.GetCmix(), myID, e2eGrp, rng, &reporter{
r: r,
})
// Create file transfer manager
m, err := fileTransfer.NewManager(rcb, fileTransfer.DefaultParams(), myID,
c.api.GetCmix(), e2eManager, kv, rng)
// Add file transfer processes to client services tracking
err = c.api.AddService(m.StartProcesses)
if err != nil {
return nil, err
}
// Return wrapped manager
return &FileTransfer{ft: m}, nil
}
// Send is the bindings-level function for sending a File
func (f *FileTransfer) Send(payload, recipientID []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,
t fileTransfer.FilePartTracker, err error) {
prog := &Progress{
Completed: completed,
Transmitted: arrived,
Total: 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
}
// Send file
ftID, err := f.ft.Send(fs.Name, fs.Type, fs.Contents, recipient, retry, fs.Preview, cb, p)
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 */
func (f *FileTransfer) RegisterSentProgressCallback(tidBytes []byte,
callback FileTransferSentProgressCallback, period string) error {
cb := func(completed bool, arrived, total uint16,
t fileTransfer.FilePartTracker, err error) {
prog := &Progress{
Completed: completed,
Transmitted: arrived,
Total: 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)
}
func (f *FileTransfer) RegisterReceivedProgressCallback(tidBytes []byte, callback FileTransferReceiveProgressCallback, period string) error {
cb := func(completed bool, received, total uint16,
t fileTransfer.FilePartTracker, err error) {
prog := &Progress{
Completed: completed,
Transmitted: received,
Total: 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))
}
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")
}
......@@ -33,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) {
cl, err := clientTrackerSingleton.get(clientID)
if err != nil {
return nil, err
......
package bindings
import (
"encoding/json"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/restlike/single"
singleuse "gitlab.com/elixxir/client/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, byte slice payload & headers, method enum and a URI
// Returns json marshalled restlike.Message & error
func RequestRestLike(clientID int, recipient, request []byte) ([]byte, error) {
c, err := clientTrackerSingleton.get(clientID)
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
}
resp, err := req.Request(recipientContact, restlike.Method(message.Method), restlike.URI(message.URI),
message.Content, &restlike.Headers{
Headers: message.Headers,
Version: 0,
}, singleuse.GetDefaultRequestParams())
if err != nil {
return nil, err
}
return json.Marshal(resp)
}
// AsyncRequestRestLike sends an asynchronous restlike request to a given contact
// Accepts marshalled contact object as recipient, byte slice payload & headers, method enum, URI, and a RestlikeCallback
// Returns an error, and the RestlikeCallback will be called with the results of json marshalling the response when received
func AsyncRequestRestLike(clientID int, recipient, request []byte, cb RestlikeCallback) error {
c, err := clientTrackerSingleton.get(clientID)
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))
}
return req.AsyncRequest(recipientContact, restlike.Method(message.Method), restlike.URI(message.URI),
message.Content, &restlike.Headers{
Headers: message.Headers,
Version: 0,
}, rlcb, singleuse.GetDefaultRequestParams())
}
......@@ -9,6 +9,47 @@ import (
"gitlab.com/xx_network/primitives/id"
)
/* PUBLIC WRAPPER METHODS */
// TransmitSingleUse accepts a marshalled recipient contact object, tag, payload, SingleUseResponse callback func & a
// Client. Transmits payload to recipient via single use
func TransmitSingleUse(clientID int, recipient []byte, tag string, payload []byte, responseCB SingleUseResponse) ([]byte, error) {
cl, err := clientTrackerSingleton.get(clientID)
if err != nil {
return nil, err
}
recipientContact, err := contact.Unmarshal(recipient)
if err != nil {
return nil, err
}
rcb := &singleUseResponse{response: responseCB}
rids, eid, err := single.TransmitRequest(recipientContact, tag, payload, rcb, single.GetDefaultRequestParams(), cl.api.GetCmix(), cl.api.GetRng().GetStream(), cl.api.GetStorage().GetE2EGroup())
if err != nil {
return nil, err
}
sr := SingleUseSendReport{
EphID: eid,
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(clientID int, tag string, cb SingleUseCallback) (StopFunc, error) {
cl, err := clientTrackerSingleton.get(clientID)
if err != nil {
return nil, err
}
listener := singleUseListener{scb: cb}
l := single.Listen(tag, cl.api.GetUser().ReceptionID, cl.api.GetUser().E2eDhPrivateKey, cl.api.GetCmix(), cl.api.GetStorage().GetE2EGroup(), listener)
return l.Stop, nil
}
// JSON Types
// SingleUseSendReport is the bindings struct used to represent information returned by single.TransmitRequest
......@@ -61,11 +102,15 @@ type StopFunc func()
// SingleUseCallback func is passed into Listen and called when messages are received
// Accepts a SingleUseCallbackReport marshalled to json
type SingleUseCallback func(callbackReport []byte, err error)
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 func(responseReport []byte, err error)
type SingleUseResponse interface {
Callback(responseReport []byte, err error)
}
/* CALLBACK WRAPPERS */
......@@ -93,14 +138,14 @@ func (sl singleUseListener) Callback(req *single.Request, eid receptionID.Epheme
EphID: eid,
}
sl.scb(json.Marshal(scr))
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 {
responseFunc SingleUseResponse
response SingleUseResponse
}
// Callback builds a SingleUseSendReport & passes the json marshalled version into the callback
......@@ -116,36 +161,5 @@ func (sr singleUseResponse) Callback(payload []byte, receptionID receptionID.Eph
Payload: payload,
Err: err,
}
sr.responseFunc(json.Marshal(&sendReport))
}
/* PUBLIC WRAPPER METHODS */
// TransmitSingleUse accepts a marshalled recipient contact object, tag, payload, SingleUseResponse callback func & a
// Client. Transmits payload to recipient via single use
func TransmitSingleUse(recipient []byte, tag string, payload []byte, responseCB SingleUseResponse, cl *Client) ([]byte, error) {
recipientContact, err := contact.Unmarshal(recipient)
if err != nil {
return nil, err
}
rcb := &singleUseResponse{responseFunc: responseCB}
rids, eid, err := single.TransmitRequest(recipientContact, tag, payload, rcb, single.GetDefaultRequestParams(), cl.api.GetCmix(), cl.api.GetRng().GetStream(), cl.api.GetStorage().GetE2EGroup())
if err != nil {
return nil, err
}
sr := SingleUseSendReport{
EphID: eid,
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(tag string, cl *Client, cb SingleUseCallback) StopFunc {
listener := singleUseListener{scb: cb}
l := single.Listen(tag, cl.api.GetUser().ReceptionID, cl.api.GetUser().E2eDhPrivateKey, cl.api.GetCmix(), cl.api.GetStorage().GetE2EGroup(), listener)
return l.Stop
sr.response.Callback(json.Marshal(&sendReport))
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment