2017-05-02 23:33:51 +00:00
|
|
|
package application
|
|
|
|
|
|
|
|
import (
|
2017-07-04 18:29:11 +00:00
|
|
|
"crypto/rsa"
|
|
|
|
"github.com/s-rah/go-ricochet"
|
2017-05-02 23:33:51 +00:00
|
|
|
"github.com/s-rah/go-ricochet/channels"
|
|
|
|
"github.com/s-rah/go-ricochet/connection"
|
2017-12-13 19:33:35 +00:00
|
|
|
"github.com/s-rah/go-ricochet/identity"
|
2017-07-04 18:29:11 +00:00
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"time"
|
2017-05-02 23:33:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// RicochetApplication bundles many useful constructs that are
|
|
|
|
// likely standard in a ricochet application
|
|
|
|
type RicochetApplication struct {
|
2017-07-04 18:29:11 +00:00
|
|
|
contactManager ContactManagerInterface
|
|
|
|
privateKey *rsa.PrivateKey
|
|
|
|
chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
|
|
|
|
chatMessageAckHandler func(*RicochetApplicationInstance, uint32)
|
2017-11-02 23:43:11 +00:00
|
|
|
l net.Listener
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
|
2017-07-04 18:29:11 +00:00
|
|
|
type RicochetApplicationInstance struct {
|
|
|
|
connection.AutoConnectionHandler
|
|
|
|
connection *connection.Connection
|
|
|
|
RemoteHostname string
|
|
|
|
ChatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string)
|
|
|
|
ChatMessageAckHandler func(*RicochetApplicationInstance, uint32)
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
|
2017-07-04 18:29:11 +00:00
|
|
|
func (rai *RicochetApplicationInstance) ContactRequest(name string, message string) string {
|
|
|
|
return "Accepted"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rai *RicochetApplicationInstance) ContactRequestRejected() {
|
|
|
|
}
|
|
|
|
func (rai *RicochetApplicationInstance) ContactRequestAccepted() {
|
|
|
|
}
|
|
|
|
func (rai *RicochetApplicationInstance) ContactRequestError() {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rai *RicochetApplicationInstance) SendChatMessage(message string) {
|
|
|
|
rai.connection.Do(func() error {
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
// Technically this errors afte the second time but we can ignore it.
|
2017-11-02 23:43:11 +00:00
|
|
|
rai.connection.RequestOpenChannel("im.ricochet.chat",
|
|
|
|
&channels.ChatChannel{
|
|
|
|
Handler: rai,
|
2017-11-02 23:05:01 +00:00
|
|
|
})
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
|
2017-07-04 18:29:11 +00:00
|
|
|
channel := rai.connection.Channel("im.ricochet.chat", channels.Outbound)
|
2017-05-02 23:33:51 +00:00
|
|
|
if channel != nil {
|
2017-09-23 22:29:46 +00:00
|
|
|
chatchannel, ok := channel.Handler.(*channels.ChatChannel)
|
2017-05-02 23:33:51 +00:00
|
|
|
if ok {
|
|
|
|
chatchannel.SendMessage(message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
2017-07-04 18:29:11 +00:00
|
|
|
|
|
|
|
func (rai *RicochetApplicationInstance) ChatMessage(messageID uint32, when time.Time, message string) bool {
|
|
|
|
go rai.ChatMessageHandler(rai, messageID, when, message)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-09-25 20:01:07 +00:00
|
|
|
func (rai *RicochetApplicationInstance) ChatMessageAck(messageID uint32, accepted bool) {
|
2017-07-04 18:29:11 +00:00
|
|
|
rai.ChatMessageAckHandler(rai, messageID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ra *RicochetApplication) Init(pk *rsa.PrivateKey, cm ContactManagerInterface) {
|
|
|
|
ra.privateKey = pk
|
|
|
|
ra.contactManager = cm
|
|
|
|
ra.chatMessageHandler = func(*RicochetApplicationInstance, uint32, time.Time, string) {}
|
|
|
|
ra.chatMessageAckHandler = func(*RicochetApplicationInstance, uint32) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ra *RicochetApplication) OnChatMessage(call func(*RicochetApplicationInstance, uint32, time.Time, string)) {
|
|
|
|
ra.chatMessageHandler = call
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ra *RicochetApplication) OnChatMessageAck(call func(*RicochetApplicationInstance, uint32)) {
|
|
|
|
ra.chatMessageAckHandler = call
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ra *RicochetApplication) handleConnection(conn net.Conn) {
|
|
|
|
rc, err := goricochet.NegotiateVersionInbound(conn)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("There was an error")
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ich := connection.HandleInboundConnection(rc)
|
|
|
|
|
2017-12-13 19:33:35 +00:00
|
|
|
err = ich.ProcessAuthAsServer(identity.Initialize("", ra.privateKey), ra.contactManager.LookupContact)
|
2017-07-04 18:29:11 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("There was an error")
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
rai := new(RicochetApplicationInstance)
|
2017-11-02 23:05:01 +00:00
|
|
|
rai.Init()
|
2017-07-04 18:29:11 +00:00
|
|
|
rai.RemoteHostname = rc.RemoteHostname
|
|
|
|
rai.connection = rc
|
|
|
|
rai.ChatMessageHandler = ra.chatMessageHandler
|
|
|
|
rai.ChatMessageAckHandler = ra.chatMessageAckHandler
|
|
|
|
|
|
|
|
rai.RegisterChannelHandler("im.ricochet.contact.request", func() channels.Handler {
|
|
|
|
contact := new(channels.ContactRequestChannel)
|
|
|
|
contact.Handler = rai
|
|
|
|
return contact
|
|
|
|
})
|
|
|
|
rai.RegisterChannelHandler("im.ricochet.chat", func() channels.Handler {
|
|
|
|
chat := new(channels.ChatChannel)
|
|
|
|
chat.Handler = rai
|
|
|
|
return chat
|
|
|
|
})
|
|
|
|
rc.Process(rai)
|
|
|
|
}
|
|
|
|
|
2017-11-02 23:43:11 +00:00
|
|
|
func (ra *RicochetApplication) Shutdown() {
|
|
|
|
log.Printf("Closing")
|
|
|
|
ra.l.Close()
|
|
|
|
log.Printf("Closed")
|
2017-07-04 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2017-07-04 18:29:11 +00:00
|
|
|
func (ra *RicochetApplication) Run(l net.Listener) {
|
|
|
|
if ra.privateKey == nil || ra.contactManager == nil {
|
|
|
|
return
|
|
|
|
}
|
2017-07-04 18:39:19 +00:00
|
|
|
ra.l = l
|
2017-11-02 23:43:11 +00:00
|
|
|
var err error
|
2017-07-04 18:39:19 +00:00
|
|
|
for err == nil {
|
|
|
|
conn, err := ra.l.Accept()
|
2017-07-04 18:29:11 +00:00
|
|
|
if err == nil {
|
|
|
|
go ra.handleConnection(conn)
|
2017-07-04 18:39:19 +00:00
|
|
|
} else {
|
2017-11-02 23:43:11 +00:00
|
|
|
log.Printf("Closing")
|
|
|
|
return
|
2017-07-04 18:29:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|