Skip to content
Snippets Groups Projects
Commit efe89559 authored by Benjamin Wenger's avatar Benjamin Wenger
Browse files

implemented the bindings interface, made some slight improvements

 to the UD package
parent e7377972
No related branches found
No related tags found
No related merge requests found
......@@ -8,12 +8,27 @@
package bindings
import (
"errors"
"gitlab.com/elixxir/client/interfaces/contact"
"gitlab.com/elixxir/primitives/fact"
)
/* fact object*/
//creates a new fact. The factType must be either:
// 0 - Username
// 1 - Email
// 2 - Phone Number
// The fact must be well formed for the type and must not include commas or
// semicolons. If it is not well formed, it will be rejected. Phone numbers
// must have the two letter country codes appended. For the complete set of
// validation, see /elixxir/primitives/fact/fact.go
func NewFact(factType int, factStr string)(*Fact, error){
f, err := fact.NewFact(fact.FactType(factType), factStr)
if err!=nil{
return nil, err
}
return &Fact{f:&f}, nil
}
type Fact struct {
f *fact.Fact
}
......@@ -26,6 +41,10 @@ func (f *Fact) Type() int {
return int(f.f.T)
}
func (f *Fact) Stringify() string {
return f.f.Stringify()
}
/* contact object*/
type Contact struct {
c *contact.Contact
......@@ -54,32 +73,3 @@ func (c *Contact) GetFactList() *FactList {
func (c *Contact) Marshal() ([]byte, error) {
return c.c.Marshal()
}
/* FactList object*/
type FactList struct {
c *contact.Contact
}
func (fl *FactList) Num() int {
return len(fl.c.Facts)
}
func (fl *FactList) Get(i int) Fact {
return Fact{f: &(fl.c.Facts)[i]}
}
func (fl *FactList) Add(factData string, factType int) error {
ft := fact.FactType(factType)
if !ft.IsValid() {
return errors.New("Invalid fact type")
}
fl.c.Facts = append(fl.c.Facts, fact.Fact{
Fact: factData,
T: ft,
})
return nil
}
func (fl *FactList) Marshal() ([]byte, error) {
return []byte(fl.c.Facts.Stringify()), nil
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package bindings
/*
// Client is defined inside the api package. At minimum, it implements all of
// functionality defined here. A Client handles all network connectivity, key
// generation, and storage for a given cryptographic identity on the cmix
// network.
// These threads may become a significant drain on battery when offline, ensure
// they are stopped if there is no internet access
type client interface {
// ----- Network -----
// StartNetworkFollower kicks off the tracking of the network. It starts
// long running network client threads and returns an object for checking
// state and stopping those threads.
// Call this when returning from sleep and close when going back to
// sleep.
StartNetworkFollower() error
// StopNetworkFollower stops the network follower if it is running.
// It returns errors if the Follower is in the wrong status to stop or if it
// fails to stop it.
// if the network follower is running and this fails, the client object will
// most likely be in an unrecoverable state and need to be trashed.
StopNetworkFollower(timeoutMS int) error
// NetworkFollowerStatus gets the state of the network follower.
// Returns:
// Stopped - 0
// Starting - 1000
// Running - 2000
// Stopping - 3000
NetworkFollowerStatus() int
// Returns true if the following of the network is in a state where messages
// can be sent, false otherwise
IsNetworkHealthy() bool
// Registers a callback which gets triggered every time network health
// changes
RegisterNetworkHealthCB(func(bool))
// ----- Reception -----
// RegisterListener records and installs a listener for messages
// matching specific uid, msgType, and/or username
RegisterListener(uid []byte, msgType int, username string,
listener Listener)
// ----- Transmission -----
// SendE2E sends an end-to-end payload to the provided recipient with
// the provided msgType. Returns the list of rounds in which parts of
// the message were sent or an error if it fails.
SendE2E(payload, recipient []byte, msgType int) (RoundList, error)
// SendUnsafe sends an unencrypted payload to the provided recipient
// with the provided msgType. Returns the list of rounds in which parts
// of the message were sent or an error if it fails.
// NOTE: Do not use this function unless you know what you are doing.
// This function always produces an error message in client logging.
SendUnsafe(payload, recipient []byte, msgType int) (RoundList, error)
// SendCMIX sends a "raw" CMIX message payload to the provided
// recipient. Note that both SendE2E and SendUnsafe call SendCMIX.
// Returns the round ID of the round the payload was sent or an error
// if it fails.
SendCMIX(payload, recipient []byte) (int, error)
// ----- Notifications -----
// RegisterForNotifications allows a client to register for push
// notifications.
// Note that clients are not required to register for push notifications
// especially as these rely on third parties (i.e., Firebase *cough*
// *cough* google's palantir *cough*) that may represent a security
// risk to the user.
RegisterForNotifications(token []byte) error
// UnregisterForNotifications turns of notifications for this client
UnregisterForNotifications() error
// ----- Registration -----
// Returns true if the cryptographic identity has been registered with
// the CMIX user discovery agent.
// Note that clients do not need to perform this step if they use
// out of band methods to exchange cryptographic identities
// (e.g., QR codes), but failing to be registered precludes usage
// of the user discovery mechanism (this may be preferred by user).
IsRegistered() bool
// RegisterIdentity registers an arbitrary username with the user
// discovery protocol. Returns an error when it cannot connect or
// the username is already registered.
RegisterIdentity(username string) error
// RegisterEmail makes the users email searchable after confirmation.
// It returns a registration confirmation token to be used with
// ConfirmRegistration or an error on failure.
RegisterEmail(email string) ([]byte, error)
// RegisterPhone makes the users phone searchable after confirmation.
// It returns a registration confirmation token to be used with
// ConfirmRegistration or an error on failure.
RegisterPhone(phone string) ([]byte, error)
// ConfirmRegistration sends the user discovery agent a confirmation
// token (from Register Email/Phone) and code (string sent via Email
// or SMS to confirm ownership) to confirm ownership.
ConfirmRegistration(token, code []byte) error
// ----- Contacts -----
// GetUser returns the current user Identity for this client. This
// can be serialized into a byte stream for out-of-band sharing.
GetUser() (bind.Contact, error)
// ----- User Discovery -----
// Search accepts a "separator" separated list of search elements with
// an associated list of searchTypes. It returns a ContactList which
// allows you to iterate over the found contact objects.
Search(data, separator string, searchTypes []byte) ContactList
// SearchWithHandler is a non-blocking search that also registers
// a callback interface for user disovery events.
SearchWithHandler(data, separator string, searchTypes []byte,
hdlr UserDiscoveryHandler)
// ----- Key Exchange -----
// CreateAuthenticatedChannel creates a 1-way authenticated channel
// so this user can send messages to the desired recipient Contact.
// To receive confirmation from the remote user, clients must
// register a listener to do that.
CreateAuthenticatedChannel(recipient bind.Contact, payload []byte) error
// RegierAuthEventsHandler registers a callback interface for channel
// authentication events.
RegisterAuthEventsHandler(hdlr AuthEventHandler)
// ----- Network -----
// RegisterRoundEventsHandler registers a callback interface for round
// events.
RegisterRoundEventsHandler()
}
// ContactList contains a list of contacts
type ContactList interface {
// GetLen returns the number of contacts in the list
GetLen() int
// GetContact returns the contact at index i
GetContact(i int) bind.Contact
}
// ----- Callback interfaces -----
// AuthEventHandler handles authentication requests initiated by
// CreateAuthenticatedChannel
type AuthEventHandler interface {
// HandleConfirmation handles AuthEvents received after
// the client has called CreateAuthenticatedChannel for
// the provided contact. Payload is typically empty but
// may include a small introductory message.
HandleConfirmation(contact bind.Contact, payload []byte)
// HandleRequest handles AuthEvents received before
// the client has called CreateAuthenticatedChannel for
// the provided contact. It should prompt the user to accept
// the channel creation "request" and, if approved,
// call CreateAuthenticatedChannel for this Contact.
HandleRequest(contact bind.Contact, payload []byte)
}
// UserDiscoveryHandler handles search results against the user discovery agent.
type UserDiscoveryHandler interface {
HandleSearchResults(results ContactList)
}
// Message is a message received from the cMix network in the clear
// or that has been decrypted using established E2E keys.
type Message interface {
//Returns the id of the message
GetID() []byte
// Returns the message's sender ID, if available
GetSender() []byte
// Returns the message payload/contents
// Parse this with protobuf/whatever according to the message type
GetPayload() []byte
// Returns the message's type
GetMessageType() int
// Returns the message's timestamp in milliseconds since unix epoc
GetTimestampMS() int
// Returns the message's timestamp in ns since unix epoc
GetTimestampNano() int
}*/
......@@ -9,9 +9,13 @@ package bindings
import (
"errors"
"gitlab.com/elixxir/client/interfaces/contact"
"gitlab.com/elixxir/primitives/fact"
"gitlab.com/xx_network/primitives/id"
)
/*IntList*/
type IntList struct {
lst []int
}
......@@ -35,6 +39,8 @@ func (il *IntList) Get(i int) (int, error) {
return il.lst[i], nil
}
/*RoundList*/
type RoundList struct {
list []id.Round
}
......@@ -53,3 +59,59 @@ func (rl *RoundList) Get(i int) (int, error) {
return int(rl.list[i]), nil
}
/*ContactList*/
type ContactList struct {
list []contact.Contact
}
// Gets the number of round IDs stored
func (cl *ContactList) Len() int {
return len(cl.list)
}
// Gets a stored round ID at the given index
func (cl *ContactList) Get(i int) (*Contact, error) {
if i < 0 || i > len(cl.list) {
return nil, errors.New("contact cannot be under 0 or over" +
" list len")
}
return &Contact{c:&cl.list[i]}, nil
}
/*FactList*/
func NewFactList()*FactList{
return &FactList{ c: &contact.Contact{
ID: nil,
DhPubKey: nil,
OwnershipProof: nil,
Facts: make([]fact.Fact,0),
}}
}
type FactList struct {
c *contact.Contact
}
func (fl *FactList) Num() int {
return len(fl.c.Facts)
}
func (fl *FactList) Get(i int) Fact {
return Fact{f: &(fl.c.Facts)[i]}
}
func (fl *FactList) Add(factData string, factType int) error {
f, err := fact.NewFact(fact.FactType(factType), factData)
if err!=nil{
return err
}
fl.c.Facts = append(fl.c.Facts, f)
return nil
}
func (fl *FactList) Stringify() (string, error) {
return fl.c.Facts.Stringify(), nil
}
///////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 xx network SEZC //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file //
///////////////////////////////////////////////////////////////////////////////
package bindings
import (
"github.com/pkg/errors"
"gitlab.com/elixxir/client/interfaces/contact"
"gitlab.com/elixxir/client/ud"
"gitlab.com/elixxir/primitives/fact"
"gitlab.com/xx_network/primitives/id"
"time"
)
//This package wraps the user discovery system
// User Discovery object
type UserDiscovery struct{
ud *ud.Manager
}
// Returns a new user discovery object. Only call this once. It must be called
// after StartNetworkFollower is called and will fail if the network has never
// been contacted.
// This function technically has a memory leak because it causes both sides of
// the bindings to think the other is in charge of the client object.
// In general this is not an issue because the client object should exist
// for the life of the program.
func NewUserDiscovery(client *Client)(*UserDiscovery, error){
m, err := ud.NewManager(&client.api)
if err!=nil{
return nil, err
}else{
return &UserDiscovery{ud:m}, nil
}
}
// Register registers a user with user discovery. Will return an error if the
// network signatures are malformed or if the username is taken. Usernames
// cannot be changed after registration at this time. Will fail if the user is
// already registered.
// Registration does not go over cmix, it occurs over normal communications
func (ud *UserDiscovery)Register(username string)error{
return ud.ud.Register(username)
}
// Adds a fact for the user to user discovery. Will only succeed if the
// user is already registered and the system does not have the fact currently
// registered for any user.
// Will fail if the fact string is not well formed.
// This does not complete the fact registration process, it returns a
// confirmation id instead. Over the communications system the fact is
// associated with, a code will be sent. This confirmation ID needs to be
// called along with the code to finalize the fact.
func (ud *UserDiscovery)AddFact(fStr string)(string, error){
f, err := fact.UnstringifyFact(fStr)
if err !=nil{
return "", errors.WithMessage(err, "Failed to add due to " +
"malformed fact")
}
return ud.ud.SendRegisterFact(f)
}
// Confirms a fact first registered via AddFact. The confirmation ID comes from
// AddFact while the code will come over the associated communications system
func (ud *UserDiscovery)ConfirmFact(confirmationID, code string)error{
return ud.ud.SendConfirmFact(confirmationID, code)
}
// Removes a previously confirmed fact. Will fail if the passed fact string is
// not well formed or if the fact is not associated with this client.
func (ud *UserDiscovery)RemoveFact(fStr string)error{
f, err := fact.UnstringifyFact(fStr)
if err !=nil{
return errors.WithMessage(err, "Failed to remove due to " +
"malformed fact")
}
return ud.ud.RemoveFact(f)
}
// SearchCallback returns the result of a search
type SearchCallback interface {
Callback(contacts *ContactList, error string)
}
// Searches for the passed Facts. The factList is the stringification of a
// fact list object, look at /bindings/list.go for more on that object.
// This will reject if that object is malformed. The SearchCallback will return
// a list of contacts, each having the facts it hit against.
// This is NOT intended to be used to search for multiple users at once, that
// can have a privacy reduction. Instead, it is intended to be used to search
// for a user where multiple pieces of information is known.
func (ud UserDiscovery)Search(fl string, callback SearchCallback,
timeoutMS int)error{
factList, _, err := fact.UnstringifyFactList(fl)
if err!=nil{
return errors.WithMessage(err, "Failed to search due to " +
"malformed fact list")
}
timeout := time.Duration(timeoutMS)*time.Millisecond
cb := func(cl []contact.Contact, err error){
var contactList *ContactList
var errStr string
if err==nil{
contactList = &ContactList{list:cl}
}else{
errStr = err.Error()
}
callback.Callback(contactList, errStr)
}
return ud.ud.Search(factList, cb, timeout)
}
// SingleSearchCallback returns the result of a single search
type SingleSearchCallback interface {
Callback(contact *Contact, error string)
}
// Searches for the passed Facts. The fact is the stringification of a
// fact object, look at /bindings/contact.go for more on that object.
// This will reject if that object is malformed. The SearchCallback will return
// a list of contacts, each having the facts it hit against.
// This only searches for a single fact at a time. It is intended to make some
// simple use cases of the API easier.
func (ud UserDiscovery)SearchSingle(f string, callback SingleSearchCallback,
timeoutMS int)error{
fObj, err := fact.UnstringifyFact(f)
if err!=nil{
return errors.WithMessage(err, "Failed to single search due " +
"to malformed fact")
}
timeout := time.Duration(timeoutMS)*time.Millisecond
cb := func(cl []contact.Contact, err error){
var contact *Contact
var errStr string
if err==nil{
contact = &Contact{c:&cl[0]}
}else{
errStr = err.Error()
}
callback.Callback(contact, errStr)
}
return ud.ud.Search([]fact.Fact{fObj}, cb, timeout)
}
// SingleSearchCallback returns the result of a single search
type LookupCallback interface {
Callback(contact *Contact, error string)
}
// Looks for the contact object associated with the given userID. The
// id is the byte representation of an id.
// This will reject if that id is malformed. The LookupCallback will return
// the associated contact if it exists.
func (ud UserDiscovery)Lookup(idBytes []byte, callback LookupCallback,
timeoutMS int)error {
uid, err := id.Unmarshal(idBytes)
if err!=nil{
return errors.WithMessage(err, "Failed to lookup due to " +
"malformed id")
}
timeout := time.Duration(timeoutMS)*time.Millisecond
cb := func(cl contact.Contact, err error){
var contact *Contact
var errStr string
if err==nil{
contact = &Contact{c:&cl}
}else{
errStr = err.Error()
}
callback.Callback(contact, errStr)
}
return ud.ud.Lookup(uid, cb, timeout)
}
\ No newline at end of file
......@@ -10,27 +10,37 @@ import (
"gitlab.com/xx_network/comms/connect"
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/primitives/id"
jww "github.com/spf13/jwalterweatherman"
)
type addFactComms interface {
SendRegisterFact(host *connect.Host, message *pb.FactRegisterRequest) (*pb.FactRegisterResponse, error)
}
func (m *Manager) SendRegisterFact(fact fact.Fact) (*pb.FactRegisterResponse, error) {
// Adds a fact for the user to user discovery. Will only succeed if the
// user is already registered and the system does not have the fact currently
// registered for any user.
// This does not complete the fact registration process, it returns a
// confirmation id instead. Over the communications system the fact is
// associated with, a code will be sent. This confirmation ID needs to be
// called along with the code to finalize the fact.
func (m *Manager) SendRegisterFact(fact fact.Fact) (string, error) {
jww.INFO.Printf("ud.SendRegisterFact(%s)", fact.Stringify())
uid := m.storage.User().GetCryptographicIdentity().GetUserID()
return m.addFact(fact, uid, m.comms)
}
func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (*pb.FactRegisterResponse, error) {
func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (string, error) {
if !m.IsRegistered() {
return nil, errors.New("Failed to add fact: " +
return "", errors.New("Failed to add fact: " +
"client is not registered")
}
// Create a primitives Fact so we can hash it
f, err := fact.NewFact(inFact.T, inFact.Fact)
if err != nil {
return &pb.FactRegisterResponse{}, err
return "", err
}
// Create a hash of our fact
......@@ -39,7 +49,7 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (*pb.F
// Sign our inFact for putting into the request
fsig, err := rsa.Sign(rand.Reader, m.privKey, hash.CMixHash, fhash, nil)
if err != nil {
return &pb.FactRegisterResponse{}, err
return "", err
}
// Create our Fact Removal Request message data
......@@ -55,6 +65,11 @@ func (m *Manager) addFact(inFact fact.Fact, uid *id.ID, aFC addFactComms) (*pb.F
// Send the message
response, err := aFC.SendRegisterFact(m.host, &remFactMsg)
confirmationID := ""
if response!=nil{
confirmationID=response.ConfirmationID
}
// Return the error
return response, err
return confirmationID, err
}
......@@ -5,13 +5,17 @@ import (
pb "gitlab.com/elixxir/comms/mixmessages"
"gitlab.com/xx_network/comms/connect"
"gitlab.com/xx_network/comms/messages"
jww "github.com/spf13/jwalterweatherman"
)
type confirmFactComm interface {
SendConfirmFact(host *connect.Host, message *pb.FactConfirmRequest) (*messages.Ack, error)
}
// Confirms a fact first registered via AddFact. The confirmation ID comes from
// AddFact while the code will come over the associated communications system
func (m *Manager) SendConfirmFact(confirmationID, code string) error {
jww.INFO.Printf("ud.SendConfirmFact(%s, %s)", confirmationID, code)
if err := m.confirmFact(confirmationID, code, m.comms); err!=nil{
return errors.WithMessage(err, "Failed to confirm fact")
}
......
......@@ -56,7 +56,7 @@ func (m *Manager) lookupProcess(c chan message.Receive, quitCh <-chan struct{})
// Lookup returns the public key of the passed ID as known by the user discovery
// system or returns by the timeout.
func (m *Manager) Lookup(uid *id.ID, callback lookupCallback, timeout time.Duration) error {
jww.INFO.Printf("ud.Lookup(%s, %s)", uid, timeout)
if !m.IsRegistered(){
return errors.New("Failed to lookup: " +
"client is not registered")
......
......@@ -14,6 +14,7 @@ import (
"gitlab.com/xx_network/crypto/signature/rsa"
"gitlab.com/xx_network/primitives/id"
"sync"
jww "github.com/spf13/jwalterweatherman"
)
type Manager struct {
......@@ -48,7 +49,7 @@ type Manager struct {
// New manager builds a new user discovery manager. It requires that an
// updated NDF is available and will error if one is not.
func NewManager(client *api.Client)(*Manager, error){
jww.INFO.Println("ud.NewManager()")
if !client.GetHealth().IsHealthy(){
return nil, errors.New("cannot start UD Manager when network " +
"is not healthy")
......
......@@ -9,14 +9,20 @@ import (
"gitlab.com/xx_network/comms/connect"
"gitlab.com/xx_network/comms/messages"
"gitlab.com/xx_network/crypto/signature/rsa"
jww "github.com/spf13/jwalterweatherman"
)
type registerUserComms interface {
SendRegisterUser(*connect.Host, *pb.UDBUserRegistration) (*messages.Ack, error)
}
// Register registers a user with user discovery.
// Register registers a user with user discovery. Will return an error if the
// network signatures are malformed or if the username is taken. Usernames cannot
// be changed after registration at this time. Will fail if the user is already
// registered.
// Registration does not go over cmix, it occurs over normal communications
func (m *Manager) Register(username string) error {
jww.INFO.Printf("ud.Register(%s)", username)
return m.register(username, m.comms)
}
......
......@@ -6,13 +6,17 @@ import (
"gitlab.com/elixxir/primitives/fact"
"gitlab.com/xx_network/comms/connect"
"gitlab.com/xx_network/comms/messages"
jww "github.com/spf13/jwalterweatherman"
)
type removeFactComms interface {
SendDeleteMessage(host *connect.Host, message *mixmessages.FactRemovalRequest) (*messages.Ack, error)
}
// Removes a previously confirmed fact. Will fail if the fact is not
// associated with this client.
func (m *Manager) RemoveFact(fact fact.Fact) error {
jww.INFO.Printf("ud.RemoveFact(%s)", fact.Stringify())
return m.removeFact(fact, nil)
}
......
......@@ -16,7 +16,7 @@ import (
"time"
)
type searchCallback func([]contact.Contact, error)
type SearchCallback func([]contact.Contact, error)
func (m *Manager) searchProcess(c chan message.Receive, quitCh <-chan struct{}) {
for true {
......@@ -54,8 +54,13 @@ func (m *Manager) searchProcess(c chan message.Receive, quitCh <-chan struct{})
}
}
// Search...
func (m *Manager) Search(list fact.FactList, callback searchCallback, timeout time.Duration) error {
// Searches for the passed Facts. The SearchCallback will return
// a list of contacts, each having the facts it hit against.
// This is NOT intended to be used to search for multiple users at once, that
// can have a privacy reduction. Instead, it is intended to be used to search
// for a user where multiple pieces of information is known.
func (m *Manager) Search(list fact.FactList, callback SearchCallback, timeout time.Duration) error {
jww.INFO.Printf("ud.Search(%s, %s)", list.Stringify(), timeout)
if !m.IsRegistered() {
return errors.New("Failed to search: " +
"client is not registered")
......
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