diff --git a/application/application.go b/application/application.go index 2a2c778..051ae46 100644 --- a/application/application.go +++ b/application/application.go @@ -3,98 +3,28 @@ package application import ( "crypto/rsa" "github.com/s-rah/go-ricochet" - "github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/connection" "github.com/s-rah/go-ricochet/identity" "log" "net" "sync" - "time" ) // RicochetApplication bundles many useful constructs that are // likely standard in a ricochet application type RicochetApplication struct { - contactManager ContactManagerInterface - privateKey *rsa.PrivateKey - chatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string) - chatMessageAckHandler func(*RicochetApplicationInstance, uint32) - onConnected func(*RicochetApplicationInstance) - onLeave func(*RicochetApplicationInstance) - l net.Listener - instances []*RicochetApplicationInstance - lock sync.Mutex + contactManager ContactManagerInterface + privateKey *rsa.PrivateKey + l net.Listener + instances []*ApplicationInstance + lock sync.Mutex + aif ApplicationInstanceFactory } -type RicochetApplicationInstance struct { - connection.AutoConnectionHandler - connection *connection.Connection - RemoteHostname string - ChatMessageHandler func(*RicochetApplicationInstance, uint32, time.Time, string) - ChatMessageAckHandler func(*RicochetApplicationInstance, uint32) - OnLeave func(*RicochetApplicationInstance) -} - -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 { - // Technically this errors afte the second time but we can ignore it. - rai.connection.RequestOpenChannel("im.ricochet.chat", - &channels.ChatChannel{ - Handler: rai, - }) - - channel := rai.connection.Channel("im.ricochet.chat", channels.Outbound) - if channel != nil { - chatchannel, ok := channel.Handler.(*channels.ChatChannel) - if ok { - chatchannel.SendMessage(message) - } - } - return nil - }) -} - -func (rai *RicochetApplicationInstance) ChatMessage(messageID uint32, when time.Time, message string) bool { - go rai.ChatMessageHandler(rai, messageID, when, message) - return true -} - -func (rai *RicochetApplicationInstance) ChatMessageAck(messageID uint32, accepted bool) { - rai.ChatMessageAckHandler(rai, messageID) -} - -func (ra *RicochetApplication) Init(pk *rsa.PrivateKey, cm ContactManagerInterface) { +func (ra *RicochetApplication) Init(pk *rsa.PrivateKey, af ApplicationInstanceFactory, cm ContactManagerInterface) { ra.privateKey = pk + ra.aif = af 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) OnConnected(call func(*RicochetApplicationInstance)) { - ra.onConnected = call -} - -func (ra *RicochetApplication) OnLeave(call func(*RicochetApplicationInstance)) { - ra.onLeave = call } func (ra *RicochetApplication) handleConnection(conn net.Conn) { @@ -114,39 +44,17 @@ func (ra *RicochetApplication) handleConnection(conn net.Conn) { return } rc.TraceLog(true) - rai := new(RicochetApplicationInstance) - rai.Init() - rai.RemoteHostname = rc.RemoteHostname - rai.connection = rc - rai.ChatMessageHandler = ra.chatMessageHandler - rai.ChatMessageAckHandler = ra.chatMessageAckHandler - rai.OnLeave = ra.onLeave - - 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 - }) + rai := ra.aif.GetApplicationInstance(rc) ra.lock.Lock() ra.instances = append(ra.instances, rai) ra.lock.Unlock() - go ra.onConnected(rai) rc.Process(rai) } -func (rai *RicochetApplicationInstance) OnClosed(err error) { - rai.OnLeave(rai) -} - -func (ra *RicochetApplication) Broadcast(message string) { +func (ra *RicochetApplication) Broadcast(do func(rai *ApplicationInstance)) { ra.lock.Lock() for _, rai := range ra.instances { - rai.SendChatMessage(message) + do(rai) } ra.lock.Unlock() } diff --git a/application/application_factory.go b/application/application_factory.go new file mode 100644 index 0000000..8779458 --- /dev/null +++ b/application/application_factory.go @@ -0,0 +1,40 @@ +package application + +import ( + "github.com/s-rah/go-ricochet/channels" + "github.com/s-rah/go-ricochet/connection" +) + +// A concrete instance of a ricochet application, encapsulating a connection +type ApplicationInstance struct { + connection.AutoConnectionHandler + Connection *connection.Connection + RemoteHostname string +} + +// Application instance factory +type ApplicationInstanceFactory struct { + handlerMap map[string]func(*ApplicationInstance) func() channels.Handler +} + +// Init setsup an Application Factory +func (af *ApplicationInstanceFactory) Init() { + af.handlerMap = make(map[string]func(*ApplicationInstance) func() channels.Handler) +} + +// AddHandler +func (af *ApplicationInstanceFactory) AddHandler(ctype string, chandler func(*ApplicationInstance) func() channels.Handler) { + af.handlerMap[ctype] = chandler +} + +// GetApplicationInstance, +func (af *ApplicationInstanceFactory) GetApplicationInstance(rc *connection.Connection) *ApplicationInstance { + rai := new(ApplicationInstance) + rai.Init() + rai.RemoteHostname = rc.RemoteHostname + rai.Connection = rc + for t, h := range af.handlerMap { + rai.RegisterChannelHandler(t, h(rai)) + } + return rai +} diff --git a/application/examples/commandbot/main.go b/application/examples/commandbot/main.go deleted file mode 100644 index 97897e2..0000000 --- a/application/examples/commandbot/main.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "github.com/s-rah/go-ricochet/application" - "github.com/s-rah/go-ricochet/utils" - "log" - "time" -) - -func main() { - commandbot := new(application.RicochetApplication) - pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key") - - if err != nil { - log.Fatalf("error reading private key file: %v", err) - } - - l, err := application.SetupOnion("127.0.0.1:9051", "", pk, 9878) - - if err != nil { - log.Fatalf("error setting up onion service: %v", err) - } - - commandbot.Init(pk, new(application.AcceptAllContactManager)) - commandbot.OnChatMessage(func(rai *application.RicochetApplicationInstance, id uint32, timestamp time.Time, message string) { - log.Printf("message from %v - %v", rai.RemoteHostname, message) - go echobot.Broadcast(rai.RemoteHostname + " " + message) - }) - log.Printf("commandbot listening on %s", l.Addr().String()) - commandbot.Run(l) -} diff --git a/application/examples/echobot/main.go b/application/examples/echobot/main.go index ec3bb25..3fe9b33 100644 --- a/application/examples/echobot/main.go +++ b/application/examples/echobot/main.go @@ -2,11 +2,53 @@ package main import ( "github.com/s-rah/go-ricochet/application" + "github.com/s-rah/go-ricochet/channels" "github.com/s-rah/go-ricochet/utils" "log" "time" ) +type EchoBotInstance struct { + rai *application.ApplicationInstance + ra *application.RicochetApplication +} + +func (ebi *EchoBotInstance) Init(rai *application.ApplicationInstance, ra *application.RicochetApplication) { + ebi.rai = rai + ebi.ra = ra +} + +func (ebi *EchoBotInstance) ChatMessage(messageID uint32, when time.Time, message string) bool { + log.Printf("message from %v - %v", ebi.rai.RemoteHostname, message) + go ebi.ra.Broadcast(func(rai *application.ApplicationInstance) { + ebi.SendChatMessage(rai, ebi.rai.RemoteHostname+" "+message) + }) + return true +} + +func (ebi *EchoBotInstance) ChatMessageAck(messageID uint32, accepted bool) { + +} + +func (ebi *EchoBotInstance) SendChatMessage(rai *application.ApplicationInstance, message string) { + rai.Connection.Do(func() error { + // Technically this errors after the second time but we can ignore it. + rai.Connection.RequestOpenChannel("im.ricochet.chat", + &channels.ChatChannel{ + Handler: ebi, + }) + + channel := rai.Connection.Channel("im.ricochet.chat", channels.Outbound) + if channel != nil { + chatchannel, ok := channel.Handler.(*channels.ChatChannel) + if ok { + chatchannel.SendMessage(message) + } + } + return nil + }) +} + func main() { echobot := new(application.RicochetApplication) pk, err := utils.LoadPrivateKeyFromFile("./testing/private_key") @@ -21,11 +63,19 @@ func main() { log.Fatalf("error setting up onion service: %v", err) } - echobot.Init(pk, new(application.AcceptAllContactManager)) - echobot.OnChatMessage(func(rai *application.RicochetApplicationInstance, id uint32, timestamp time.Time, message string) { - log.Printf("message from %v - %v", rai.RemoteHostname, message) - go echobot.Broadcast(rai.RemoteHostname + " " + message) + af := application.ApplicationInstanceFactory{} + af.Init() + af.AddHandler("im.ricochet.chat", func(rai *application.ApplicationInstance) func() channels.Handler { + ebi := new(EchoBotInstance) + ebi.Init(rai, echobot) + return func() channels.Handler { + chat := new(channels.ChatChannel) + chat.Handler = ebi + return chat + } }) + + echobot.Init(pk, af, new(application.AcceptAllContactManager)) log.Printf("echobot listening on %s", l.Addr().String()) echobot.Run(l) } diff --git a/utils/networking.go b/utils/networking.go index 9eef409..782d8f6 100644 --- a/utils/networking.go +++ b/utils/networking.go @@ -7,11 +7,11 @@ import ( ) const ( - // InvalidPacketLengthError is returned whenever ricochet receives a packet too small or too large to conform to the spec. + // InvalidPacketLengthError is returned whenever ricochet receives a packet too small or too large to conform to the spec. InvalidPacketLengthError = Error("InvalidPacketLengthError") - + // InvalidChannelIDError channels must be between 0 and 65535 - InvalidChannelIDError = Error("InvalidChannelIDError") + InvalidChannelIDError = Error("InvalidChannelIDError") ) // RicochetData is a structure containing the raw data and the channel it the