core: RPC and config for outbound contact requests

This commit is contained in:
John Brooks 2016-10-27 13:57:04 -06:00
parent 2fd3cd2ea0
commit 8baf1034f6
5 changed files with 155 additions and 14 deletions

View File

@ -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) { 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) { func (s *RpcServer) UpdateContact(ctx context.Context, req *rpc.Contact) (*rpc.Contact, error) {

View File

@ -9,6 +9,10 @@ import (
"sync" "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 { type Config struct {
path string path string
filePath string filePath string
@ -28,15 +32,24 @@ type ConfigRoot struct {
type ConfigContact struct { type ConfigContact struct {
Hostname string Hostname string
LastConnected string LastConnected string `json:",omitempty"`
Nickname string Nickname string
WhenCreated 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 { type ConfigIdentity struct {
DataDirectory string DataDirectory string `json:",omitempty"`
HostnameBlacklist []string ServiceKey string
ServiceKey string
} }
func LoadConfig(configPath string) (*Config, error) { func LoadConfig(configPath string) (*Config, error) {

View File

@ -2,9 +2,9 @@ package core
import ( import (
"fmt" "fmt"
protocol "github.com/s-rah/go-ricochet"
"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"
"golang.org/x/net/context" "golang.org/x/net/context"
"log" "log"
"strconv" "strconv"
@ -16,6 +16,9 @@ import (
// XXX There is generally a lot of duplication and boilerplate between // XXX There is generally a lot of duplication and boilerplate between
// Contact, ConfigContact, and rpc.Contact. This should be reduced somehow. // 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
@ -49,9 +52,20 @@ func ContactFromConfig(core *Ricochet, id int, data ConfigContact, events *utils
return nil, fmt.Errorf("Invalid contact hostname '%s", data.Hostname) return nil, fmt.Errorf("Invalid contact hostname '%s", data.Hostname)
} }
// XXX Should have some global trigger that starts all contact connections if data.Request.Pending {
// at the right time if data.Request.WhenRejected != "" {
go contact.contactConnection() 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 return contact, nil
} }
@ -109,6 +123,15 @@ func (c *Contact) Data() *ricochet.Contact {
LastConnected: c.data.LastConnected, LastConnected: c.data.LastConnected,
Status: c.status, 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 data
} }
@ -298,10 +321,24 @@ func (c *Contact) setConnection(conn *protocol.OpenConnection) error {
// XXX implement this // XXX implement this
c.connection = conn c.connection = conn
c.status = ricochet.Contact_ONLINE
log.Printf("Assigned connection %v to contact %v", c.connection, c) 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 // Update LastConnected time
config := c.core.Config.OpenWrite() config := c.core.Config.OpenWrite()
@ -391,6 +428,20 @@ func (c *Contact) shouldReplaceConnection(conn *protocol.OpenConnection) bool {
return false 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 // XXX also will go away during protocol API rework
func (c *Contact) OnConnectionAuthenticated(conn *protocol.OpenConnection) { func (c *Contact) OnConnectionAuthenticated(conn *protocol.OpenConnection) {
c.connChannel <- conn c.connChannel <- conn

View File

@ -7,9 +7,12 @@ import (
"github.com/ricochet-im/ricochet-go/rpc" "github.com/ricochet-im/ricochet-go/rpc"
"strconv" "strconv"
"sync" "sync"
"time"
) )
type ContactList struct { type ContactList struct {
core *Ricochet
mutex sync.RWMutex mutex sync.RWMutex
events *utils.Publisher events *utils.Publisher
@ -18,6 +21,7 @@ type ContactList struct {
func LoadContactList(core *Ricochet) (*ContactList, error) { func LoadContactList(core *Ricochet) (*ContactList, error) {
list := &ContactList{ list := &ContactList{
core: core,
events: utils.CreatePublisher(), events: utils.CreatePublisher(),
} }
@ -76,8 +80,71 @@ func (this *ContactList) ContactByAddress(address string) *Contact {
return nil return nil
} }
func (this *ContactList) AddContact(address string, name string) (*Contact, error) { func (this *ContactList) AddContactRequest(address, name, fromName, text string) (*Contact, error) {
return nil, errors.New("Not implemented") 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 { func (this *ContactList) RemoveContact(contact *Contact) error {

View File

@ -4,8 +4,8 @@ import (
"crypto/rsa" "crypto/rsa"
"encoding/base64" "encoding/base64"
"errors" "errors"
protocol "github.com/s-rah/go-ricochet"
"github.com/ricochet-im/ricochet-go/core/utils" "github.com/ricochet-im/ricochet-go/core/utils"
protocol "github.com/s-rah/go-ricochet"
"github.com/yawning/bulb/utils/pkcs1" "github.com/yawning/bulb/utils/pkcs1"
"log" "log"
"sync" "sync"