From 2c4a1c8b375256ad09ddddc86d123b42f11d6a4c Mon Sep 17 00:00:00 2001 From: John Brooks Date: Mon, 29 Aug 2016 20:46:41 -0600 Subject: [PATCH] core: Load contacts from config --- backend/rpc.go | 31 +++++++++++++++++++++ cli/cli.go | 17 ++++++++++++ core/contact.go | 65 ++++++++++++++++++++++++++++++++++++--------- core/contactlist.go | 30 ++++++++++++++++++++- core/identity.go | 42 ++++++++++++++++++++--------- rpc/core.proto | 5 ++++ 6 files changed, 165 insertions(+), 25 deletions(-) diff --git a/backend/rpc.go b/backend/rpc.go index eb525b0..0d6bf0c 100644 --- a/backend/rpc.go +++ b/backend/rpc.go @@ -6,6 +6,7 @@ import ( rpc "github.com/special/notricochet/rpc" "golang.org/x/net/context" "log" + "time" ) var NotImplementedError error = errors.New("Not implemented") @@ -92,6 +93,36 @@ func (core *RicochetCore) GetIdentity(ctx context.Context, req *rpc.IdentityRequ } func (core *RicochetCore) MonitorContacts(req *rpc.MonitorContactsRequest, stream rpc.RicochetCore_MonitorContactsServer) error { + // Populate + contacts := core.Identity().ContactList().Contacts() + for _, contact := range contacts { + data := &rpc.Contact{ + Id: int32(contact.Id()), + Address: contact.Address(), + Nickname: contact.Nickname(), + WhenCreated: contact.WhenCreated().Format(time.RFC3339), + LastConnected: contact.LastConnected().Format(time.RFC3339), + } + event := &rpc.ContactEvent{ + Type: rpc.ContactEvent_POPULATE, + Subject: &rpc.ContactEvent_Contact{ + Contact: data, + }, + } + if err := stream.Send(event); err != nil { + return err + } + } + // Terminate populate list with a null subject + { + event := &rpc.ContactEvent{ + Type: rpc.ContactEvent_POPULATE, + } + if err := stream.Send(event); err != nil { + return err + } + } + return NotImplementedError } diff --git a/cli/cli.go b/cli/cli.go index 4f9e0fc..8aa3de3 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -109,6 +109,23 @@ func main() { } else { log.Printf("network stopped: %v", status) } + case "contacts": + stream, err := c.Backend.MonitorContacts(context.Background(), &rpc.MonitorContactsRequest{}) + if err != nil { + log.Printf("contacts error: %v", err) + } else { + for { + event, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Printf("contacts error: %v", err) + break + } + log.Printf("contact event: %v", event) + } + } case "help": fallthrough default: diff --git a/core/contact.go b/core/contact.go index fc7b207..47d6631 100644 --- a/core/contact.go +++ b/core/contact.go @@ -1,18 +1,59 @@ package core -type ContactStatus int - -const ( - ContactOnline ContactStatus = iota - ContactOffline - ContactRequestPending - ContactRequestRejected - ContactOutdated +import ( + "fmt" + "strings" + "time" ) -type Contact struct { - InternalId int +// XXX There is generally a lot of duplication and boilerplate between +// Contact, ConfigContact, and rpc.Contact. This should be reduced somehow. - Name string - Address string +// XXX This is threadsafe only because it can't be modified right now. + +type Contact struct { + id int + + data ConfigContact +} + +func ContactFromConfig(id int, data ConfigContact) (*Contact, error) { + contact := &Contact{ + id: id, + data: data, + } + + if id < 0 { + return nil, fmt.Errorf("Invalid contact ID '%d'", id) + } else if len(data.Hostname) != 22 || !strings.HasSuffix(data.Hostname, ".onion") { + return nil, fmt.Errorf("Invalid contact hostname '%s", data.Hostname) + } + + return contact, nil +} + +func (c *Contact) Id() int { + return c.id +} + +func (c *Contact) Nickname() string { + return c.data.Nickname +} + +func (c *Contact) Address() string { + return "ricochet:" + c.data.Hostname[0:16] +} + +func (c *Contact) Hostname() string { + return c.data.Hostname +} + +func (c *Contact) LastConnected() time.Time { + time, _ := time.Parse(time.RFC3339, c.data.LastConnected) + return time +} + +func (c *Contact) WhenCreated() time.Time { + time, _ := time.Parse(time.RFC3339, c.data.WhenCreated) + return time } diff --git a/core/contactlist.go b/core/contactlist.go index f983c3a..bf67b0c 100644 --- a/core/contactlist.go +++ b/core/contactlist.go @@ -2,6 +2,8 @@ package core import ( "errors" + "fmt" + "strconv" ) type ContactList struct { @@ -10,6 +12,32 @@ type ContactList struct { inboundRequests map[int]*InboundContactRequest } +func LoadContactList(core Ricochet) (*ContactList, error) { + list := &ContactList{} + config := core.Config().OpenRead() + defer config.Close() + + list.contacts = make(map[int]*Contact, len(config.Contacts)) + for idStr, data := range config.Contacts { + id, err := strconv.Atoi(idStr) + if err != nil { + return nil, fmt.Errorf("Invalid contact id '%s'", idStr) + } + if _, exists := list.contacts[id]; exists { + return nil, fmt.Errorf("Duplicate contact id '%d'", id) + } + + contact, err := ContactFromConfig(id, data) + if err != nil { + return nil, err + } + list.contacts[id] = contact + } + + // XXX Requests aren't implemented + return list, nil +} + func (this *ContactList) Contacts() []*Contact { re := make([]*Contact, 0, len(this.contacts)) for _, contact := range this.contacts { @@ -40,7 +68,7 @@ func (this *ContactList) ContactById(id int) *Contact { func (this *ContactList) ContactByAddress(address string) *Contact { for _, contact := range this.contacts { - if contact.Address == address { + if contact.Address() == address { return contact } } diff --git a/core/identity.go b/core/identity.go index f22af5a..766c820 100644 --- a/core/identity.go +++ b/core/identity.go @@ -9,6 +9,8 @@ import ( ) type Identity struct { + core Ricochet + address string privateKey *rsa.PrivateKey @@ -16,35 +18,51 @@ type Identity struct { } func CreateIdentity(core Ricochet) (*Identity, error) { - me := &Identity{} + me := &Identity{ + core: core, + } - config := core.Config().OpenRead() + err := me.loadIdentity() + if err != nil { + log.Printf("Loading identity failed: %v", err) + return nil, err + } + + contactList, err := LoadContactList(core) + if err != nil { + log.Printf("Loading contact list failed: %v", err) + return nil, err + } + me.contactList = contactList + + return me, nil +} + +func (me *Identity) loadIdentity() error { + config := me.core.Config().OpenRead() defer config.Close() + if config.Identity.ServiceKey != "" { keyData, err := base64.StdEncoding.DecodeString(config.Identity.ServiceKey) if err != nil { - log.Printf("Decoding identity key failed: %v", err) - return nil, err + return err } me.privateKey, _, err = pkcs1.DecodePrivateKeyDER(keyData) if err != nil { - log.Printf("Decoding identity key failed: %v", err) - return nil, err + return err } me.address, err = pkcs1.OnionAddr(&me.privateKey.PublicKey) - if err != nil && me.address == "" { - err = errors.New("Cannot calculate onion address") - } if err != nil { - log.Printf("Decoding identify key failed: %v", err) - return nil, err + return err + } else if me.address == "" { + return errors.New("Invalid onion address") } me.address = "ricochet:" + me.address } - return me, nil + return nil } func (me *Identity) Address() string { diff --git a/rpc/core.proto b/rpc/core.proto index 8191bb5..628f41c 100644 --- a/rpc/core.proto +++ b/rpc/core.proto @@ -30,6 +30,11 @@ service RicochetCore { // XXX Service status rpc GetIdentity (IdentityRequest) returns (Identity); + // Query contacts and monitor for contact changes. The full contact list + // is sent in POPULATE events, terminated by a POPULATE event with no + // subject. Any new, removed, or modified contacts, including changes in + // the state of contacts, are sent as ADD, UPDATE, or DELETE events until + // the stream is closed. rpc MonitorContacts (MonitorContactsRequest) returns (stream ContactEvent); rpc AddContactRequest (ContactRequest) returns (Contact); rpc UpdateContact (Contact) returns (Contact);