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) {
|
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) {
|
||||||
|
|
|
@ -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,14 +32,23 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// XXX Should have some global trigger that starts all contact connections
|
||||||
// at the right time
|
// at the right time
|
||||||
go contact.contactConnection()
|
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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue