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