cwtch/protocol/connections/engine.go

302 rader
11 KiB
Go
Normal vy Historik

2019-01-04 21:44:21 +00:00
package connections
import (
"crypto/rsa"
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/protocol"
"cwtch.im/cwtch/protocol/connections/peer"
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/application"
"git.openprivacy.ca/openprivacy/libricochet-go/channels"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
"github.com/golang/protobuf/proto"
"golang.org/x/crypto/ed25519"
2019-01-08 18:58:01 +00:00
"sync"
"time"
2019-01-04 21:44:21 +00:00
)
type engine struct {
2019-01-04 21:44:21 +00:00
queue *event.Queue
connectionsManager *Manager
// Engine Attributes
identity identity.Identity
acn connectivity.ACN
2019-01-04 21:44:21 +00:00
app *application.RicochetApplication
// Engine State
started bool
2019-01-08 18:58:01 +00:00
// Blocklist
blocked sync.Map
2019-01-04 21:44:21 +00:00
// Pointer to the Global Event Manager
eventManager event.Manager
// Required for listen(), inaccessible from identity
privateKey ed25519.PrivateKey
}
// Engine (ProtocolEngine) encapsulates the logic necessary to make and receive Cwtch connections.
// Note: ProtocolEngine doesn't have access to any information necessary to encrypt or decrypt GroupMessages
type Engine interface {
Identity() identity.Identity
ACN() connectivity.ACN
EventManager() event.Manager
GetPeerHandler(string) *CwtchPeerHandler
ContactRequest(string, string) string
LookupContact(string, rsa.PublicKey) (bool, bool)
LookupContactV3(string, ed25519.PublicKey) (bool, bool)
Shutdown()
2019-01-04 21:44:21 +00:00
}
// NewProtocolEngine initializes a new engine that runs Cwtch using the given parameters
func NewProtocolEngine(identity identity.Identity, privateKey ed25519.PrivateKey, acn connectivity.ACN, eventManager event.Manager, blockedPeers []string) Engine {
engine := new(engine)
engine.identity = identity
2019-01-04 21:44:21 +00:00
engine.privateKey = privateKey
engine.queue = event.NewEventQueue(100)
go engine.eventHandler()
engine.acn = acn
engine.connectionsManager = NewConnectionsManager(engine.acn)
2019-01-04 21:44:21 +00:00
go engine.connectionsManager.AttemptReconnections()
engine.eventManager = eventManager
2019-01-08 18:58:01 +00:00
2019-01-04 21:44:21 +00:00
engine.eventManager.Subscribe(event.ProtocolEngineStartListen, engine.queue.EventChannel)
engine.eventManager.Subscribe(event.PeerRequest, engine.queue.EventChannel)
engine.eventManager.Subscribe(event.InvitePeerToGroup, engine.queue.EventChannel)
engine.eventManager.Subscribe(event.JoinServer, engine.queue.EventChannel)
engine.eventManager.Subscribe(event.SendMessageToGroup, engine.queue.EventChannel)
engine.eventManager.Subscribe(event.SendMessageToPeer, engine.queue.EventChannel)
2019-01-08 18:58:01 +00:00
engine.eventManager.Subscribe(event.BlockPeer, engine.queue.EventChannel)
for _, peer := range blockedPeers {
engine.blocked.Store(peer, true)
}
2019-01-04 21:44:21 +00:00
return engine
}
func (e *engine) ACN() connectivity.ACN {
return e.acn
}
func (e *engine) Identity() identity.Identity {
return e.identity
}
func (e *engine) EventManager() event.Manager {
return e.eventManager
}
2019-01-04 21:44:21 +00:00
// eventHandler process events from other subsystems
func (e *engine) eventHandler() {
2019-01-04 21:44:21 +00:00
for {
ev := e.queue.Next()
switch ev.EventType {
case event.StatusRequest:
e.eventManager.Publish(event.Event{EventType: event.ProtocolEngineStatus, EventID: ev.EventID})
case event.PeerRequest:
e.peerWithOnion(ev.Data[event.RemotePeer])
2019-01-04 21:44:21 +00:00
case event.InvitePeerToGroup:
e.inviteOnionToGroup(ev.Data[event.RemotePeer], []byte(ev.Data[event.GroupInvite]))
2019-01-04 21:44:21 +00:00
case event.JoinServer:
e.joinServer(ev.Data[event.GroupServer])
2019-01-04 21:44:21 +00:00
case event.SendMessageToGroup:
e.sendMessageToGroup(ev.Data[event.GroupServer], []byte(ev.Data[event.Ciphertext]), []byte(ev.Data[event.Signature]))
2019-01-04 21:44:21 +00:00
case event.SendMessageToPeer:
log.Debugf("Sending Message to Peer.....")
2019-01-21 20:08:03 +00:00
ppc := e.connectionsManager.GetPeerPeerConnectionForOnion(ev.Data[event.RemotePeer])
if ppc != nil && ppc.GetState() == AUTHENTICATED {
2019-03-04 02:01:38 +00:00
err := ppc.SendPacket([]byte(ev.Data[event.Data]))
if err != nil {
e.eventManager.Publish(event.NewEvent(event.SendMessageToPeerError, map[event.Field]string{event.RemotePeer: ev.Data[event.RemotePeer], event.Signature: ev.EventID, event.Error: err.Error()}))
}
} else {
e.eventManager.Publish(event.NewEvent(event.SendMessageToPeerError, map[event.Field]string{event.RemotePeer: ev.Data[event.RemotePeer], event.Signature: ev.EventID, event.Error: "peer is offline or the connection has yet to finalize"}))
2019-01-04 21:44:21 +00:00
}
2019-01-08 18:58:01 +00:00
case event.BlockPeer:
2019-01-21 20:08:03 +00:00
e.blocked.Store(ev.Data[event.RemotePeer], true)
ppc := e.connectionsManager.GetPeerPeerConnectionForOnion(ev.Data[event.RemotePeer])
if ppc != nil {
ppc.Close()
}
e.app.Close(ev.Data[event.RemotePeer])
2019-01-04 21:44:21 +00:00
case event.ProtocolEngineStartListen:
go e.listenFn()
default:
return
}
}
}
// GetPeerHandler is an external interface function that allows callers access to a CwtchPeerHandler
// TODO: There is likely a slightly better way to encapsulate this behavior
func (e *engine) GetPeerHandler(remotePeerHostname string) *CwtchPeerHandler {
2019-01-04 21:44:21 +00:00
return &CwtchPeerHandler{Onion: remotePeerHostname, EventBus: e.eventManager}
}
// Listen sets up an onion listener to process incoming cwtch messages
func (e *engine) listenFn() {
2019-01-04 21:44:21 +00:00
ra := new(application.RicochetApplication)
onionService, err := e.acn.Listen(e.privateKey, application.RicochetPort)
2019-01-04 21:44:21 +00:00
if err != nil /*&& fmt.Sprintf("%v", err) != "550 Unspecified Tor error: Onion address collision"*/ {
e.eventManager.Publish(event.NewEvent(event.ProtocolEngineStopped, map[event.Field]string{event.Identity: e.identity.Hostname(), event.Error: err.Error()}))
2019-01-04 21:44:21 +00:00
return
}
2019-01-23 20:50:53 +00:00
af := application.InstanceFactory{}
2019-01-04 21:44:21 +00:00
af.Init()
2019-01-23 20:50:53 +00:00
af.AddHandler("im.cwtch.peer", func(rai *application.Instance) func() channels.Handler {
2019-01-04 21:44:21 +00:00
cpi := new(CwtchPeerInstance)
cpi.Init(rai, ra)
return func() channels.Handler {
cpc := new(peer.CwtchPeerChannel)
cpc.Handler = e.GetPeerHandler(rai.RemoteHostname)
return cpc
}
})
2019-01-23 20:50:53 +00:00
af.AddHandler("im.cwtch.peer.data", func(rai *application.Instance) func() channels.Handler {
2019-01-04 21:44:21 +00:00
cpi := new(CwtchPeerInstance)
cpi.Init(rai, ra)
return func() channels.Handler {
cpc := new(peer.CwtchPeerDataChannel)
cpc.Handler = e.GetPeerHandler(rai.RemoteHostname)
return cpc
}
})
ra.Init(e.ACN(), e.identity.Name, e.identity, af, e)
2019-01-04 21:44:21 +00:00
log.Infof("Running cwtch peer on %v", onionService.AddressFull())
e.started = true
e.app = ra
ra.Run(onionService)
e.eventManager.Publish(event.NewEvent(event.ProtocolEngineStopped, map[event.Field]string{event.Identity: e.identity.Hostname()}))
2019-01-04 21:44:21 +00:00
return
}
2019-01-08 18:58:01 +00:00
// LookupContact is a V2 API Call, we want to reject all V2 Peers
// TODO Deprecate
func (e *engine) LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
2019-01-08 18:58:01 +00:00
return false, false
2019-01-04 21:44:21 +00:00
}
2019-01-08 18:58:01 +00:00
// ContactRequest is a V2 API Call needed to implement ContactRequestHandler Interface
// TODO Deprecate
func (e *engine) ContactRequest(name string, message string) string {
2019-01-08 18:58:01 +00:00
return "Rejected"
2019-01-04 21:44:21 +00:00
}
2019-01-08 18:58:01 +00:00
// LookupContactV3 returns that a contact is known and allowed to communicate for all cases.
func (e *engine) LookupContactV3(hostname string, publicKey ed25519.PublicKey) (allowed, known bool) {
2019-01-08 18:58:01 +00:00
// TODO: We want to autoblock those that are blocked, The known parameter has no use anymore and should be
// disregarded by peers, so we set it to false.
if _, blocked := e.blocked.Load(hostname); blocked {
return false, false
}
return true, false
2019-01-04 21:44:21 +00:00
}
// Shutdown tears down the eventHandler goroutine
func (e *engine) Shutdown() {
2019-01-04 21:44:21 +00:00
e.connectionsManager.Shutdown()
e.app.Shutdown()
e.queue.Shutdown()
}
// peerWithOnion is the entry point for cwtchPeer relationships
func (e *engine) peerWithOnion(onion string) *PeerPeerConnection {
2019-01-04 21:44:21 +00:00
return e.connectionsManager.ManagePeerConnection(onion, e)
}
// inviteOnionToGroup kicks off the invite process
func (e *engine) inviteOnionToGroup(onion string, invite []byte) error {
2019-01-04 21:44:21 +00:00
ppc := e.connectionsManager.GetPeerPeerConnectionForOnion(onion)
if ppc == nil {
return errors.New("peer connection not setup for onion. peers must be trusted before sending")
}
if ppc.GetState() == AUTHENTICATED {
log.Infof("Got connection for group: %v - Sending Invite\n", ppc)
ppc.SendGroupInvite(invite)
} else {
return errors.New("cannot send invite to onion: peer connection is not ready")
}
return nil
}
// receiveGroupMessage is a callback function that processes GroupMessages from a given server
func (e *engine) receiveGroupMessage(server string, gm *protocol.GroupMessage) {
2019-01-04 21:44:21 +00:00
// Publish Event so that a Profile Engine can deal with it.
// Note: This technically means that *multiple* Profile Engines could listen to the same ProtocolEngine!
2019-01-21 20:08:03 +00:00
e.eventManager.Publish(event.NewEvent(event.EncryptedGroupMessage, map[event.Field]string{event.Ciphertext: string(gm.GetCiphertext()), event.Signature: string(gm.GetSignature())}))
2019-01-04 21:44:21 +00:00
}
// finishedFetch is a callback function the processes the termination of a fetch channel from a given server
func (e *engine) finishedFetch(server string) {
e.eventManager.Publish(event.NewEvent(event.FinishedFetch, map[event.Field]string{event.GroupServer: server}))
}
// joinServer manages a new server connection with the given onion address
func (e *engine) joinServer(onion string) {
e.connectionsManager.ManageServerConnection(onion, e, e.receiveGroupMessage, e.finishedFetch)
2019-01-04 21:44:21 +00:00
}
// sendMessageToGroup attempts to sent the given message to the given group id.
func (e *engine) sendMessageToGroup(server string, ct []byte, sig []byte) {
2019-01-04 21:44:21 +00:00
psc := e.connectionsManager.GetPeerServerConnectionForOnion(server)
if psc == nil {
2019-02-20 20:58:05 +00:00
e.eventManager.Publish(event.NewEvent(event.SendMessageToGroupError, map[event.Field]string{event.GroupServer: server, event.Signature: string(sig), event.Error: "server is offline or the connection has yet to finalize"}))
2019-01-04 21:44:21 +00:00
}
gm := &protocol.GroupMessage{
Ciphertext: ct,
Signature: sig,
}
err := psc.SendGroupMessage(gm)
2019-02-20 20:58:05 +00:00
if err != nil {
e.eventManager.Publish(event.NewEvent(event.SendMessageToGroupError, map[event.Field]string{event.GroupServer: server, event.Signature: string(sig), event.Error: err.Error()}))
}
2019-01-04 21:44:21 +00:00
}
// CwtchPeerInstance encapsulates incoming peer connections
type CwtchPeerInstance struct {
2019-01-23 20:50:53 +00:00
rai *application.Instance
2019-01-04 21:44:21 +00:00
ra *application.RicochetApplication
}
// Init sets up a CwtchPeerInstance
2019-01-23 20:50:53 +00:00
func (cpi *CwtchPeerInstance) Init(rai *application.Instance, ra *application.RicochetApplication) {
2019-01-04 21:44:21 +00:00
cpi.rai = rai
cpi.ra = ra
}
// CwtchPeerHandler encapsulates handling of incoming CwtchPackets
type CwtchPeerHandler struct {
Onion string
EventBus event.Manager
2019-01-04 21:44:21 +00:00
DataHandler func(string, []byte) []byte
}
// HandleGroupInvite handles incoming GroupInvites
func (cph *CwtchPeerHandler) HandleGroupInvite(gci *protocol.GroupChatInvite) {
log.Debugf("Received GroupID from %v %v\n", cph.Onion, gci.String())
marshal, err := proto.Marshal(gci)
if err == nil {
2019-01-22 19:11:25 +00:00
cph.EventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: cph.Onion, event.GroupInvite: string(marshal)}))
2019-01-04 21:44:21 +00:00
}
}
// HandlePacket handles the Cwtch cwtchPeer Data Channel
func (cph *CwtchPeerHandler) HandlePacket(data []byte) []byte {
2019-01-22 19:11:25 +00:00
cph.EventBus.Publish(event.NewEvent(event.NewMessageFromPeer, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: cph.Onion, event.Data: string(data)}))
2019-01-04 21:44:21 +00:00
return []byte{} // TODO remove this
}