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) {
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) {

View File

@ -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) {

View File

@ -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

View File

@ -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 {

View File

@ -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"