From a52de9078c6b907f6f2b65d89cdc83432a2ab5af Mon Sep 17 00:00:00 2001 From: John Brooks Date: Thu, 15 Sep 2016 18:32:58 -0600 Subject: [PATCH] core: Attach protocol connections to Contact --- core/contact.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++-- core/identity.go | 10 +++++ core/protocol.go | 21 ++++++++++- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/core/contact.go b/core/contact.go index 47d6631..7f37e62 100644 --- a/core/contact.go +++ b/core/contact.go @@ -2,19 +2,24 @@ package core import ( "fmt" + protocol "github.com/s-rah/go-ricochet" + "github.com/special/notricochet/rpc" + "log" "strings" + "sync" "time" ) // XXX There is generally a lot of duplication and boilerplate between // Contact, ConfigContact, and rpc.Contact. This should be reduced somehow. -// XXX This is threadsafe only because it can't be modified right now. - type Contact struct { - id int - + id int data ConfigContact + + mutex sync.Mutex + + connection *protocol.OpenConnection } func ContactFromConfig(id int, data ConfigContact) (*Contact, error) { @@ -37,23 +42,107 @@ func (c *Contact) Id() int { } func (c *Contact) Nickname() string { + c.mutex.Lock() + defer c.mutex.Unlock() return c.data.Nickname } func (c *Contact) Address() string { + c.mutex.Lock() + defer c.mutex.Unlock() return "ricochet:" + c.data.Hostname[0:16] } func (c *Contact) Hostname() string { + c.mutex.Lock() + defer c.mutex.Unlock() return c.data.Hostname } func (c *Contact) LastConnected() time.Time { + c.mutex.Lock() + defer c.mutex.Unlock() time, _ := time.Parse(time.RFC3339, c.data.LastConnected) return time } func (c *Contact) WhenCreated() time.Time { + c.mutex.Lock() + defer c.mutex.Unlock() time, _ := time.Parse(time.RFC3339, c.data.WhenCreated) return time } + +func (c *Contact) Status() ricochet.Contact_Status { + c.mutex.Lock() + defer c.mutex.Unlock() + if c.connection == nil { + return ricochet.Contact_UNKNOWN + } else { + return ricochet.Contact_ONLINE + } +} + +func (c *Contact) SetConnection(conn *protocol.OpenConnection) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if conn == c.connection { + return fmt.Errorf("Duplicate assignment of connection %v to contact %v", conn, c) + } + + if !conn.IsAuthed || conn.Closed { + return fmt.Errorf("Connection %v is not in a valid state to assign to contact %v", conn, c) + } + + if c.data.Hostname[0:16] != conn.OtherHostname { + return fmt.Errorf("Connection hostname %s doesn't match contact hostname %s when assigning connection", conn.OtherHostname, c.data.Hostname[0:16]) + } + + if c.connection != nil && c.connection.Closed { + log.Printf("Replacing dead connection %v for contact %v", c.connection, c) + c.connection = nil + } + + // Decide whether to replace an existing connection with this one + if c.connection != nil { + // If the existing connection is in the same direction, always use the new one + if c.connection.Client == conn.Client { + log.Printf("Replacing existing same-direction connection %v with new connection %v for contact %v", c.connection, conn, c) + c.connection.Close() + c.connection = nil + } + + // If the existing connection is more than 30 seconds old, use the new one + // XXX implement this + + // Fall back to string comparison of hostnames for a stable resolution + preferOutbound := conn.MyHostname < conn.OtherHostname + if preferOutbound == conn.Client { + // New connection wins + log.Printf("Replacing existing connection %v with new connection %v for contact %v according to fallback order", c.connection, conn, c) + c.connection.Close() + c.connection = nil + } else { + // Old connection wins + log.Printf("Keeping existing connection %v instead of new connection %v for contact %v according to fallback order", c.connection, conn, c) + conn.Close() + return fmt.Errorf("Using existing connection") + } + } + + // If this connection is inbound and there's an outbound attempt, keep this + // connection and cancel outbound if we haven't sent authentication yet, or + // if the outbound connection will lose the fallback comparison above. + // XXX implement this + + c.connection = conn + log.Printf("Assigned connection %v to contact %v", c.connection, c) + + // XXX implicit accept contact requests + // XXX update connected date + // XXX signal state and data changes + // XXX react to connection state changes + + return nil +} diff --git a/core/identity.go b/core/identity.go index e0ffe48..f406796 100644 --- a/core/identity.go +++ b/core/identity.go @@ -100,6 +100,10 @@ func (me *Identity) setPrivateKey(key *rsa.PrivateKey) error { // BUG(special): No error handling for failures under publishService func (me *Identity) publishService(key *rsa.PrivateKey) { + // This call will block until a control connection is available and the + // ADD_ONION command has returned. After creating the listener, it will + // be automatically re-published if the control connection is lost and + // later reconnected. service, listener, err := me.core.Network.NewOnionListener(9878, key) if err != nil { log.Printf("Identity listener failed: %v", err) @@ -108,6 +112,12 @@ func (me *Identity) publishService(key *rsa.PrivateKey) { } if key == nil { + if service.PrivateKey == nil { + log.Printf("Setting private key failed: no key returned") + // XXX handle + return + } + err := me.setPrivateKey(service.PrivateKey.(*rsa.PrivateKey)) if err != nil { log.Printf("Setting private key failed: %v", err) diff --git a/core/protocol.go b/core/protocol.go index ad50008..8e6721d 100644 --- a/core/protocol.go +++ b/core/protocol.go @@ -76,10 +76,26 @@ func (handler *protocolHandler) OnAuthenticationChallenge(oc *protocol.OpenConne func (handler *protocolHandler) OnAuthenticationProof(oc *protocol.OpenConnection, channelID int32, publicKey []byte, signature []byte, isKnownContact bool) { result := oc.ValidateProof(channelID, publicKey, signature) - log.Printf("protocol: OnAuthenticationProof, result: %v", result) + + var contact *Contact + if result { + if len(oc.OtherHostname) != 16 { + log.Printf("protocol: Invalid format for hostname '%s' in authentication proof", oc.OtherHostname) + result = false + } else { + contact = handler.p.core.Identity.ContactList().ContactByAddress("ricochet:" + oc.OtherHostname) + } + } + isKnownContact = (contact != nil) + oc.SendAuthenticationResult(channelID, result, isKnownContact) oc.IsAuthed = result oc.CloseChannel(channelID) + + log.Printf("protocol: OnAuthenticationProof, result: %v, contact: %v", result, contact) + if result && contact != nil { + contact.SetConnection(oc) + } } func (handler *protocolHandler) OnAuthenticationResult(oc *protocol.OpenConnection, channelID int32, result bool, isKnownContact bool) { @@ -89,7 +105,8 @@ func (handler *protocolHandler) OnAuthenticationResult(oc *protocol.OpenConnecti // Contact Management func (handler *protocolHandler) IsKnownContact(hostname string) bool { - return true + contact := handler.p.core.Identity.ContactList().ContactByAddress("ricochet:" + hostname) + return contact != nil } func (handler *protocolHandler) OnContactRequest(oc *protocol.OpenConnection, channelID int32, nick string, message string) {