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 (
|
||||
"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
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue