core: RPC and config for outbound contact requests
This commit is contained in:
parent
2fd3cd2ea0
commit
8baf1034f6
|
@ -120,7 +120,17 @@ func (s *RpcServer) MonitorContacts(req *rpc.MonitorContactsRequest, stream rpc.
|
|||
}
|
||||
|
||||
func (s *RpcServer) AddContactRequest(ctx context.Context, req *rpc.ContactRequest) (*rpc.Contact, error) {
|
||||
return nil, NotImplementedError
|
||||
contactList := s.core.Identity.ContactList()
|
||||
if req.Direction != rpc.ContactRequest_OUTBOUND {
|
||||
return nil, errors.New("Request must be outbound")
|
||||
}
|
||||
|
||||
contact, err := contactList.AddContactRequest(req.Address, req.Nickname, req.FromNickname, req.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return contact.Data(), nil
|
||||
}
|
||||
|
||||
func (s *RpcServer) UpdateContact(ctx context.Context, req *rpc.Contact) (*rpc.Contact, error) {
|
||||
|
|
|
@ -9,6 +9,10 @@ import (
|
|||
"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 {
|
||||
path string
|
||||
filePath string
|
||||
|
@ -28,15 +32,24 @@ type ConfigRoot struct {
|
|||
|
||||
type ConfigContact struct {
|
||||
Hostname string
|
||||
LastConnected 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
|
||||
HostnameBlacklist []string
|
||||
ServiceKey string
|
||||
DataDirectory string `json:",omitempty"`
|
||||
ServiceKey string
|
||||
}
|
||||
|
||||
func LoadConfig(configPath string) (*Config, error) {
|
||||
|
|
|
@ -2,9 +2,9 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
protocol "github.com/s-rah/go-ricochet"
|
||||
"github.com/ricochet-im/ricochet-go/core/utils"
|
||||
"github.com/ricochet-im/ricochet-go/rpc"
|
||||
protocol "github.com/s-rah/go-ricochet"
|
||||
"golang.org/x/net/context"
|
||||
"log"
|
||||
"strconv"
|
||||
|
@ -16,6 +16,9 @@ import (
|
|||
// 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
|
||||
|
||||
|
@ -49,9 +52,20 @@ func ContactFromConfig(core *Ricochet, id int, data ConfigContact, events *utils
|
|||
return nil, fmt.Errorf("Invalid contact hostname '%s", data.Hostname)
|
||||
}
|
||||
|
||||
// XXX Should have some global trigger that starts all contact connections
|
||||
// at the right time
|
||||
go contact.contactConnection()
|
||||
if data.Request.Pending {
|
||||
if data.Request.WhenRejected != "" {
|
||||
contact.status = ricochet.Contact_REJECTED
|
||||
} else {
|
||||
contact.status = ricochet.Contact_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
// XXX Ugly and fragile way to inhibit connections
|
||||
if contact.status != ricochet.Contact_REJECTED {
|
||||
// XXX Should have some global trigger that starts all contact connections
|
||||
// at the right time
|
||||
go contact.contactConnection()
|
||||
}
|
||||
|
||||
return contact, nil
|
||||
}
|
||||
|
@ -109,6 +123,15 @@ func (c *Contact) Data() *ricochet.Contact {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -298,10 +321,24 @@ func (c *Contact) setConnection(conn *protocol.OpenConnection) error {
|
|||
// XXX implement this
|
||||
|
||||
c.connection = conn
|
||||
c.status = ricochet.Contact_ONLINE
|
||||
log.Printf("Assigned connection %v to contact %v", c.connection, c)
|
||||
|
||||
// XXX implicit accept contact requests
|
||||
if c.data.Request.Pending {
|
||||
if conn.Client {
|
||||
// XXX Need to check knownContact flag in authentication and implicit accept also
|
||||
// Outbound connection for contact request; send request message
|
||||
// XXX hardcoded channel ID
|
||||
log.Printf("Sending outbound contact request to %v", c)
|
||||
conn.SendContactRequest(5, c.data.Request.MyNickname, c.data.Request.Message)
|
||||
} else {
|
||||
// Inbound connection for contact request; implicitly accept request
|
||||
// and continue as contact
|
||||
log.Printf("Contact request implicitly accepted by incoming connection for contact %v", c)
|
||||
c.requestAccepted()
|
||||
}
|
||||
} else {
|
||||
c.status = ricochet.Contact_ONLINE
|
||||
}
|
||||
|
||||
// Update LastConnected time
|
||||
config := c.core.Config.OpenWrite()
|
||||
|
@ -391,6 +428,20 @@ func (c *Contact) shouldReplaceConnection(conn *protocol.OpenConnection) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Assumes mutex is held, and assumes the caller will send the UPDATE event
|
||||
func (c *Contact) requestAccepted() {
|
||||
config := c.core.Config.OpenWrite()
|
||||
c.data.Request = ConfigContactRequest{}
|
||||
config.Contacts[strconv.Itoa(c.id)] = c.data
|
||||
config.Save()
|
||||
|
||||
if c.connection != nil {
|
||||
c.status = ricochet.Contact_ONLINE
|
||||
} else {
|
||||
c.status = ricochet.Contact_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
// XXX also will go away during protocol API rework
|
||||
func (c *Contact) OnConnectionAuthenticated(conn *protocol.OpenConnection) {
|
||||
c.connChannel <- conn
|
||||
|
|
|
@ -7,9 +7,12 @@ import (
|
|||
"github.com/ricochet-im/ricochet-go/rpc"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ContactList struct {
|
||||
core *Ricochet
|
||||
|
||||
mutex sync.RWMutex
|
||||
events *utils.Publisher
|
||||
|
||||
|
@ -18,6 +21,7 @@ type ContactList struct {
|
|||
|
||||
func LoadContactList(core *Ricochet) (*ContactList, error) {
|
||||
list := &ContactList{
|
||||
core: core,
|
||||
events: utils.CreatePublisher(),
|
||||
}
|
||||
|
||||
|
@ -76,8 +80,71 @@ func (this *ContactList) ContactByAddress(address string) *Contact {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (this *ContactList) AddContact(address string, name string) (*Contact, error) {
|
||||
return nil, errors.New("Not implemented")
|
||||
func (this *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
|
||||
// XXX check that address is valid before relying on format below
|
||||
// XXX validity checks on name/text also useful
|
||||
|
||||
for _, contact := range this.contacts {
|
||||
if contact.Address() == address {
|
||||
return nil, errors.New("Contact already exists with this address")
|
||||
}
|
||||
if contact.Nickname() == name {
|
||||
return nil, errors.New("Contact already exists with this nickname")
|
||||
}
|
||||
}
|
||||
|
||||
// XXX check inbound requests
|
||||
|
||||
// Write new contact into config
|
||||
config := this.core.Config.OpenWrite()
|
||||
|
||||
maxContactId := 0
|
||||
for idstr, _ := range config.Contacts {
|
||||
if id, err := strconv.Atoi(idstr); err == nil {
|
||||
if maxContactId < id {
|
||||
maxContactId = id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contactId := maxContactId + 1
|
||||
configContact := ConfigContact{
|
||||
Hostname: address[9:] + ".onion",
|
||||
Nickname: name,
|
||||
WhenCreated: time.Now().Format(time.RFC3339),
|
||||
Request: ConfigContactRequest{
|
||||
Pending: true,
|
||||
MyNickname: fromName,
|
||||
Message: text,
|
||||
},
|
||||
}
|
||||
|
||||
config.Contacts[strconv.Itoa(contactId)] = configContact
|
||||
if err := config.Save(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create Contact
|
||||
// XXX This starts connection immediately, which could cause contact update
|
||||
// events before the add event in an unlikely race case
|
||||
contact, err := ContactFromConfig(this.core, contactId, configContact, this.events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
this.contacts[contactId] = contact
|
||||
|
||||
event := ricochet.ContactEvent{
|
||||
Type: ricochet.ContactEvent_ADD,
|
||||
Subject: &ricochet.ContactEvent_Contact{
|
||||
Contact: contact.Data(),
|
||||
},
|
||||
}
|
||||
this.events.Publish(event)
|
||||
|
||||
return contact, nil
|
||||
}
|
||||
|
||||
func (this *ContactList) RemoveContact(contact *Contact) error {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
protocol "github.com/s-rah/go-ricochet"
|
||||
"github.com/ricochet-im/ricochet-go/core/utils"
|
||||
protocol "github.com/s-rah/go-ricochet"
|
||||
"github.com/yawning/bulb/utils/pkcs1"
|
||||
"log"
|
||||
"sync"
|
||||
|
|
Loading…
Reference in New Issue