From 32230b77c1bc30c0b2cb4933687331917af5de47 Mon Sep 17 00:00:00 2001 From: John Brooks Date: Sun, 24 Sep 2017 14:15:53 -0600 Subject: [PATCH] core: Reuse protobuf structures for configuration The existing configuration was partially compatible with Ricochet's, but not enough to actually be useful. It also required a bunch of boilerplate code to copy data between configuration data structures, internal data structures, and RPC data structures. Protobuf conveniently supports encoding messages to JSON, and we already need to store most data (e.g. contacts) in protobuf structures. This commit changes the configuration to be a protobuf JSON serialization of the Config message, which can directly reuse RPC messages like Contact. Additionally, the RWMutex-based configuration type was a deadlock waiting to happen. There is now a read-only clone of the configuration available atomically at any time. Writers need an exclusive lock on the ConfigFile object, which commits its changes to disk and readers on unlock. --- core/config.go | 177 ---------------------------------- core/config/config.go | 115 ++++++++++++++++++++++ core/contact.go | 111 +++++++++------------ core/contactlist.go | 53 +++++----- core/identity.go | 22 ++--- core/inboundcontactrequest.go | 7 +- core/ricochet.go | 5 +- ricochet-cli/main.go | 8 +- rpc/config.pb.go | 89 +++++++++++++++++ rpc/config.proto | 17 ++++ rpc/contact.pb.go | 114 ++++++++++++++-------- rpc/contact.proto | 3 + rpc/rpc.go | 2 +- 13 files changed, 388 insertions(+), 335 deletions(-) delete mode 100644 core/config.go create mode 100644 core/config/config.go create mode 100644 rpc/config.pb.go create mode 100644 rpc/config.proto diff --git a/core/config.go b/core/config.go deleted file mode 100644 index 0cdf358..0000000 --- a/core/config.go +++ /dev/null @@ -1,177 +0,0 @@ -package core - -import ( - "encoding/json" - "io/ioutil" - "log" - "os" - "sync" -) - -// XXX This is partially but not fully compatible with Ricochet's JSON -// configs. It might be better to be explicitly not compatible, but have -// an automatic import function. - -type Config struct { - filePath string - - root ConfigRoot - mutex sync.RWMutex -} - -type ConfigRoot struct { - Contacts map[string]ConfigContact - Identity ConfigIdentity - - // Not used by the permanent instance in Config - writable bool - config *Config -} - -type ConfigContact struct { - Hostname string - LastConnected string `json:",omitempty"` - Nickname string - WhenCreated string - Request ConfigContactRequest `json:",omitempty"` -} - -type ConfigContactRequest struct { - Pending bool - MyNickname string - Message string - WhenDelivered string `json:",omitempty"` - WhenRejected string `json:",omitempty"` - RemoteError string `json:",omitempty"` -} - -type ConfigIdentity struct { - DataDirectory string `json:",omitempty"` - ServiceKey string -} - -func LoadConfig(filePath string) (*Config, error) { - config := &Config{ - filePath: filePath, - } - - data, err := ioutil.ReadFile(config.filePath) - if err != nil { - log.Printf("Config read error from %s: %v", config.filePath, err) - return nil, err - } - - if err := json.Unmarshal(data, &config.root); err != nil { - log.Printf("Config parse error: %v", err) - return nil, err - } - - return config, nil -} - -// Return a read-only snapshot of the current configuration. This object -// _must not_ be stored, and you must call Close() when finished with it. -// This function may block. -func (c *Config) OpenRead() *ConfigRoot { - c.mutex.RLock() - root := c.root.Clone() - root.writable = false - root.config = c - return root -} - -// Return a writable snapshot of the current configuration. This object -// _must not_ be stored, and you must call Save() or Discard() when -// finished with it. This function may block. -func (c *Config) OpenWrite() *ConfigRoot { - c.mutex.Lock() - root := c.root.Clone() - root.writable = true - root.config = c - return root -} - -func (root *ConfigRoot) Clone() *ConfigRoot { - re := *root - re.Contacts = make(map[string]ConfigContact) - for k, v := range root.Contacts { - re.Contacts[k] = v - } - return &re -} - -func (root *ConfigRoot) Close() { - if root.writable { - log.Panic("Close called on writable config object; use Save or Discard") - } - if root.config == nil { - log.Panic("Close called on closed config object") - } - root.config.mutex.RUnlock() - root.config = nil -} - -// Save writes the state to disk, and updates the Config object if -// successful. Changes to the object are discarded on error. -func (root *ConfigRoot) Save() error { - if !root.writable { - log.Panic("Save called on read-only config object") - } - if root.config == nil { - log.Panic("Save called on closed config object") - } - c := root.config - root.writable = false - root.config = nil - err := c.save(root) - c.mutex.Unlock() - return err -} - -// Discard closes a config write without saving any changes to disk -// or to the Config object. -func (root *ConfigRoot) Discard() { - if !root.writable { - log.Panic("Discard called on read-only config object") - } - if root.config == nil { - log.Panic("Discard called on closed config object") - } - c := root.config - root.config = nil - c.mutex.Unlock() -} - -func (c *Config) save(newRoot *ConfigRoot) error { - data, err := json.MarshalIndent(newRoot, "", " ") - if err != nil { - log.Printf("Config encoding error: %v", err) - return err - } - - // Make a pathetic attempt at atomic file write by writing into a - // temporary file and renaming over the original; this is probably - // imperfect as-implemented, but better than truncating and writing - // directly. - tempPath := c.filePath + ".new" - file, err := os.OpenFile(tempPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) - if err != nil { - log.Printf("Config save error: %v", err) - return err - } - - if _, err := file.Write(data); err != nil { - log.Printf("Config write error: %v", err) - file.Close() - return err - } - - file.Close() - if err := os.Rename(tempPath, c.filePath); err != nil { - log.Printf("Config replace error: %v", err) - return err - } - - c.root = *newRoot - return nil -} diff --git a/core/config/config.go b/core/config/config.go new file mode 100644 index 0000000..6043162 --- /dev/null +++ b/core/config/config.go @@ -0,0 +1,115 @@ +package config + +import ( + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "github.com/ricochet-im/ricochet-go/rpc" + "log" + "os" + "sync" + "sync/atomic" +) + +type ConfigFile struct { + filePath string + root *ricochet.Config + readSnapshot atomic.Value + mutex sync.Mutex +} + +func NewConfigFile(path string) (*ConfigFile, error) { + cfg := &ConfigFile{ + filePath: path, + root: &ricochet.Config{}, + } + cfg.readSnapshot.Store(cfg.root) + if err := cfg.save(); err != nil { + return nil, err + } + return cfg, nil +} + +func LoadConfigFile(path string) (*ConfigFile, error) { + cfg := &ConfigFile{ + filePath: path, + root: &ricochet.Config{}, + } + + file, err := os.Open(cfg.filePath) + if err != nil { + return nil, err + } + defer file.Close() + + json := jsonpb.Unmarshaler{ + AllowUnknownFields: true, + } + if err := json.Unmarshal(file, cfg.root); err != nil { + return nil, err + } + + cfg.readSnapshot.Store(cfg.root) + return cfg, nil +} + +// Read returns a **read-only** snapshot of the current configuration. This +// function is threadsafe, and the values in this instance of the configuration +// will not change when the configuration changes. +// +// Do not under any circumstances modify any part of this object. +func (cfg *ConfigFile) Read() *ricochet.Config { + return cfg.readSnapshot.Load().(*ricochet.Config) +} + +// Lock gains exclusive control of the configuration state, allowing the caller +// to safely make changes to the configuration. Concurrent readers will not see +// any changes until Unlock() is called, at which point they will atomically be +// visible in future calls to Read() as well as being saved persistently. +func (cfg *ConfigFile) Lock() *ricochet.Config { + cfg.mutex.Lock() + // Clone the current configuration for a mutable copy + cfg.root = proto.Clone(cfg.root).(*ricochet.Config) + return cfg.root +} + +func (cfg *ConfigFile) Unlock() { + // Clone cfg.root again to guarantee that any messages are detached from + // instances that exist elsewhere in the code. Inefficient but safe. + cfg.root = proto.Clone(cfg.root).(*ricochet.Config) + cfg.readSnapshot.Store(cfg.root) + err := cfg.save() + if err != nil { + log.Printf("WARNING: Unable to save configuration: %s", err) + } + cfg.mutex.Unlock() +} + +func (cfg *ConfigFile) save() error { + json := jsonpb.Marshaler{Indent: " "} + + // Make a pathetic attempt at atomic file write by writing into a + // temporary file and renaming over the original; this is probably + // imperfect as-implemented, but better than truncating and writing + // directly. + tempPath := cfg.filePath + ".new" + file, err := os.OpenFile(tempPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if err != nil { + log.Printf("Config save error: %v", err) + return err + } + + err = json.Marshal(file, cfg.root) + if err != nil { + log.Printf("Config encoding error: %v", err) + file.Close() + return err + } + + file.Close() + if err := os.Rename(tempPath, cfg.filePath); err != nil { + log.Printf("Config replace error: %v", err) + return err + } + + return nil +} diff --git a/core/contact.go b/core/contact.go index 8cdbc14..0987657 100644 --- a/core/contact.go +++ b/core/contact.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "github.com/golang/protobuf/proto" "github.com/ricochet-im/ricochet-go/core/utils" "github.com/ricochet-im/ricochet-go/rpc" protocol "github.com/s-rah/go-ricochet" @@ -14,18 +15,11 @@ import ( "time" ) -// XXX There is generally a lot of duplication and boilerplate between -// Contact, ConfigContact, and rpc.Contact. This should be reduced somehow. - -// XXX Consider replacing the config contact with the protobuf structure, -// and extending the protobuf structure for everything it needs. - type Contact struct { core *Ricochet - id int - data ConfigContact - status ricochet.Contact_Status + id int + data *ricochet.Contact mutex sync.Mutex events *utils.Publisher @@ -41,7 +35,7 @@ type Contact struct { conversation *Conversation } -func ContactFromConfig(core *Ricochet, id int, data ConfigContact, events *utils.Publisher) (*Contact, error) { +func ContactFromConfig(core *Ricochet, id int, data *ricochet.Contact, events *utils.Publisher) (*Contact, error) { contact := &Contact{ core: core, id: id, @@ -53,16 +47,18 @@ func ContactFromConfig(core *Ricochet, id int, data ConfigContact, events *utils if id < 0 { return nil, fmt.Errorf("Invalid contact ID '%d'", id) - } else if !IsOnionValid(data.Hostname) { - return nil, fmt.Errorf("Invalid contact hostname '%s", data.Hostname) + } else if !IsAddressValid(data.Address) { + return nil, fmt.Errorf("Invalid contact address '%s", data.Address) } - if data.Request.Pending { - if data.Request.WhenRejected != "" { - contact.status = ricochet.Contact_REJECTED + if data.Request != nil { + if data.Request.Rejected { + contact.data.Status = ricochet.Contact_REJECTED } else { - contact.status = ricochet.Contact_REQUEST + contact.data.Status = ricochet.Contact_REQUEST } + } else if contact.data.Status != ricochet.Contact_REJECTED { + contact.data.Status = ricochet.Contact_UNKNOWN } return contact, nil @@ -81,14 +77,14 @@ func (c *Contact) Nickname() string { func (c *Contact) Address() string { c.mutex.Lock() defer c.mutex.Unlock() - address, _ := AddressFromOnion(c.data.Hostname) - return address + return c.data.Address } func (c *Contact) Hostname() string { c.mutex.Lock() defer c.mutex.Unlock() - return c.data.Hostname + hostname, _ := OnionFromAddress(c.data.Address) + return hostname } func (c *Contact) LastConnected() time.Time { @@ -108,47 +104,28 @@ func (c *Contact) WhenCreated() time.Time { func (c *Contact) Status() ricochet.Contact_Status { c.mutex.Lock() defer c.mutex.Unlock() - return c.status + return c.data.Status } func (c *Contact) Data() *ricochet.Contact { c.mutex.Lock() defer c.mutex.Unlock() - address, _ := AddressFromOnion(c.data.Hostname) - data := &ricochet.Contact{ - Id: int32(c.id), - Address: address, - Nickname: c.data.Nickname, - WhenCreated: c.data.WhenCreated, - LastConnected: c.data.LastConnected, - Status: c.status, - } - if c.data.Request.Pending { - data.Request = &ricochet.ContactRequest{ - Direction: ricochet.ContactRequest_OUTBOUND, - Address: data.Address, - Nickname: data.Nickname, - Text: c.data.Request.Message, - FromNickname: c.data.Request.MyNickname, - } - } - return data + return proto.Clone(c.data).(*ricochet.Contact) } func (c *Contact) IsRequest() bool { c.mutex.Lock() defer c.mutex.Unlock() - return c.data.Request.Pending + return c.data.Request != nil } func (c *Contact) Conversation() *Conversation { c.mutex.Lock() defer c.mutex.Unlock() if c.conversation == nil { - address, _ := AddressFromOnion(c.data.Hostname) entity := &ricochet.Entity{ ContactId: int32(c.id), - Address: address, + Address: c.data.Address, } c.conversation = NewConversation(c, entity, c.core.Identity.ConversationStream) } @@ -187,7 +164,7 @@ func (c *Contact) shouldMakeOutboundConnections() bool { defer c.mutex.Unlock() // Don't make connections to contacts in the REJECTED state - if c.status == ricochet.Contact_REJECTED { + if c.data.Status == ricochet.Contact_REJECTED { return false } @@ -263,7 +240,7 @@ func (c *Contact) contactConnection() { // already closed. If there was an existing connection and this returns nil, // the old connection is closed but c.connection has not been reset. if err := c.considerUsingConnection(conn); err != nil { - log.Printf("Discarded new contact %s connection: %s", c.data.Hostname, err) + log.Printf("Discarded new contact %s connection: %s", c.data.Address, err) go closeUnhandledConnection(conn) c.mutex.Unlock() continue @@ -335,8 +312,8 @@ func (c *Contact) connectOutbound(ctx context.Context, connChannel chan *connect Network: c.core.Network, NeverGiveUp: true, } - hostname := c.data.Hostname - isRequest := c.data.Request.Pending + hostname, _ := OnionFromAddress(c.data.Address) + isRequest := c.data.Request != nil c.mutex.Unlock() for { @@ -446,8 +423,8 @@ func (c *Contact) sendContactRequest(conn *connection.Connection, ctx context.Co _, err := conn.RequestOpenChannel("im.ricochet.contact.request", &channels.ContactRequestChannel{ Handler: &requestChannelHandler{Response: responseChan}, - Name: c.data.Request.MyNickname, // XXX mutex - Message: c.data.Request.Message, + Name: c.data.Request.FromNickname, // XXX mutex + Message: c.data.Request.Text, }) return err }) @@ -502,9 +479,9 @@ func (c *Contact) considerUsingConnection(conn *connection.Connection) error { }() if conn.IsInbound { - log.Printf("Contact %s has a new inbound connection", c.data.Hostname) + log.Printf("Contact %s has a new inbound connection", c.data.Address) } else { - log.Printf("Contact %s has a new outbound connection", c.data.Hostname) + log.Printf("Contact %s has a new outbound connection", c.data.Address) } if conn == c.connection { @@ -515,8 +492,9 @@ func (c *Contact) considerUsingConnection(conn *connection.Connection) error { return fmt.Errorf("Connection %v is not authenticated", conn) } - if c.data.Hostname[0:16] != conn.RemoteHostname { - return fmt.Errorf("Connection hostname %s doesn't match contact hostname %s when assigning connection", conn.RemoteHostname, c.data.Hostname[0:16]) + plainHost, _ := PlainHostFromAddress(c.data.Address) + if plainHost != conn.RemoteHostname { + return fmt.Errorf("Connection hostname %s doesn't match contact hostname %s when assigning connection", conn.RemoteHostname, plainHost) } if c.connection != nil && !c.shouldReplaceConnection(conn) { @@ -539,28 +517,29 @@ func (c *Contact) considerUsingConnection(conn *connection.Connection) error { // Assumes c.mutex is held. func (c *Contact) onConnectionStateChanged() { if c.connection != nil { - if c.data.Request.Pending && c.connection.IsInbound { + if c.data.Request != nil && c.connection.IsInbound { // Inbound connection implicitly accepts the contact request and can continue as a contact // Outbound request logic is all handled by connectOutbound. log.Printf("Contact request implicitly accepted by contact %v", c) c.updateContactRequest("Accepted") } else { - c.status = ricochet.Contact_ONLINE + c.data.Status = ricochet.Contact_ONLINE } } else { - if c.status == ricochet.Contact_ONLINE { - c.status = ricochet.Contact_OFFLINE + if c.data.Status == ricochet.Contact_ONLINE { + c.data.Status = ricochet.Contact_OFFLINE } } // Update LastConnected time c.timeConnected = time.Now() - - config := c.core.Config.OpenWrite() c.data.LastConnected = c.timeConnected.Format(time.RFC3339) - config.Contacts[strconv.Itoa(c.id)] = c.data - config.Save() + config := c.core.Config.Lock() + config.Contacts[strconv.Itoa(c.id)] = c.data + c.core.Config.Unlock() + + // XXX I wonder if events and config updates can be combined now, and made safer... // _really_ assumes c.mutex was held c.mutex.Unlock() event := ricochet.ContactEvent{ @@ -615,7 +594,7 @@ func (c *Contact) UpdateContactRequest(status string) bool { c.mutex.Lock() defer c.mutex.Unlock() - if !c.data.Request.Pending { + if c.data.Request == nil { return false } @@ -635,7 +614,6 @@ func (c *Contact) UpdateContactRequest(status string) bool { // Same as above, but assumes the mutex is already held and that the caller // will send an UPDATE event func (c *Contact) updateContactRequest(status string) bool { - config := c.core.Config.OpenWrite() now := time.Now().Format(time.RFC3339) // Whether to keep the channel open var re bool @@ -646,11 +624,11 @@ func (c *Contact) updateContactRequest(status string) bool { re = true case "Accepted": - c.data.Request = ConfigContactRequest{} + c.data.Request = nil if c.connection != nil { - c.status = ricochet.Contact_ONLINE + c.data.Status = ricochet.Contact_ONLINE } else { - c.status = ricochet.Contact_UNKNOWN + c.data.Status = ricochet.Contact_UNKNOWN } case "Rejected": @@ -664,8 +642,9 @@ func (c *Contact) updateContactRequest(status string) bool { log.Printf("Unknown contact request status '%s'", status) } + config := c.core.Config.Lock() + defer c.core.Config.Unlock() config.Contacts[strconv.Itoa(c.id)] = c.data - config.Save() return re } diff --git a/core/contactlist.go b/core/contactlist.go index 34eb669..4e904b5 100644 --- a/core/contactlist.go +++ b/core/contactlist.go @@ -27,9 +27,7 @@ func LoadContactList(core *Ricochet) (*ContactList, error) { inboundRequests: make(map[string]*InboundContactRequest), } - config := core.Config.OpenRead() - defer config.Close() - + config := core.Config.Read() list.contacts = make(map[int]*Contact, len(config.Contacts)) for idStr, data := range config.Contacts { id, err := strconv.Atoi(idStr) @@ -99,20 +97,15 @@ func (cl *ContactList) InboundRequestByAddress(address string) *InboundContactRe // Generally, you will use AddContactRequest (for outbound requests) and // AddOrUpdateInboundContactRequest plus InboundContactRequest.Accept() instead of // using this function directly. -func (this *ContactList) AddNewContact(configContact ConfigContact) (*Contact, error) { +func (this *ContactList) AddNewContact(data *ricochet.Contact) (*Contact, error) { this.mutex.Lock() defer this.mutex.Unlock() - address, ok := AddressFromOnion(configContact.Hostname) - if !ok { - return nil, errors.New("Invalid ricochet address") - } - for _, contact := range this.contacts { - if contact.Address() == address { + if contact.Address() == data.Address { return nil, errors.New("Contact already exists with this address") } - if contact.Nickname() == configContact.Nickname { + if contact.Nickname() == data.Nickname { return nil, errors.New("Contact already exists with this nickname") } } @@ -120,7 +113,7 @@ func (this *ContactList) AddNewContact(configContact ConfigContact) (*Contact, e // XXX check inbound requests (but this can be called for an inbound req too) // Write new contact into config - config := this.core.Config.OpenWrite() + config := this.core.Config.Lock() maxContactId := 0 for idstr, _ := range config.Contacts { @@ -132,13 +125,15 @@ func (this *ContactList) AddNewContact(configContact ConfigContact) (*Contact, e } contactId := maxContactId + 1 - config.Contacts[strconv.Itoa(contactId)] = configContact - if err := config.Save(); err != nil { - return nil, err + + if config.Contacts == nil { + config.Contacts = make(map[string]*ricochet.Contact) } + config.Contacts[strconv.Itoa(contactId)] = data + this.core.Config.Unlock() // Create Contact - contact, err := ContactFromConfig(this.core, contactId, configContact, this.events) + contact, err := ContactFromConfig(this.core, contactId, data, this.events) if err != nil { return nil, err } @@ -163,8 +158,7 @@ func (this *ContactList) AddNewContact(configContact ConfigContact) (*Contact, e // If an inbound request already exists for this address, that request will be automatically // accepted, and the returned contact will already be fully established. func (cl *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) { - onion, valid := OnionFromAddress(address) - if !valid { + if !IsAddressValid(address) { return nil, errors.New("Invalid ricochet address") } if !IsNicknameAcceptable(name) { @@ -177,17 +171,20 @@ func (cl *ContactList) AddContactRequest(address, name, fromName, text string) ( return nil, errors.New("Invalid message") } - configContact := ConfigContact{ - Hostname: onion, + data := &ricochet.Contact{ + Address: address, Nickname: name, WhenCreated: time.Now().Format(time.RFC3339), - Request: ConfigContactRequest{ - Pending: true, - MyNickname: fromName, - Message: text, + Request: &ricochet.ContactRequest{ + Direction: ricochet.ContactRequest_OUTBOUND, + Address: address, + Nickname: name, + FromNickname: fromName, + Text: text, + WhenCreated: time.Now().Format(time.RFC3339), }, } - contact, err := cl.AddNewContact(configContact) + contact, err := cl.AddNewContact(data) if err != nil { return nil, err } @@ -213,11 +210,9 @@ func (this *ContactList) RemoveContact(contact *Contact) error { // leaves a goroutine up among other things. contact.StopConnection() - config := this.core.Config.OpenWrite() + config := this.core.Config.Lock() delete(config.Contacts, strconv.Itoa(contact.Id())) - if err := config.Save(); err != nil { - return err - } + this.core.Config.Unlock() delete(this.contacts, contact.Id()) diff --git a/core/identity.go b/core/identity.go index 9cc263a..c13905e 100644 --- a/core/identity.go +++ b/core/identity.go @@ -2,9 +2,9 @@ package core import ( "crypto/rsa" - "encoding/base64" "errors" "github.com/ricochet-im/ricochet-go/core/utils" + "github.com/ricochet-im/ricochet-go/rpc" protocol "github.com/s-rah/go-ricochet" connection "github.com/s-rah/go-ricochet/connection" "github.com/yawning/bulb/utils/pkcs1" @@ -51,15 +51,10 @@ func CreateIdentity(core *Ricochet) (*Identity, error) { } func (me *Identity) loadIdentity() error { - config := me.core.Config.OpenRead() - defer config.Close() - - if config.Identity.ServiceKey != "" { - keyData, err := base64.StdEncoding.DecodeString(config.Identity.ServiceKey) - if err != nil { - return err - } + config := me.core.Config.Read() + if keyData := config.Secrets.GetServicePrivateKey(); keyData != nil { + var err error me.privateKey, _, err = pkcs1.DecodePrivateKeyDER(keyData) if err != nil { return err @@ -90,9 +85,12 @@ func (me *Identity) setPrivateKey(key *rsa.PrivateKey) error { if err != nil { return err } - config := me.core.Config.OpenWrite() - config.Identity.ServiceKey = base64.StdEncoding.EncodeToString(keyData) - config.Save() + config := me.core.Config.Lock() + if config.Secrets == nil { + config.Secrets = &ricochet.Secrets{} + } + config.Secrets.ServicePrivateKey = keyData + me.core.Config.Unlock() // Update Identity me.address, err = AddressFromKey(&key.PublicKey) diff --git a/core/inboundcontactrequest.go b/core/inboundcontactrequest.go index 0c8c740..62e4995 100644 --- a/core/inboundcontactrequest.go +++ b/core/inboundcontactrequest.go @@ -266,13 +266,12 @@ func (cr *InboundContactRequest) Accept() (*Contact, error) { log.Printf("Accepting contact request from %s", cr.data.Address) - onion, _ := OnionFromAddress(cr.data.Address) - configContact := ConfigContact{ - Hostname: onion, + data := &ricochet.Contact{ + Address: cr.data.Address, Nickname: cr.data.FromNickname, WhenCreated: cr.data.WhenCreated, } - contact, err := cr.core.Identity.ContactList().AddNewContact(configContact) + contact, err := cr.core.Identity.ContactList().AddNewContact(data) if err != nil { log.Printf("Error occurred in accepting contact request: %s", err) return nil, err diff --git a/core/ricochet.go b/core/ricochet.go index ad5395f..cca2c00 100644 --- a/core/ricochet.go +++ b/core/ricochet.go @@ -2,6 +2,7 @@ package core import ( cryptorand "crypto/rand" + "github.com/ricochet-im/ricochet-go/core/config" "log" "math" "math/big" @@ -11,12 +12,12 @@ import ( ) type Ricochet struct { - Config *Config + Config *config.ConfigFile Network *Network Identity *Identity } -func (core *Ricochet) Init(conf *Config) (err error) { +func (core *Ricochet) Init(conf *config.ConfigFile) (err error) { initRand() core.Config = conf diff --git a/ricochet-cli/main.go b/ricochet-cli/main.go index 6ad4cd0..73821b7 100644 --- a/ricochet-cli/main.go +++ b/ricochet-cli/main.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/chzyer/readline" ricochet "github.com/ricochet-im/ricochet-go/core" + "github.com/ricochet-im/ricochet-go/core/config" rpc "github.com/ricochet-im/ricochet-go/rpc" "google.golang.org/grpc" "log" @@ -175,13 +176,16 @@ func checkBackendAddressSafety(address string) error { } func startBackend() error { - config, err := ricochet.LoadConfig(configPath) + cfg, err := config.LoadConfigFile(configPath) + if err != nil && os.IsNotExist(err) { + cfg, err = config.NewConfigFile(configPath) + } if err != nil { return err } core := new(ricochet.Ricochet) - if err := core.Init(config); err != nil { + if err := core.Init(cfg); err != nil { return err } diff --git a/rpc/config.pb.go b/rpc/config.pb.go new file mode 100644 index 0000000..3af94c0 --- /dev/null +++ b/rpc/config.pb.go @@ -0,0 +1,89 @@ +// Code generated by protoc-gen-go. +// source: config.proto +// DO NOT EDIT! + +package ricochet + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Config struct { + Identity *Identity `protobuf:"bytes,1,opt,name=identity" json:"identity,omitempty"` + Contacts map[string]*Contact `protobuf:"bytes,2,rep,name=contacts" json:"contacts,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Secrets *Secrets `protobuf:"bytes,3,opt,name=secrets" json:"secrets,omitempty"` +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} } + +func (m *Config) GetIdentity() *Identity { + if m != nil { + return m.Identity + } + return nil +} + +func (m *Config) GetContacts() map[string]*Contact { + if m != nil { + return m.Contacts + } + return nil +} + +func (m *Config) GetSecrets() *Secrets { + if m != nil { + return m.Secrets + } + return nil +} + +// Secrets are not transmitted to frontend RPC clients +type Secrets struct { + ServicePrivateKey []byte `protobuf:"bytes,1,opt,name=servicePrivateKey,proto3" json:"servicePrivateKey,omitempty"` +} + +func (m *Secrets) Reset() { *m = Secrets{} } +func (m *Secrets) String() string { return proto.CompactTextString(m) } +func (*Secrets) ProtoMessage() {} +func (*Secrets) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{1} } + +func (m *Secrets) GetServicePrivateKey() []byte { + if m != nil { + return m.ServicePrivateKey + } + return nil +} + +func init() { + proto.RegisterType((*Config)(nil), "ricochet.Config") + proto.RegisterType((*Secrets)(nil), "ricochet.Secrets") +} + +func init() { proto.RegisterFile("config.proto", fileDescriptor5) } + +var fileDescriptor5 = []byte{ + // 235 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0xce, 0xcf, 0x4b, + 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x28, 0xca, 0x4c, 0xce, 0x4f, 0xce, + 0x48, 0x2d, 0x91, 0xe2, 0x4d, 0xce, 0xcf, 0x2b, 0x49, 0x4c, 0x2e, 0x81, 0x48, 0x48, 0xf1, 0x65, + 0xa6, 0xa4, 0xe6, 0x95, 0x64, 0x96, 0x54, 0x42, 0xf8, 0x4a, 0x1f, 0x19, 0xb9, 0xd8, 0x9c, 0xc1, + 0x3a, 0x85, 0xf4, 0xb8, 0x38, 0x60, 0x92, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x42, 0x7a, + 0x30, 0x63, 0xf4, 0x3c, 0xa1, 0x32, 0x41, 0x70, 0x35, 0x42, 0x56, 0x5c, 0x1c, 0x50, 0xb3, 0x8b, + 0x25, 0x98, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0xe4, 0x10, 0xea, 0x21, 0x66, 0x82, 0x28, 0xb0, 0x02, + 0xd7, 0xbc, 0x92, 0xa2, 0xca, 0x20, 0xb8, 0x7a, 0x21, 0x6d, 0x2e, 0xf6, 0xe2, 0xd4, 0xe4, 0xa2, + 0xd4, 0x92, 0x62, 0x09, 0x66, 0xb0, 0x55, 0x82, 0x08, 0xad, 0xc1, 0x10, 0x89, 0x20, 0x98, 0x0a, + 0x29, 0x3f, 0x2e, 0x5e, 0x14, 0x73, 0x84, 0x04, 0xb8, 0x98, 0xb3, 0x53, 0x21, 0x8e, 0xe4, 0x0c, + 0x02, 0x31, 0x85, 0xd4, 0xb9, 0x58, 0xcb, 0x12, 0x73, 0x4a, 0x53, 0x25, 0x98, 0xd0, 0x4d, 0x83, + 0xea, 0x0c, 0x82, 0xc8, 0x5b, 0x31, 0x59, 0x30, 0x2a, 0x99, 0x73, 0xb1, 0x43, 0xed, 0x10, 0xd2, + 0xe1, 0x12, 0x2c, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x0d, 0x28, 0xca, 0x2c, 0x4b, 0x2c, 0x49, + 0xf5, 0x86, 0x9a, 0xcb, 0x13, 0x84, 0x29, 0x91, 0xc4, 0x06, 0x0e, 0x33, 0x63, 0x40, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x42, 0xba, 0x23, 0x37, 0x6c, 0x01, 0x00, 0x00, +} diff --git a/rpc/config.proto b/rpc/config.proto new file mode 100644 index 0000000..f4a9673 --- /dev/null +++ b/rpc/config.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package ricochet; + +import "contact.proto"; +import "identity.proto"; + +message Config { + Identity identity = 1; + map contacts = 2; + Secrets secrets = 3; +} + +// Secrets are not transmitted to frontend RPC clients +message Secrets { + bytes servicePrivateKey = 1; +} + diff --git a/rpc/contact.pb.go b/rpc/contact.pb.go index 76efe4b..dcb08c7 100644 --- a/rpc/contact.pb.go +++ b/rpc/contact.pb.go @@ -11,6 +11,7 @@ It is generated from these files: core.proto identity.proto network.proto + config.proto It has these top-level messages: Contact @@ -38,6 +39,8 @@ It has these top-level messages: NetworkStatus StartNetworkRequest StopNetworkRequest + Config + Secrets */ package ricochet @@ -202,13 +205,16 @@ func (m *Contact) GetStatus() Contact_Status { } type ContactRequest struct { - Direction ContactRequest_Direction `protobuf:"varint,1,opt,name=direction,enum=ricochet.ContactRequest_Direction" json:"direction,omitempty"` - Address string `protobuf:"bytes,2,opt,name=address" json:"address,omitempty"` - Nickname string `protobuf:"bytes,3,opt,name=nickname" json:"nickname,omitempty"` - Text string `protobuf:"bytes,4,opt,name=text" json:"text,omitempty"` - FromNickname string `protobuf:"bytes,5,opt,name=fromNickname" json:"fromNickname,omitempty"` - WhenCreated string `protobuf:"bytes,6,opt,name=whenCreated" json:"whenCreated,omitempty"` - Rejected bool `protobuf:"varint,7,opt,name=rejected" json:"rejected,omitempty"` + Direction ContactRequest_Direction `protobuf:"varint,1,opt,name=direction,enum=ricochet.ContactRequest_Direction" json:"direction,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address" json:"address,omitempty"` + Nickname string `protobuf:"bytes,3,opt,name=nickname" json:"nickname,omitempty"` + Text string `protobuf:"bytes,4,opt,name=text" json:"text,omitempty"` + FromNickname string `protobuf:"bytes,5,opt,name=fromNickname" json:"fromNickname,omitempty"` + WhenCreated string `protobuf:"bytes,6,opt,name=whenCreated" json:"whenCreated,omitempty"` + Rejected bool `protobuf:"varint,7,opt,name=rejected" json:"rejected,omitempty"` + WhenDelivered string `protobuf:"bytes,8,opt,name=whenDelivered" json:"whenDelivered,omitempty"` + WhenRejected string `protobuf:"bytes,9,opt,name=whenRejected" json:"whenRejected,omitempty"` + RemoteError string `protobuf:"bytes,10,opt,name=remoteError" json:"remoteError,omitempty"` } func (m *ContactRequest) Reset() { *m = ContactRequest{} } @@ -265,6 +271,27 @@ func (m *ContactRequest) GetRejected() bool { return false } +func (m *ContactRequest) GetWhenDelivered() string { + if m != nil { + return m.WhenDelivered + } + return "" +} + +func (m *ContactRequest) GetWhenRejected() string { + if m != nil { + return m.WhenRejected + } + return "" +} + +func (m *ContactRequest) GetRemoteError() string { + if m != nil { + return m.RemoteError + } + return "" +} + type MonitorContactsRequest struct { } @@ -467,39 +494,42 @@ func init() { func init() { proto.RegisterFile("contact.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 539 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xd1, 0x72, 0x93, 0x40, - 0x14, 0x2d, 0x84, 0x02, 0xb9, 0x69, 0x23, 0xdd, 0xe9, 0x38, 0xd8, 0xbe, 0x30, 0x3b, 0x8e, 0x93, - 0x17, 0xd1, 0x89, 0xbe, 0xdb, 0x34, 0xd0, 0x31, 0x1a, 0x21, 0x6e, 0x61, 0x7c, 0x26, 0xb0, 0x4e, - 0xd1, 0x74, 0x89, 0xb0, 0x51, 0xf3, 0x43, 0x7e, 0x8b, 0x9f, 0xe3, 0x27, 0x38, 0xbb, 0x40, 0x6a, - 0x12, 0x75, 0x1c, 0xdf, 0xf6, 0x9e, 0x73, 0x2e, 0xbb, 0x7b, 0xee, 0x59, 0xe0, 0x38, 0x2d, 0x18, - 0x4f, 0x52, 0xee, 0x2e, 0xcb, 0x82, 0x17, 0xc8, 0x2c, 0xf3, 0xb4, 0x48, 0x6f, 0x28, 0xc7, 0xdf, - 0x55, 0x30, 0xc6, 0x35, 0x87, 0xfa, 0xa0, 0xe6, 0x99, 0xad, 0x38, 0xca, 0xe0, 0x90, 0xa8, 0x79, - 0x86, 0x6c, 0x30, 0x92, 0x2c, 0x2b, 0x69, 0x55, 0xd9, 0xaa, 0xa3, 0x0c, 0xba, 0xa4, 0x2d, 0xd1, - 0x19, 0x98, 0x2c, 0x4f, 0x3f, 0xb2, 0xe4, 0x96, 0xda, 0x1d, 0x49, 0x6d, 0x6a, 0xe4, 0x40, 0xef, - 0xcb, 0x0d, 0x65, 0xe3, 0x92, 0x26, 0x9c, 0x66, 0xb6, 0x26, 0xe9, 0x5f, 0x21, 0xf4, 0x10, 0x8e, - 0x17, 0x49, 0xc5, 0xc7, 0x05, 0x63, 0x34, 0x15, 0x9a, 0x43, 0xa9, 0xd9, 0x06, 0xd1, 0x10, 0x8c, - 0x92, 0x7e, 0x5a, 0xd1, 0x8a, 0xdb, 0xba, 0xa3, 0x0c, 0x7a, 0x43, 0xdb, 0x6d, 0x4f, 0xed, 0x36, - 0x27, 0x26, 0x35, 0x4f, 0x5a, 0x21, 0x7a, 0x0a, 0x7a, 0xc5, 0x13, 0xbe, 0xaa, 0x6c, 0x70, 0x94, - 0x41, 0xff, 0x37, 0x2d, 0xee, 0xb5, 0xe4, 0x49, 0xa3, 0xc3, 0x13, 0xd0, 0x6b, 0x04, 0xf5, 0xc0, - 0x88, 0x83, 0xd7, 0x41, 0xf8, 0x2e, 0xb0, 0x0e, 0x44, 0x11, 0x5e, 0x5d, 0x4d, 0x27, 0x81, 0x6f, - 0x29, 0x08, 0x40, 0x0f, 0x03, 0xb9, 0x56, 0x05, 0x41, 0xfc, 0xb7, 0xb1, 0x7f, 0x1d, 0x59, 0x1d, - 0x74, 0x04, 0x26, 0xf1, 0x5f, 0xf9, 0xe3, 0xc8, 0xf7, 0x2c, 0x0d, 0x7f, 0x53, 0xa1, 0xbf, 0x7d, - 0x30, 0x74, 0x01, 0xdd, 0x2c, 0x2f, 0x69, 0xca, 0xf3, 0x82, 0x49, 0x63, 0xfb, 0x43, 0xfc, 0xa7, - 0x5b, 0xb8, 0x5e, 0xab, 0x24, 0x77, 0x4d, 0xff, 0x39, 0x03, 0x04, 0x1a, 0xa7, 0x5f, 0x79, 0x63, - 0xbe, 0x5c, 0x23, 0x0c, 0x47, 0xef, 0xcb, 0xe2, 0x36, 0x68, 0x7b, 0x6a, 0xd3, 0xb7, 0xb0, 0xdd, - 0xd9, 0xe9, 0xfb, 0xb3, 0x3b, 0x03, 0xb3, 0xa4, 0x1f, 0xea, 0xb1, 0x19, 0x8e, 0x32, 0x30, 0xc9, - 0xa6, 0xc6, 0x8f, 0xa0, 0xbb, 0xb9, 0x83, 0x30, 0x6a, 0x12, 0x5c, 0x86, 0x71, 0xe0, 0x59, 0x07, - 0xc2, 0xa8, 0x30, 0x8e, 0xea, 0x4a, 0xc1, 0x36, 0xdc, 0x7f, 0x53, 0xb0, 0x9c, 0x17, 0x65, 0xe3, - 0x40, 0xd5, 0x58, 0x80, 0x7f, 0x28, 0x70, 0xd4, 0x60, 0xfe, 0x67, 0xca, 0x38, 0x7a, 0x02, 0x1a, - 0x5f, 0x2f, 0x69, 0xe3, 0xdd, 0xf9, 0x9e, 0x77, 0x52, 0xe5, 0x46, 0xeb, 0x25, 0x25, 0x52, 0x88, - 0x1e, 0x83, 0xd1, 0x44, 0x5d, 0xfa, 0xd5, 0x1b, 0x9e, 0xec, 0xf5, 0xbc, 0x3c, 0x20, 0xad, 0x06, - 0x3d, 0xbf, 0x0b, 0x59, 0xe7, 0xef, 0x21, 0x13, 0x5d, 0x8d, 0x14, 0xbf, 0x00, 0x4d, 0x6c, 0x89, - 0x4c, 0xd0, 0x82, 0x78, 0x3a, 0xad, 0x2f, 0x38, 0x0b, 0x67, 0xf1, 0x74, 0x14, 0x89, 0xc0, 0x18, - 0xd0, 0x19, 0x79, 0x9e, 0xa5, 0x8a, 0xe4, 0xc4, 0x33, 0x4f, 0x80, 0x1d, 0xb1, 0xf6, 0xfc, 0xa9, - 0x1f, 0xf9, 0x96, 0x76, 0xd9, 0x05, 0xa3, 0x5a, 0xcd, 0x85, 0x6d, 0xf8, 0x04, 0xee, 0x8d, 0xb2, - 0x6c, 0xb3, 0xd7, 0x72, 0xb1, 0xc6, 0x17, 0x70, 0xea, 0xd1, 0x05, 0xe5, 0x74, 0x27, 0x4d, 0xff, - 0xfc, 0x3e, 0xf1, 0x29, 0xa0, 0x9d, 0x2f, 0x88, 0xef, 0x9e, 0xc3, 0x03, 0x22, 0x67, 0x35, 0x61, - 0xf3, 0x62, 0xc5, 0xb2, 0xf6, 0xf9, 0x08, 0x72, 0xae, 0xcb, 0x3f, 0xc3, 0xb3, 0x9f, 0x01, 0x00, - 0x00, 0xff, 0xff, 0xe5, 0x62, 0x19, 0x3f, 0x2a, 0x04, 0x00, 0x00, + // 581 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0x5e, 0xda, 0x2c, 0x3f, 0xa7, 0x5b, 0xc9, 0xac, 0x09, 0x85, 0xed, 0x26, 0xb2, 0x10, 0xea, + 0x0d, 0x01, 0x15, 0xee, 0xd9, 0xd6, 0x64, 0xa2, 0x50, 0x92, 0xe1, 0x25, 0xe2, 0x3a, 0x4b, 0x8c, + 0x16, 0xe8, 0xe2, 0xe2, 0xb8, 0x83, 0xbd, 0x06, 0x4f, 0xc5, 0xe3, 0xf0, 0x08, 0xc8, 0x4e, 0xd2, + 0xad, 0x2b, 0x20, 0xc4, 0x9d, 0xcf, 0x77, 0xbe, 0x63, 0x1f, 0x7f, 0xdf, 0xb1, 0x61, 0x37, 0x67, + 0x95, 0xc8, 0x72, 0xe1, 0x2f, 0x38, 0x13, 0x0c, 0x59, 0xbc, 0xcc, 0x59, 0x7e, 0x49, 0x05, 0xfe, + 0xd1, 0x03, 0x73, 0xd2, 0xe4, 0xd0, 0x10, 0x7a, 0x65, 0xe1, 0x6a, 0x9e, 0x36, 0xda, 0x26, 0xbd, + 0xb2, 0x40, 0x2e, 0x98, 0x59, 0x51, 0x70, 0x5a, 0xd7, 0x6e, 0xcf, 0xd3, 0x46, 0x36, 0xe9, 0x42, + 0x74, 0x00, 0x56, 0x55, 0xe6, 0x9f, 0xab, 0xec, 0x8a, 0xba, 0x7d, 0x95, 0x5a, 0xc5, 0xc8, 0x83, + 0xc1, 0xd7, 0x4b, 0x5a, 0x4d, 0x38, 0xcd, 0x04, 0x2d, 0x5c, 0x5d, 0xa5, 0xef, 0x42, 0xe8, 0x31, + 0xec, 0xce, 0xb3, 0x5a, 0x4c, 0x58, 0x55, 0xd1, 0x5c, 0x72, 0xb6, 0x15, 0x67, 0x1d, 0x44, 0x63, + 0x30, 0x39, 0xfd, 0xb2, 0xa4, 0xb5, 0x70, 0x0d, 0x4f, 0x1b, 0x0d, 0xc6, 0xae, 0xdf, 0x75, 0xed, + 0xb7, 0x1d, 0x93, 0x26, 0x4f, 0x3a, 0x22, 0x7a, 0x0e, 0x46, 0x2d, 0x32, 0xb1, 0xac, 0x5d, 0xf0, + 0xb4, 0xd1, 0xf0, 0x37, 0x25, 0xfe, 0xb9, 0xca, 0x93, 0x96, 0x87, 0xa7, 0x60, 0x34, 0x08, 0x1a, + 0x80, 0x99, 0x46, 0x6f, 0xa3, 0xf8, 0x43, 0xe4, 0x6c, 0xc9, 0x20, 0x3e, 0x3d, 0x9d, 0x4d, 0xa3, + 0xd0, 0xd1, 0x10, 0x80, 0x11, 0x47, 0x6a, 0xdd, 0x93, 0x09, 0x12, 0xbe, 0x4f, 0xc3, 0xf3, 0xc4, + 0xe9, 0xa3, 0x1d, 0xb0, 0x48, 0xf8, 0x26, 0x9c, 0x24, 0x61, 0xe0, 0xe8, 0xf8, 0x7b, 0x1f, 0x86, + 0xeb, 0x8d, 0xa1, 0x23, 0xb0, 0x8b, 0x92, 0xd3, 0x5c, 0x94, 0xac, 0x52, 0xc2, 0x0e, 0xc7, 0xf8, + 0x4f, 0xb7, 0xf0, 0x83, 0x8e, 0x49, 0x6e, 0x8b, 0xfe, 0xd3, 0x03, 0x04, 0xba, 0xa0, 0xdf, 0x44, + 0x2b, 0xbe, 0x5a, 0x23, 0x0c, 0x3b, 0x1f, 0x39, 0xbb, 0x8a, 0xba, 0x9a, 0x46, 0xf4, 0x35, 0xec, + 0xbe, 0x77, 0xc6, 0xa6, 0x77, 0x07, 0x60, 0x71, 0xfa, 0xa9, 0xb1, 0xcd, 0xf4, 0xb4, 0x91, 0x45, + 0x56, 0xb1, 0xf4, 0x55, 0x52, 0x03, 0x3a, 0x2f, 0xaf, 0x29, 0xa7, 0x85, 0x6b, 0x35, 0xbe, 0xae, + 0x81, 0xb2, 0x0f, 0x09, 0x90, 0x6e, 0x17, 0xbb, 0xe9, 0xe3, 0x2e, 0x26, 0xfb, 0xe0, 0xf4, 0x8a, + 0x09, 0x1a, 0x72, 0xce, 0xb8, 0x32, 0xd3, 0x26, 0x77, 0x21, 0xfc, 0x04, 0xec, 0x95, 0x5e, 0xd2, + 0x94, 0x69, 0x74, 0x12, 0xa7, 0x51, 0xe0, 0x6c, 0x49, 0x53, 0xe2, 0x34, 0x69, 0x22, 0x0d, 0xbb, + 0xf0, 0xf0, 0x1d, 0xab, 0x4a, 0xc1, 0x78, 0xab, 0x76, 0xdd, 0xca, 0x8d, 0x7f, 0x6a, 0xb0, 0xd3, + 0x62, 0xe1, 0x35, 0xad, 0x04, 0x7a, 0x06, 0xba, 0xb8, 0x59, 0xd0, 0xd6, 0xa7, 0xc3, 0x0d, 0x9f, + 0x14, 0xcb, 0x4f, 0x6e, 0x16, 0x94, 0x28, 0x22, 0x7a, 0x0a, 0x66, 0xfb, 0xac, 0x94, 0x37, 0x83, + 0xf1, 0xde, 0x46, 0xcd, 0xeb, 0x2d, 0xd2, 0x71, 0xd0, 0xcb, 0xdb, 0x81, 0xee, 0xff, 0x7d, 0xa0, + 0x65, 0x55, 0x4b, 0xc5, 0xaf, 0x40, 0x97, 0x47, 0x22, 0x0b, 0xf4, 0x28, 0x9d, 0xcd, 0x9a, 0x0b, + 0x9e, 0xc5, 0x67, 0xe9, 0xec, 0x38, 0x91, 0xc3, 0x69, 0x42, 0xff, 0x38, 0x08, 0x9c, 0x9e, 0x9c, + 0xd2, 0xf4, 0x2c, 0x90, 0x60, 0x5f, 0xae, 0x83, 0x70, 0x16, 0x26, 0xa1, 0xa3, 0x9f, 0xd8, 0x60, + 0xd6, 0xcb, 0x0b, 0x29, 0x2c, 0xde, 0x83, 0x07, 0xc7, 0x45, 0xb1, 0x3a, 0x6b, 0x31, 0xbf, 0xc1, + 0x47, 0xb0, 0x1f, 0xd0, 0x39, 0x15, 0xf4, 0xde, 0xe4, 0xfe, 0xf3, 0x5f, 0x80, 0xf7, 0x01, 0xdd, + 0xdb, 0x41, 0xee, 0x7b, 0x08, 0x8f, 0x1a, 0x37, 0xa7, 0xd5, 0x05, 0x5b, 0x56, 0x45, 0xf7, 0x54, + 0x65, 0xf2, 0xc2, 0x50, 0xbf, 0xd0, 0x8b, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x13, 0x18, 0x1c, + 0x90, 0x96, 0x04, 0x00, 0x00, } diff --git a/rpc/contact.proto b/rpc/contact.proto index 950f545..eeb3f37 100644 --- a/rpc/contact.proto +++ b/rpc/contact.proto @@ -34,6 +34,9 @@ message ContactRequest { string fromNickname = 5; string whenCreated = 6; bool rejected = 7; + string whenDelivered = 8; + string whenRejected = 9; + string remoteError = 10; } message MonitorContactsRequest { diff --git a/rpc/rpc.go b/rpc/rpc.go index 66f0181..5b4b645 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -1,3 +1,3 @@ package ricochet -//go:generate protoc --go_out=plugins=grpc:. contact.proto conversation.proto core.proto identity.proto network.proto +//go:generate protoc --go_out=plugins=grpc:. contact.proto conversation.proto core.proto identity.proto network.proto config.proto