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.
This commit is contained in:
parent
914163c7de
commit
32230b77c1
177
core/config.go
177
core/config.go
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
111
core/contact.go
111
core/contact.go
|
@ -2,6 +2,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/ricochet-im/ricochet-go/core/utils"
|
"github.com/ricochet-im/ricochet-go/core/utils"
|
||||||
"github.com/ricochet-im/ricochet-go/rpc"
|
"github.com/ricochet-im/ricochet-go/rpc"
|
||||||
protocol "github.com/s-rah/go-ricochet"
|
protocol "github.com/s-rah/go-ricochet"
|
||||||
|
@ -14,18 +15,11 @@ import (
|
||||||
"time"
|
"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 {
|
type Contact struct {
|
||||||
core *Ricochet
|
core *Ricochet
|
||||||
|
|
||||||
id int
|
id int
|
||||||
data ConfigContact
|
data *ricochet.Contact
|
||||||
status ricochet.Contact_Status
|
|
||||||
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
events *utils.Publisher
|
events *utils.Publisher
|
||||||
|
@ -41,7 +35,7 @@ type Contact struct {
|
||||||
conversation *Conversation
|
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{
|
contact := &Contact{
|
||||||
core: core,
|
core: core,
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -53,16 +47,18 @@ func ContactFromConfig(core *Ricochet, id int, data ConfigContact, events *utils
|
||||||
|
|
||||||
if id < 0 {
|
if id < 0 {
|
||||||
return nil, fmt.Errorf("Invalid contact ID '%d'", id)
|
return nil, fmt.Errorf("Invalid contact ID '%d'", id)
|
||||||
} else if !IsOnionValid(data.Hostname) {
|
} else if !IsAddressValid(data.Address) {
|
||||||
return nil, fmt.Errorf("Invalid contact hostname '%s", data.Hostname)
|
return nil, fmt.Errorf("Invalid contact address '%s", data.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Request.Pending {
|
if data.Request != nil {
|
||||||
if data.Request.WhenRejected != "" {
|
if data.Request.Rejected {
|
||||||
contact.status = ricochet.Contact_REJECTED
|
contact.data.Status = ricochet.Contact_REJECTED
|
||||||
} else {
|
} 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
|
return contact, nil
|
||||||
|
@ -81,14 +77,14 @@ func (c *Contact) Nickname() string {
|
||||||
func (c *Contact) Address() string {
|
func (c *Contact) Address() string {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
address, _ := AddressFromOnion(c.data.Hostname)
|
return c.data.Address
|
||||||
return address
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) Hostname() string {
|
func (c *Contact) Hostname() string {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
return c.data.Hostname
|
hostname, _ := OnionFromAddress(c.data.Address)
|
||||||
|
return hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) LastConnected() time.Time {
|
func (c *Contact) LastConnected() time.Time {
|
||||||
|
@ -108,47 +104,28 @@ func (c *Contact) WhenCreated() time.Time {
|
||||||
func (c *Contact) Status() ricochet.Contact_Status {
|
func (c *Contact) Status() ricochet.Contact_Status {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
return c.status
|
return c.data.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) Data() *ricochet.Contact {
|
func (c *Contact) Data() *ricochet.Contact {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
address, _ := AddressFromOnion(c.data.Hostname)
|
return proto.Clone(c.data).(*ricochet.Contact)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) IsRequest() bool {
|
func (c *Contact) IsRequest() bool {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
return c.data.Request.Pending
|
return c.data.Request != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) Conversation() *Conversation {
|
func (c *Contact) Conversation() *Conversation {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
if c.conversation == nil {
|
if c.conversation == nil {
|
||||||
address, _ := AddressFromOnion(c.data.Hostname)
|
|
||||||
entity := &ricochet.Entity{
|
entity := &ricochet.Entity{
|
||||||
ContactId: int32(c.id),
|
ContactId: int32(c.id),
|
||||||
Address: address,
|
Address: c.data.Address,
|
||||||
}
|
}
|
||||||
c.conversation = NewConversation(c, entity, c.core.Identity.ConversationStream)
|
c.conversation = NewConversation(c, entity, c.core.Identity.ConversationStream)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +164,7 @@ func (c *Contact) shouldMakeOutboundConnections() bool {
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
// Don't make connections to contacts in the REJECTED state
|
// 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +240,7 @@ func (c *Contact) contactConnection() {
|
||||||
// already closed. If there was an existing connection and this returns nil,
|
// already closed. If there was an existing connection and this returns nil,
|
||||||
// the old connection is closed but c.connection has not been reset.
|
// the old connection is closed but c.connection has not been reset.
|
||||||
if err := c.considerUsingConnection(conn); err != nil {
|
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)
|
go closeUnhandledConnection(conn)
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
continue
|
continue
|
||||||
|
@ -335,8 +312,8 @@ func (c *Contact) connectOutbound(ctx context.Context, connChannel chan *connect
|
||||||
Network: c.core.Network,
|
Network: c.core.Network,
|
||||||
NeverGiveUp: true,
|
NeverGiveUp: true,
|
||||||
}
|
}
|
||||||
hostname := c.data.Hostname
|
hostname, _ := OnionFromAddress(c.data.Address)
|
||||||
isRequest := c.data.Request.Pending
|
isRequest := c.data.Request != nil
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -446,8 +423,8 @@ func (c *Contact) sendContactRequest(conn *connection.Connection, ctx context.Co
|
||||||
_, err := conn.RequestOpenChannel("im.ricochet.contact.request",
|
_, err := conn.RequestOpenChannel("im.ricochet.contact.request",
|
||||||
&channels.ContactRequestChannel{
|
&channels.ContactRequestChannel{
|
||||||
Handler: &requestChannelHandler{Response: responseChan},
|
Handler: &requestChannelHandler{Response: responseChan},
|
||||||
Name: c.data.Request.MyNickname, // XXX mutex
|
Name: c.data.Request.FromNickname, // XXX mutex
|
||||||
Message: c.data.Request.Message,
|
Message: c.data.Request.Text,
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
@ -502,9 +479,9 @@ func (c *Contact) considerUsingConnection(conn *connection.Connection) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if conn.IsInbound {
|
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 {
|
} 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 {
|
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)
|
return fmt.Errorf("Connection %v is not authenticated", conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.data.Hostname[0:16] != conn.RemoteHostname {
|
plainHost, _ := PlainHostFromAddress(c.data.Address)
|
||||||
return fmt.Errorf("Connection hostname %s doesn't match contact hostname %s when assigning connection", conn.RemoteHostname, c.data.Hostname[0:16])
|
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) {
|
if c.connection != nil && !c.shouldReplaceConnection(conn) {
|
||||||
|
@ -539,28 +517,29 @@ func (c *Contact) considerUsingConnection(conn *connection.Connection) error {
|
||||||
// Assumes c.mutex is held.
|
// Assumes c.mutex is held.
|
||||||
func (c *Contact) onConnectionStateChanged() {
|
func (c *Contact) onConnectionStateChanged() {
|
||||||
if c.connection != nil {
|
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
|
// Inbound connection implicitly accepts the contact request and can continue as a contact
|
||||||
// Outbound request logic is all handled by connectOutbound.
|
// Outbound request logic is all handled by connectOutbound.
|
||||||
log.Printf("Contact request implicitly accepted by contact %v", c)
|
log.Printf("Contact request implicitly accepted by contact %v", c)
|
||||||
c.updateContactRequest("Accepted")
|
c.updateContactRequest("Accepted")
|
||||||
} else {
|
} else {
|
||||||
c.status = ricochet.Contact_ONLINE
|
c.data.Status = ricochet.Contact_ONLINE
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if c.status == ricochet.Contact_ONLINE {
|
if c.data.Status == ricochet.Contact_ONLINE {
|
||||||
c.status = ricochet.Contact_OFFLINE
|
c.data.Status = ricochet.Contact_OFFLINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update LastConnected time
|
// Update LastConnected time
|
||||||
c.timeConnected = time.Now()
|
c.timeConnected = time.Now()
|
||||||
|
|
||||||
config := c.core.Config.OpenWrite()
|
|
||||||
c.data.LastConnected = c.timeConnected.Format(time.RFC3339)
|
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
|
// _really_ assumes c.mutex was held
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
event := ricochet.ContactEvent{
|
event := ricochet.ContactEvent{
|
||||||
|
@ -615,7 +594,7 @@ func (c *Contact) UpdateContactRequest(status string) bool {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
if !c.data.Request.Pending {
|
if c.data.Request == nil {
|
||||||
return false
|
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
|
// Same as above, but assumes the mutex is already held and that the caller
|
||||||
// will send an UPDATE event
|
// will send an UPDATE event
|
||||||
func (c *Contact) updateContactRequest(status string) bool {
|
func (c *Contact) updateContactRequest(status string) bool {
|
||||||
config := c.core.Config.OpenWrite()
|
|
||||||
now := time.Now().Format(time.RFC3339)
|
now := time.Now().Format(time.RFC3339)
|
||||||
// Whether to keep the channel open
|
// Whether to keep the channel open
|
||||||
var re bool
|
var re bool
|
||||||
|
@ -646,11 +624,11 @@ func (c *Contact) updateContactRequest(status string) bool {
|
||||||
re = true
|
re = true
|
||||||
|
|
||||||
case "Accepted":
|
case "Accepted":
|
||||||
c.data.Request = ConfigContactRequest{}
|
c.data.Request = nil
|
||||||
if c.connection != nil {
|
if c.connection != nil {
|
||||||
c.status = ricochet.Contact_ONLINE
|
c.data.Status = ricochet.Contact_ONLINE
|
||||||
} else {
|
} else {
|
||||||
c.status = ricochet.Contact_UNKNOWN
|
c.data.Status = ricochet.Contact_UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
case "Rejected":
|
case "Rejected":
|
||||||
|
@ -664,8 +642,9 @@ func (c *Contact) updateContactRequest(status string) bool {
|
||||||
log.Printf("Unknown contact request status '%s'", status)
|
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.Contacts[strconv.Itoa(c.id)] = c.data
|
||||||
config.Save()
|
|
||||||
return re
|
return re
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,7 @@ func LoadContactList(core *Ricochet) (*ContactList, error) {
|
||||||
inboundRequests: make(map[string]*InboundContactRequest),
|
inboundRequests: make(map[string]*InboundContactRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
config := core.Config.OpenRead()
|
config := core.Config.Read()
|
||||||
defer config.Close()
|
|
||||||
|
|
||||||
list.contacts = make(map[int]*Contact, len(config.Contacts))
|
list.contacts = make(map[int]*Contact, len(config.Contacts))
|
||||||
for idStr, data := range config.Contacts {
|
for idStr, data := range config.Contacts {
|
||||||
id, err := strconv.Atoi(idStr)
|
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
|
// Generally, you will use AddContactRequest (for outbound requests) and
|
||||||
// AddOrUpdateInboundContactRequest plus InboundContactRequest.Accept() instead of
|
// AddOrUpdateInboundContactRequest plus InboundContactRequest.Accept() instead of
|
||||||
// using this function directly.
|
// using this function directly.
|
||||||
func (this *ContactList) AddNewContact(configContact ConfigContact) (*Contact, error) {
|
func (this *ContactList) AddNewContact(data *ricochet.Contact) (*Contact, error) {
|
||||||
this.mutex.Lock()
|
this.mutex.Lock()
|
||||||
defer this.mutex.Unlock()
|
defer this.mutex.Unlock()
|
||||||
|
|
||||||
address, ok := AddressFromOnion(configContact.Hostname)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("Invalid ricochet address")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, contact := range this.contacts {
|
for _, contact := range this.contacts {
|
||||||
if contact.Address() == address {
|
if contact.Address() == data.Address {
|
||||||
return nil, errors.New("Contact already exists with this 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")
|
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)
|
// XXX check inbound requests (but this can be called for an inbound req too)
|
||||||
|
|
||||||
// Write new contact into config
|
// Write new contact into config
|
||||||
config := this.core.Config.OpenWrite()
|
config := this.core.Config.Lock()
|
||||||
|
|
||||||
maxContactId := 0
|
maxContactId := 0
|
||||||
for idstr, _ := range config.Contacts {
|
for idstr, _ := range config.Contacts {
|
||||||
|
@ -132,13 +125,15 @@ func (this *ContactList) AddNewContact(configContact ConfigContact) (*Contact, e
|
||||||
}
|
}
|
||||||
|
|
||||||
contactId := maxContactId + 1
|
contactId := maxContactId + 1
|
||||||
config.Contacts[strconv.Itoa(contactId)] = configContact
|
|
||||||
if err := config.Save(); err != nil {
|
if config.Contacts == nil {
|
||||||
return nil, err
|
config.Contacts = make(map[string]*ricochet.Contact)
|
||||||
}
|
}
|
||||||
|
config.Contacts[strconv.Itoa(contactId)] = data
|
||||||
|
this.core.Config.Unlock()
|
||||||
|
|
||||||
// Create Contact
|
// Create Contact
|
||||||
contact, err := ContactFromConfig(this.core, contactId, configContact, this.events)
|
contact, err := ContactFromConfig(this.core, contactId, data, this.events)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// If an inbound request already exists for this address, that request will be automatically
|
||||||
// accepted, and the returned contact will already be fully established.
|
// accepted, and the returned contact will already be fully established.
|
||||||
func (cl *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) {
|
func (cl *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) {
|
||||||
onion, valid := OnionFromAddress(address)
|
if !IsAddressValid(address) {
|
||||||
if !valid {
|
|
||||||
return nil, errors.New("Invalid ricochet address")
|
return nil, errors.New("Invalid ricochet address")
|
||||||
}
|
}
|
||||||
if !IsNicknameAcceptable(name) {
|
if !IsNicknameAcceptable(name) {
|
||||||
|
@ -177,17 +171,20 @@ func (cl *ContactList) AddContactRequest(address, name, fromName, text string) (
|
||||||
return nil, errors.New("Invalid message")
|
return nil, errors.New("Invalid message")
|
||||||
}
|
}
|
||||||
|
|
||||||
configContact := ConfigContact{
|
data := &ricochet.Contact{
|
||||||
Hostname: onion,
|
Address: address,
|
||||||
Nickname: name,
|
Nickname: name,
|
||||||
WhenCreated: time.Now().Format(time.RFC3339),
|
WhenCreated: time.Now().Format(time.RFC3339),
|
||||||
Request: ConfigContactRequest{
|
Request: &ricochet.ContactRequest{
|
||||||
Pending: true,
|
Direction: ricochet.ContactRequest_OUTBOUND,
|
||||||
MyNickname: fromName,
|
Address: address,
|
||||||
Message: text,
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -213,11 +210,9 @@ func (this *ContactList) RemoveContact(contact *Contact) error {
|
||||||
// leaves a goroutine up among other things.
|
// leaves a goroutine up among other things.
|
||||||
contact.StopConnection()
|
contact.StopConnection()
|
||||||
|
|
||||||
config := this.core.Config.OpenWrite()
|
config := this.core.Config.Lock()
|
||||||
delete(config.Contacts, strconv.Itoa(contact.Id()))
|
delete(config.Contacts, strconv.Itoa(contact.Id()))
|
||||||
if err := config.Save(); err != nil {
|
this.core.Config.Unlock()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(this.contacts, contact.Id())
|
delete(this.contacts, contact.Id())
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/ricochet-im/ricochet-go/core/utils"
|
"github.com/ricochet-im/ricochet-go/core/utils"
|
||||||
|
"github.com/ricochet-im/ricochet-go/rpc"
|
||||||
protocol "github.com/s-rah/go-ricochet"
|
protocol "github.com/s-rah/go-ricochet"
|
||||||
connection "github.com/s-rah/go-ricochet/connection"
|
connection "github.com/s-rah/go-ricochet/connection"
|
||||||
"github.com/yawning/bulb/utils/pkcs1"
|
"github.com/yawning/bulb/utils/pkcs1"
|
||||||
|
@ -51,15 +51,10 @@ func CreateIdentity(core *Ricochet) (*Identity, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *Identity) loadIdentity() error {
|
func (me *Identity) loadIdentity() error {
|
||||||
config := me.core.Config.OpenRead()
|
config := me.core.Config.Read()
|
||||||
defer config.Close()
|
|
||||||
|
|
||||||
if config.Identity.ServiceKey != "" {
|
|
||||||
keyData, err := base64.StdEncoding.DecodeString(config.Identity.ServiceKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if keyData := config.Secrets.GetServicePrivateKey(); keyData != nil {
|
||||||
|
var err error
|
||||||
me.privateKey, _, err = pkcs1.DecodePrivateKeyDER(keyData)
|
me.privateKey, _, err = pkcs1.DecodePrivateKeyDER(keyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -90,9 +85,12 @@ func (me *Identity) setPrivateKey(key *rsa.PrivateKey) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config := me.core.Config.OpenWrite()
|
config := me.core.Config.Lock()
|
||||||
config.Identity.ServiceKey = base64.StdEncoding.EncodeToString(keyData)
|
if config.Secrets == nil {
|
||||||
config.Save()
|
config.Secrets = &ricochet.Secrets{}
|
||||||
|
}
|
||||||
|
config.Secrets.ServicePrivateKey = keyData
|
||||||
|
me.core.Config.Unlock()
|
||||||
|
|
||||||
// Update Identity
|
// Update Identity
|
||||||
me.address, err = AddressFromKey(&key.PublicKey)
|
me.address, err = AddressFromKey(&key.PublicKey)
|
||||||
|
|
|
@ -266,13 +266,12 @@ func (cr *InboundContactRequest) Accept() (*Contact, error) {
|
||||||
|
|
||||||
log.Printf("Accepting contact request from %s", cr.data.Address)
|
log.Printf("Accepting contact request from %s", cr.data.Address)
|
||||||
|
|
||||||
onion, _ := OnionFromAddress(cr.data.Address)
|
data := &ricochet.Contact{
|
||||||
configContact := ConfigContact{
|
Address: cr.data.Address,
|
||||||
Hostname: onion,
|
|
||||||
Nickname: cr.data.FromNickname,
|
Nickname: cr.data.FromNickname,
|
||||||
WhenCreated: cr.data.WhenCreated,
|
WhenCreated: cr.data.WhenCreated,
|
||||||
}
|
}
|
||||||
contact, err := cr.core.Identity.ContactList().AddNewContact(configContact)
|
contact, err := cr.core.Identity.ContactList().AddNewContact(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error occurred in accepting contact request: %s", err)
|
log.Printf("Error occurred in accepting contact request: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -2,6 +2,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
cryptorand "crypto/rand"
|
cryptorand "crypto/rand"
|
||||||
|
"github.com/ricochet-im/ricochet-go/core/config"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -11,12 +12,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ricochet struct {
|
type Ricochet struct {
|
||||||
Config *Config
|
Config *config.ConfigFile
|
||||||
Network *Network
|
Network *Network
|
||||||
Identity *Identity
|
Identity *Identity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (core *Ricochet) Init(conf *Config) (err error) {
|
func (core *Ricochet) Init(conf *config.ConfigFile) (err error) {
|
||||||
initRand()
|
initRand()
|
||||||
|
|
||||||
core.Config = conf
|
core.Config = conf
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
ricochet "github.com/ricochet-im/ricochet-go/core"
|
ricochet "github.com/ricochet-im/ricochet-go/core"
|
||||||
|
"github.com/ricochet-im/ricochet-go/core/config"
|
||||||
rpc "github.com/ricochet-im/ricochet-go/rpc"
|
rpc "github.com/ricochet-im/ricochet-go/rpc"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"log"
|
"log"
|
||||||
|
@ -175,13 +176,16 @@ func checkBackendAddressSafety(address string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startBackend() 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
core := new(ricochet.Ricochet)
|
core := new(ricochet.Ricochet)
|
||||||
if err := core.Init(config); err != nil {
|
if err := core.Init(cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
package ricochet;
|
||||||
|
|
||||||
|
import "contact.proto";
|
||||||
|
import "identity.proto";
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
Identity identity = 1;
|
||||||
|
map<string, Contact> contacts = 2;
|
||||||
|
Secrets secrets = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secrets are not transmitted to frontend RPC clients
|
||||||
|
message Secrets {
|
||||||
|
bytes servicePrivateKey = 1;
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ It is generated from these files:
|
||||||
core.proto
|
core.proto
|
||||||
identity.proto
|
identity.proto
|
||||||
network.proto
|
network.proto
|
||||||
|
config.proto
|
||||||
|
|
||||||
It has these top-level messages:
|
It has these top-level messages:
|
||||||
Contact
|
Contact
|
||||||
|
@ -38,6 +39,8 @@ It has these top-level messages:
|
||||||
NetworkStatus
|
NetworkStatus
|
||||||
StartNetworkRequest
|
StartNetworkRequest
|
||||||
StopNetworkRequest
|
StopNetworkRequest
|
||||||
|
Config
|
||||||
|
Secrets
|
||||||
*/
|
*/
|
||||||
package ricochet
|
package ricochet
|
||||||
|
|
||||||
|
@ -202,13 +205,16 @@ func (m *Contact) GetStatus() Contact_Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContactRequest struct {
|
type ContactRequest struct {
|
||||||
Direction ContactRequest_Direction `protobuf:"varint,1,opt,name=direction,enum=ricochet.ContactRequest_Direction" json:"direction,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"`
|
Address string `protobuf:"bytes,2,opt,name=address" json:"address,omitempty"`
|
||||||
Nickname string `protobuf:"bytes,3,opt,name=nickname" json:"nickname,omitempty"`
|
Nickname string `protobuf:"bytes,3,opt,name=nickname" json:"nickname,omitempty"`
|
||||||
Text string `protobuf:"bytes,4,opt,name=text" json:"text,omitempty"`
|
Text string `protobuf:"bytes,4,opt,name=text" json:"text,omitempty"`
|
||||||
FromNickname string `protobuf:"bytes,5,opt,name=fromNickname" json:"fromNickname,omitempty"`
|
FromNickname string `protobuf:"bytes,5,opt,name=fromNickname" json:"fromNickname,omitempty"`
|
||||||
WhenCreated string `protobuf:"bytes,6,opt,name=whenCreated" json:"whenCreated,omitempty"`
|
WhenCreated string `protobuf:"bytes,6,opt,name=whenCreated" json:"whenCreated,omitempty"`
|
||||||
Rejected bool `protobuf:"varint,7,opt,name=rejected" json:"rejected,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{} }
|
func (m *ContactRequest) Reset() { *m = ContactRequest{} }
|
||||||
|
@ -265,6 +271,27 @@ func (m *ContactRequest) GetRejected() bool {
|
||||||
return false
|
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 {
|
type MonitorContactsRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,39 +494,42 @@ func init() {
|
||||||
func init() { proto.RegisterFile("contact.proto", fileDescriptor0) }
|
func init() { proto.RegisterFile("contact.proto", fileDescriptor0) }
|
||||||
|
|
||||||
var fileDescriptor0 = []byte{
|
var fileDescriptor0 = []byte{
|
||||||
// 539 bytes of a gzipped FileDescriptorProto
|
// 581 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xd1, 0x72, 0x93, 0x40,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xdd, 0x6e, 0xd3, 0x30,
|
||||||
0x14, 0x2d, 0x84, 0x02, 0xb9, 0x69, 0x23, 0xdd, 0xe9, 0x38, 0xd8, 0xbe, 0x30, 0x3b, 0x8e, 0x93,
|
0x14, 0x5e, 0xda, 0x2c, 0x3f, 0xa7, 0x5b, 0xc9, 0xac, 0x09, 0x85, 0xed, 0x26, 0xb2, 0x10, 0xea,
|
||||||
0x17, 0xd1, 0x89, 0xbe, 0xdb, 0x34, 0xd0, 0x31, 0x1a, 0x21, 0x6e, 0x61, 0x7c, 0x26, 0xb0, 0x4e,
|
0x0d, 0x01, 0x15, 0xee, 0xd9, 0xd6, 0x64, 0xa2, 0x50, 0x92, 0xe1, 0x25, 0xe2, 0x3a, 0x4b, 0x8c,
|
||||||
0xd1, 0x74, 0x89, 0xb0, 0x51, 0xf3, 0x43, 0x7e, 0x8b, 0x9f, 0xe3, 0x27, 0x38, 0xbb, 0x40, 0x6a,
|
0x16, 0xe8, 0xe2, 0xe2, 0xb8, 0x83, 0xbd, 0x06, 0x4f, 0xc5, 0xe3, 0xf0, 0x08, 0xc8, 0x4e, 0xd2,
|
||||||
0x12, 0x75, 0x1c, 0xdf, 0xf6, 0x9e, 0x73, 0x2e, 0xbb, 0x7b, 0xee, 0x59, 0xe0, 0x38, 0x2d, 0x18,
|
0xad, 0x2b, 0x20, 0xc4, 0x9d, 0xcf, 0x77, 0xbe, 0x63, 0x1f, 0x7f, 0xdf, 0xb1, 0x61, 0x37, 0x67,
|
||||||
0x4f, 0x52, 0xee, 0x2e, 0xcb, 0x82, 0x17, 0xc8, 0x2c, 0xf3, 0xb4, 0x48, 0x6f, 0x28, 0xc7, 0xdf,
|
0x95, 0xc8, 0x72, 0xe1, 0x2f, 0x38, 0x13, 0x0c, 0x59, 0xbc, 0xcc, 0x59, 0x7e, 0x49, 0x05, 0xfe,
|
||||||
0x55, 0x30, 0xc6, 0x35, 0x87, 0xfa, 0xa0, 0xe6, 0x99, 0xad, 0x38, 0xca, 0xe0, 0x90, 0xa8, 0x79,
|
0xd1, 0x03, 0x73, 0xd2, 0xe4, 0xd0, 0x10, 0x7a, 0x65, 0xe1, 0x6a, 0x9e, 0x36, 0xda, 0x26, 0xbd,
|
||||||
0x86, 0x6c, 0x30, 0x92, 0x2c, 0x2b, 0x69, 0x55, 0xd9, 0xaa, 0xa3, 0x0c, 0xba, 0xa4, 0x2d, 0xd1,
|
0xb2, 0x40, 0x2e, 0x98, 0x59, 0x51, 0x70, 0x5a, 0xd7, 0x6e, 0xcf, 0xd3, 0x46, 0x36, 0xe9, 0x42,
|
||||||
0x19, 0x98, 0x2c, 0x4f, 0x3f, 0xb2, 0xe4, 0x96, 0xda, 0x1d, 0x49, 0x6d, 0x6a, 0xe4, 0x40, 0xef,
|
0x74, 0x00, 0x56, 0x55, 0xe6, 0x9f, 0xab, 0xec, 0x8a, 0xba, 0x7d, 0x95, 0x5a, 0xc5, 0xc8, 0x83,
|
||||||
0xcb, 0x0d, 0x65, 0xe3, 0x92, 0x26, 0x9c, 0x66, 0xb6, 0x26, 0xe9, 0x5f, 0x21, 0xf4, 0x10, 0x8e,
|
0xc1, 0xd7, 0x4b, 0x5a, 0x4d, 0x38, 0xcd, 0x04, 0x2d, 0x5c, 0x5d, 0xa5, 0xef, 0x42, 0xe8, 0x31,
|
||||||
0x17, 0x49, 0xc5, 0xc7, 0x05, 0x63, 0x34, 0x15, 0x9a, 0x43, 0xa9, 0xd9, 0x06, 0xd1, 0x10, 0x8c,
|
0xec, 0xce, 0xb3, 0x5a, 0x4c, 0x58, 0x55, 0xd1, 0x5c, 0x72, 0xb6, 0x15, 0x67, 0x1d, 0x44, 0x63,
|
||||||
0x92, 0x7e, 0x5a, 0xd1, 0x8a, 0xdb, 0xba, 0xa3, 0x0c, 0x7a, 0x43, 0xdb, 0x6d, 0x4f, 0xed, 0x36,
|
0x30, 0x39, 0xfd, 0xb2, 0xa4, 0xb5, 0x70, 0x0d, 0x4f, 0x1b, 0x0d, 0xc6, 0xae, 0xdf, 0x75, 0xed,
|
||||||
0x27, 0x26, 0x35, 0x4f, 0x5a, 0x21, 0x7a, 0x0a, 0x7a, 0xc5, 0x13, 0xbe, 0xaa, 0x6c, 0x70, 0x94,
|
0xb7, 0x1d, 0x93, 0x26, 0x4f, 0x3a, 0x22, 0x7a, 0x0e, 0x46, 0x2d, 0x32, 0xb1, 0xac, 0x5d, 0xf0,
|
||||||
0x41, 0xff, 0x37, 0x2d, 0xee, 0xb5, 0xe4, 0x49, 0xa3, 0xc3, 0x13, 0xd0, 0x6b, 0x04, 0xf5, 0xc0,
|
0xb4, 0xd1, 0xf0, 0x37, 0x25, 0xfe, 0xb9, 0xca, 0x93, 0x96, 0x87, 0xa7, 0x60, 0x34, 0x08, 0x1a,
|
||||||
0x88, 0x83, 0xd7, 0x41, 0xf8, 0x2e, 0xb0, 0x0e, 0x44, 0x11, 0x5e, 0x5d, 0x4d, 0x27, 0x81, 0x6f,
|
0x80, 0x99, 0x46, 0x6f, 0xa3, 0xf8, 0x43, 0xe4, 0x6c, 0xc9, 0x20, 0x3e, 0x3d, 0x9d, 0x4d, 0xa3,
|
||||||
0x29, 0x08, 0x40, 0x0f, 0x03, 0xb9, 0x56, 0x05, 0x41, 0xfc, 0xb7, 0xb1, 0x7f, 0x1d, 0x59, 0x1d,
|
0xd0, 0xd1, 0x10, 0x80, 0x11, 0x47, 0x6a, 0xdd, 0x93, 0x09, 0x12, 0xbe, 0x4f, 0xc3, 0xf3, 0xc4,
|
||||||
0x74, 0x04, 0x26, 0xf1, 0x5f, 0xf9, 0xe3, 0xc8, 0xf7, 0x2c, 0x0d, 0x7f, 0x53, 0xa1, 0xbf, 0x7d,
|
0xe9, 0xa3, 0x1d, 0xb0, 0x48, 0xf8, 0x26, 0x9c, 0x24, 0x61, 0xe0, 0xe8, 0xf8, 0x7b, 0x1f, 0x86,
|
||||||
0x30, 0x74, 0x01, 0xdd, 0x2c, 0x2f, 0x69, 0xca, 0xf3, 0x82, 0x49, 0x63, 0xfb, 0x43, 0xfc, 0xa7,
|
0xeb, 0x8d, 0xa1, 0x23, 0xb0, 0x8b, 0x92, 0xd3, 0x5c, 0x94, 0xac, 0x52, 0xc2, 0x0e, 0xc7, 0xf8,
|
||||||
0x5b, 0xb8, 0x5e, 0xab, 0x24, 0x77, 0x4d, 0xff, 0x39, 0x03, 0x04, 0x1a, 0xa7, 0x5f, 0x79, 0x63,
|
0x4f, 0xb7, 0xf0, 0x83, 0x8e, 0x49, 0x6e, 0x8b, 0xfe, 0xd3, 0x03, 0x04, 0xba, 0xa0, 0xdf, 0x44,
|
||||||
0xbe, 0x5c, 0x23, 0x0c, 0x47, 0xef, 0xcb, 0xe2, 0x36, 0x68, 0x7b, 0x6a, 0xd3, 0xb7, 0xb0, 0xdd,
|
0x2b, 0xbe, 0x5a, 0x23, 0x0c, 0x3b, 0x1f, 0x39, 0xbb, 0x8a, 0xba, 0x9a, 0x46, 0xf4, 0x35, 0xec,
|
||||||
0xd9, 0xe9, 0xfb, 0xb3, 0x3b, 0x03, 0xb3, 0xa4, 0x1f, 0xea, 0xb1, 0x19, 0x8e, 0x32, 0x30, 0xc9,
|
0xbe, 0x77, 0xc6, 0xa6, 0x77, 0x07, 0x60, 0x71, 0xfa, 0xa9, 0xb1, 0xcd, 0xf4, 0xb4, 0x91, 0x45,
|
||||||
0xa6, 0xc6, 0x8f, 0xa0, 0xbb, 0xb9, 0x83, 0x30, 0x6a, 0x12, 0x5c, 0x86, 0x71, 0xe0, 0x59, 0x07,
|
0x56, 0xb1, 0xf4, 0x55, 0x52, 0x03, 0x3a, 0x2f, 0xaf, 0x29, 0xa7, 0x85, 0x6b, 0x35, 0xbe, 0xae,
|
||||||
0xc2, 0xa8, 0x30, 0x8e, 0xea, 0x4a, 0xc1, 0x36, 0xdc, 0x7f, 0x53, 0xb0, 0x9c, 0x17, 0x65, 0xe3,
|
0x81, 0xb2, 0x0f, 0x09, 0x90, 0x6e, 0x17, 0xbb, 0xe9, 0xe3, 0x2e, 0x26, 0xfb, 0xe0, 0xf4, 0x8a,
|
||||||
0x40, 0xd5, 0x58, 0x80, 0x7f, 0x28, 0x70, 0xd4, 0x60, 0xfe, 0x67, 0xca, 0x38, 0x7a, 0x02, 0x1a,
|
0x09, 0x1a, 0x72, 0xce, 0xb8, 0x32, 0xd3, 0x26, 0x77, 0x21, 0xfc, 0x04, 0xec, 0x95, 0x5e, 0xd2,
|
||||||
0x5f, 0x2f, 0x69, 0xe3, 0xdd, 0xf9, 0x9e, 0x77, 0x52, 0xe5, 0x46, 0xeb, 0x25, 0x25, 0x52, 0x88,
|
0x94, 0x69, 0x74, 0x12, 0xa7, 0x51, 0xe0, 0x6c, 0x49, 0x53, 0xe2, 0x34, 0x69, 0x22, 0x0d, 0xbb,
|
||||||
0x1e, 0x83, 0xd1, 0x44, 0x5d, 0xfa, 0xd5, 0x1b, 0x9e, 0xec, 0xf5, 0xbc, 0x3c, 0x20, 0xad, 0x06,
|
0xf0, 0xf0, 0x1d, 0xab, 0x4a, 0xc1, 0x78, 0xab, 0x76, 0xdd, 0xca, 0x8d, 0x7f, 0x6a, 0xb0, 0xd3,
|
||||||
0x3d, 0xbf, 0x0b, 0x59, 0xe7, 0xef, 0x21, 0x13, 0x5d, 0x8d, 0x14, 0xbf, 0x00, 0x4d, 0x6c, 0x89,
|
0x62, 0xe1, 0x35, 0xad, 0x04, 0x7a, 0x06, 0xba, 0xb8, 0x59, 0xd0, 0xd6, 0xa7, 0xc3, 0x0d, 0x9f,
|
||||||
0x4c, 0xd0, 0x82, 0x78, 0x3a, 0xad, 0x2f, 0x38, 0x0b, 0x67, 0xf1, 0x74, 0x14, 0x89, 0xc0, 0x18,
|
0x14, 0xcb, 0x4f, 0x6e, 0x16, 0x94, 0x28, 0x22, 0x7a, 0x0a, 0x66, 0xfb, 0xac, 0x94, 0x37, 0x83,
|
||||||
0xd0, 0x19, 0x79, 0x9e, 0xa5, 0x8a, 0xe4, 0xc4, 0x33, 0x4f, 0x80, 0x1d, 0xb1, 0xf6, 0xfc, 0xa9,
|
0xf1, 0xde, 0x46, 0xcd, 0xeb, 0x2d, 0xd2, 0x71, 0xd0, 0xcb, 0xdb, 0x81, 0xee, 0xff, 0x7d, 0xa0,
|
||||||
0x1f, 0xf9, 0x96, 0x76, 0xd9, 0x05, 0xa3, 0x5a, 0xcd, 0x85, 0x6d, 0xf8, 0x04, 0xee, 0x8d, 0xb2,
|
0x65, 0x55, 0x4b, 0xc5, 0xaf, 0x40, 0x97, 0x47, 0x22, 0x0b, 0xf4, 0x28, 0x9d, 0xcd, 0x9a, 0x0b,
|
||||||
0x6c, 0xb3, 0xd7, 0x72, 0xb1, 0xc6, 0x17, 0x70, 0xea, 0xd1, 0x05, 0xe5, 0x74, 0x27, 0x4d, 0xff,
|
0x9e, 0xc5, 0x67, 0xe9, 0xec, 0x38, 0x91, 0xc3, 0x69, 0x42, 0xff, 0x38, 0x08, 0x9c, 0x9e, 0x9c,
|
||||||
0xfc, 0x3e, 0xf1, 0x29, 0xa0, 0x9d, 0x2f, 0x88, 0xef, 0x9e, 0xc3, 0x03, 0x22, 0x67, 0x35, 0x61,
|
0xd2, 0xf4, 0x2c, 0x90, 0x60, 0x5f, 0xae, 0x83, 0x70, 0x16, 0x26, 0xa1, 0xa3, 0x9f, 0xd8, 0x60,
|
||||||
0xf3, 0x62, 0xc5, 0xb2, 0xf6, 0xf9, 0x08, 0x72, 0xae, 0xcb, 0x3f, 0xc3, 0xb3, 0x9f, 0x01, 0x00,
|
0xd6, 0xcb, 0x0b, 0x29, 0x2c, 0xde, 0x83, 0x07, 0xc7, 0x45, 0xb1, 0x3a, 0x6b, 0x31, 0xbf, 0xc1,
|
||||||
0x00, 0xff, 0xff, 0xe5, 0x62, 0x19, 0x3f, 0x2a, 0x04, 0x00, 0x00,
|
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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,9 @@ message ContactRequest {
|
||||||
string fromNickname = 5;
|
string fromNickname = 5;
|
||||||
string whenCreated = 6;
|
string whenCreated = 6;
|
||||||
bool rejected = 7;
|
bool rejected = 7;
|
||||||
|
string whenDelivered = 8;
|
||||||
|
string whenRejected = 9;
|
||||||
|
string remoteError = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MonitorContactsRequest {
|
message MonitorContactsRequest {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package ricochet
|
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
|
||||||
|
|
Loading…
Reference in New Issue