forked from cwtch.im/cwtch
App Client/Service: new IPCBridge type and test gochan impl; new IPC using eventManager; new App Client and Service; some app api changes and a few more events (NewPeer) and errors (Loading errors)
This commit is contained in:
parent
5429cc6deb
commit
04dd8fa89c
181
app/app.go
181
app/app.go
|
@ -2,6 +2,7 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/event"
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
"cwtch.im/cwtch/peer"
|
"cwtch.im/cwtch/peer"
|
||||||
"cwtch.im/cwtch/protocol/connections"
|
"cwtch.im/cwtch/protocol/connections"
|
||||||
"cwtch.im/cwtch/storage"
|
"cwtch.im/cwtch/storage"
|
||||||
|
@ -16,86 +17,114 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type application struct {
|
type applicationCore struct {
|
||||||
peers map[string]peer.CwtchPeer
|
eventBuses map[string]event.Manager
|
||||||
storage map[string]storage.ProfileStore
|
|
||||||
engines map[string]connections.Engine
|
|
||||||
eventBuses map[string]*event.Manager
|
|
||||||
acn connectivity.ACN
|
acn connectivity.ACN
|
||||||
directory string
|
directory string
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
primaryonion string
|
}
|
||||||
|
|
||||||
|
type applicationPeers struct {
|
||||||
|
peers map[string]peer.CwtchPeer
|
||||||
|
}
|
||||||
|
|
||||||
|
type application struct {
|
||||||
|
applicationCore
|
||||||
|
applicationPeers
|
||||||
|
storage map[string]storage.ProfileStore
|
||||||
|
engines map[string]connections.Engine
|
||||||
|
appBus event.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
|
// Application is a full cwtch peer application. It allows management, usage and storage of multiple peers
|
||||||
type Application interface {
|
type Application interface {
|
||||||
LoadProfiles(password string) error
|
LoadProfiles(password string)
|
||||||
CreatePeer(name string, password string) (peer.CwtchPeer, error)
|
CreatePeer(name string, password string)
|
||||||
|
|
||||||
PrimaryIdentity() peer.CwtchPeer
|
|
||||||
GetPeer(onion string) peer.CwtchPeer
|
|
||||||
ListPeers() map[string]string
|
|
||||||
GetEventBus(onion string) *event.Manager
|
|
||||||
LaunchPeers()
|
LaunchPeers()
|
||||||
|
|
||||||
|
GetPrimaryBus() event.Manager
|
||||||
|
GetEventBus(onion string) event.Manager
|
||||||
|
|
||||||
ShutdownPeer(string)
|
ShutdownPeer(string)
|
||||||
Shutdown()
|
Shutdown()
|
||||||
|
|
||||||
|
GetPeer(onion string) peer.CwtchPeer
|
||||||
|
ListPeers() map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadProfileFn is the function signature for a function in an app that loads a profile
|
||||||
|
type LoadProfileFn func(profile *model.Profile, store storage.ProfileStore)
|
||||||
|
|
||||||
|
func newAppCore(acn connectivity.ACN, appDirectory string) *applicationCore {
|
||||||
|
appCore := &applicationCore{eventBuses: make(map[string]event.Manager), directory: appDirectory, acn: acn}
|
||||||
|
os.MkdirAll(path.Join(appCore.directory, "profiles"), 0700)
|
||||||
|
return appCore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp creates a new app with some environment awareness and initializes a Tor Manager
|
// NewApp creates a new app with some environment awareness and initializes a Tor Manager
|
||||||
func NewApp(acn connectivity.ACN, appDirectory string) Application {
|
func NewApp(acn connectivity.ACN, appDirectory string) Application {
|
||||||
log.Debugf("NewApp(%v)\n", appDirectory)
|
log.Debugf("NewApp(%v)\n", appDirectory)
|
||||||
app := &application{peers: make(map[string]peer.CwtchPeer), storage: make(map[string]storage.ProfileStore), engines: make(map[string]connections.Engine), eventBuses: make(map[string]*event.Manager), directory: appDirectory, acn: acn}
|
app := &application{storage: make(map[string]storage.ProfileStore), applicationPeers: applicationPeers{peers: make(map[string]peer.CwtchPeer)}, engines: make(map[string]connections.Engine), applicationCore: *newAppCore(acn, appDirectory), appBus: event.NewEventManager()}
|
||||||
os.Mkdir(path.Join(app.directory, "profiles"), 0700)
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProfile creates a new cwtchPeer with a given name.
|
// CreatePeer creates a new Peer with a given name and core required accessories (eventbus)
|
||||||
func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer, error) {
|
func (ac *applicationCore) CreatePeer(name string, password string) (*model.Profile, error) {
|
||||||
log.Debugf("CreatePeer(%v)\n", name)
|
log.Debugf("CreatePeer(%v)\n", name)
|
||||||
|
|
||||||
eventBus := new(event.Manager)
|
|
||||||
eventBus.Initialize()
|
|
||||||
|
|
||||||
profile := storage.NewProfile(name)
|
profile := storage.NewProfile(name)
|
||||||
profileStore := storage.NewProfileWriterStore(eventBus, path.Join(app.directory, "profiles", profile.LocalID), password, profile)
|
|
||||||
pc := profileStore.GetProfileCopy()
|
ac.mutex.Lock()
|
||||||
p := peer.FromProfile(pc)
|
defer ac.mutex.Unlock()
|
||||||
_, exists := app.peers[p.GetProfile().Onion]
|
|
||||||
|
_, exists := ac.eventBuses[profile.Onion]
|
||||||
if exists {
|
if exists {
|
||||||
profileStore.Shutdown()
|
return nil, fmt.Errorf("Error: profile for onion %v already exists", profile.Onion)
|
||||||
eventBus.Shutdown()
|
|
||||||
return nil, fmt.Errorf("Error: profile for onion %v already exists", p.GetProfile().Onion)
|
|
||||||
}
|
}
|
||||||
p.Init(app.acn, eventBus)
|
|
||||||
|
eventBus := event.NewEventManager()
|
||||||
|
ac.eventBuses[profile.Onion] = eventBus
|
||||||
|
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine)
|
||||||
|
func (app *application) CreatePeer(name string, password string) {
|
||||||
|
profile, err := app.applicationCore.CreatePeer(name, password)
|
||||||
|
if err != nil {
|
||||||
|
app.appBus.Publish(event.NewEventList(event.PeerError, event.Error, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profileStore := storage.NewProfileWriterStore(app.eventBuses[profile.Onion], path.Join(app.directory, "profiles", profile.LocalID), password, profile)
|
||||||
|
app.storage[profile.Onion] = profileStore
|
||||||
|
|
||||||
|
pc := app.storage[profile.Onion].GetProfileCopy()
|
||||||
|
peer := peer.FromProfile(pc)
|
||||||
|
peer.Init(app.acn, app.eventBuses[profile.Onion])
|
||||||
|
|
||||||
blockedPeers := profile.BlockedPeers()
|
blockedPeers := profile.BlockedPeers()
|
||||||
// TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key.
|
// TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key.
|
||||||
identity := identity.InitializeV3(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
|
identity := identity.InitializeV3(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
|
||||||
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, eventBus, blockedPeers)
|
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], blockedPeers)
|
||||||
|
|
||||||
app.mutex.Lock()
|
app.peers[profile.Onion] = peer
|
||||||
app.peers[p.GetProfile().Onion] = p
|
app.engines[profile.Onion] = engine
|
||||||
app.storage[p.GetProfile().Onion] = profileStore
|
|
||||||
app.engines[p.GetProfile().Onion] = engine
|
|
||||||
app.eventBuses[p.GetProfile().Onion] = eventBus
|
|
||||||
app.mutex.Unlock()
|
|
||||||
|
|
||||||
return p, nil
|
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) LoadProfiles(password string) error {
|
// LoadProfiles takes a password and attempts to load any profiles it can from storage with it and create Peers for them
|
||||||
files, err := ioutil.ReadDir(path.Join(app.directory, "profiles"))
|
func (ac *applicationCore) LoadProfiles(password string, loadProfileFn LoadProfileFn) error {
|
||||||
|
files, err := ioutil.ReadDir(path.Join(ac.directory, "profiles"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error: cannot read profiles directory: %v", err)
|
return fmt.Errorf("Error: cannot read profiles directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
eventBus := event.NewEventManager()
|
||||||
eventBus := new(event.Manager)
|
profileStore := storage.NewProfileWriterStore(eventBus, path.Join(ac.directory, "profiles", file.Name()), password, nil)
|
||||||
eventBus.Initialize()
|
|
||||||
|
|
||||||
profileStore := storage.NewProfileWriterStore(eventBus, path.Join(app.directory, "profiles", file.Name()), password, nil)
|
|
||||||
err = profileStore.Load()
|
err = profileStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
@ -103,7 +132,7 @@ func (app *application) LoadProfiles(password string) error {
|
||||||
|
|
||||||
profile := profileStore.GetProfileCopy()
|
profile := profileStore.GetProfileCopy()
|
||||||
|
|
||||||
_, exists := app.peers[profile.Onion]
|
_, exists := ac.eventBuses[profile.Onion]
|
||||||
if exists {
|
if exists {
|
||||||
profileStore.Shutdown()
|
profileStore.Shutdown()
|
||||||
eventBus.Shutdown()
|
eventBus.Shutdown()
|
||||||
|
@ -111,29 +140,41 @@ func (app *application) LoadProfiles(password string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
peer := peer.FromProfile(profile)
|
ac.mutex.Lock()
|
||||||
peer.Init(app.acn, eventBus)
|
ac.eventBuses[profile.Onion] = eventBus
|
||||||
|
ac.mutex.Unlock()
|
||||||
|
|
||||||
blockedPeers := profile.BlockedPeers()
|
loadProfileFn(profile, profileStore)
|
||||||
identity := identity.InitializeV3(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
|
|
||||||
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, eventBus, blockedPeers)
|
|
||||||
|
|
||||||
app.mutex.Lock()
|
|
||||||
app.peers[profile.Onion] = peer
|
|
||||||
app.storage[profile.Onion] = profileStore
|
|
||||||
app.engines[profile.Onion] = engine
|
|
||||||
app.eventBuses[profile.Onion] = eventBus
|
|
||||||
if app.primaryonion == "" {
|
|
||||||
app.primaryonion = profile.Onion
|
|
||||||
}
|
|
||||||
app.mutex.Unlock()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadProfiles takes a password and attempts to load any profiles it can from storage with it and create Peers for them
|
||||||
|
func (app *application) LoadProfiles(password string) {
|
||||||
|
app.applicationCore.LoadProfiles(password, func(profile *model.Profile, profileStore storage.ProfileStore) {
|
||||||
|
peer := peer.FromProfile(profile)
|
||||||
|
peer.Init(app.acn, app.eventBuses[profile.Onion])
|
||||||
|
|
||||||
|
blockedPeers := profile.BlockedPeers()
|
||||||
|
identity := identity.InitializeV3(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
|
||||||
|
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], blockedPeers)
|
||||||
|
app.mutex.Lock()
|
||||||
|
app.peers[profile.Onion] = peer
|
||||||
|
app.storage[profile.Onion] = profileStore
|
||||||
|
app.engines[profile.Onion] = engine
|
||||||
|
app.mutex.Unlock()
|
||||||
|
app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrimaryBus returns the bus the Application uses for events that aren't peer specific
|
||||||
|
func (app *application) GetPrimaryBus() event.Manager {
|
||||||
|
return app.appBus
|
||||||
|
}
|
||||||
|
|
||||||
// LaunchPeers starts each peer Listening and connecting to peers and groups
|
// LaunchPeers starts each peer Listening and connecting to peers and groups
|
||||||
func (app *application) LaunchPeers() {
|
func (appPeers *applicationPeers) LaunchPeers() {
|
||||||
for _, p := range app.peers {
|
for _, p := range appPeers.peers {
|
||||||
if !p.IsStarted() {
|
if !p.IsStarted() {
|
||||||
p.Listen()
|
p.Listen()
|
||||||
p.StartPeersConnections()
|
p.StartPeersConnections()
|
||||||
|
@ -143,30 +184,25 @@ func (app *application) LaunchPeers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPeers returns a map of onions to their profile's Name
|
// ListPeers returns a map of onions to their profile's Name
|
||||||
func (app *application) ListPeers() map[string]string {
|
func (appPeers *applicationPeers) ListPeers() map[string]string {
|
||||||
keys := map[string]string{}
|
keys := map[string]string{}
|
||||||
for k, p := range app.peers {
|
for k, p := range appPeers.peers {
|
||||||
keys[k] = p.GetProfile().Name
|
keys[k] = p.GetProfile().Name
|
||||||
}
|
}
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrimaryIdentity returns a cwtchPeer for a given onion address
|
|
||||||
func (app *application) PrimaryIdentity() peer.CwtchPeer {
|
|
||||||
return app.peers[app.primaryonion]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeer returns a cwtchPeer for a given onion address
|
// GetPeer returns a cwtchPeer for a given onion address
|
||||||
func (app *application) GetPeer(onion string) peer.CwtchPeer {
|
func (appPeers *applicationPeers) GetPeer(onion string) peer.CwtchPeer {
|
||||||
if peer, ok := app.peers[onion]; ok {
|
if peer, ok := appPeers.peers[onion]; ok {
|
||||||
return peer
|
return peer
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEventBus returns a cwtchPeer's event bus
|
// GetEventBus returns a cwtchPeer's event bus
|
||||||
func (app *application) GetEventBus(onion string) *event.Manager {
|
func (ac *applicationCore) GetEventBus(onion string) event.Manager {
|
||||||
if manager, ok := app.eventBuses[onion]; ok {
|
if manager, ok := ac.eventBuses[onion]; ok {
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -194,4 +230,5 @@ func (app *application) Shutdown() {
|
||||||
app.storage[id].Shutdown()
|
app.storage[id].Shutdown()
|
||||||
app.eventBuses[id].Shutdown()
|
app.eventBuses[id].Shutdown()
|
||||||
}
|
}
|
||||||
|
app.appBus.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import "cwtch.im/cwtch/event"
|
||||||
|
import "git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
|
|
||||||
|
type applicationBridge struct {
|
||||||
|
applicationCore
|
||||||
|
|
||||||
|
bridge event.IPCBridge
|
||||||
|
handle func(*event.Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ab *applicationBridge) listen() {
|
||||||
|
log.Infoln("ab.listen()")
|
||||||
|
for {
|
||||||
|
ipcMessage, ok := ab.bridge.Read()
|
||||||
|
log.Infof("listen() got %v\n", ipcMessage)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipcMessage.Dest == DestApp {
|
||||||
|
ab.handle(&ipcMessage.Message)
|
||||||
|
} else {
|
||||||
|
if eventBus, exists := ab.eventBuses[ipcMessage.Dest]; exists {
|
||||||
|
eventBus.PublishLocal(ipcMessage.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ab *applicationBridge) Shutdown() {
|
||||||
|
ab.bridge.Shutdown()
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/peer"
|
||||||
|
"cwtch.im/cwtch/storage"
|
||||||
|
"fmt"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
|
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type applicationClient struct {
|
||||||
|
applicationBridge
|
||||||
|
applicationPeers
|
||||||
|
|
||||||
|
appBus event.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppClient returns an Application that acts as a client to a AppService, connected by the IPCBridge supplied
|
||||||
|
func NewAppClient(acn connectivity.ACN, appDirectory string, bridge event.IPCBridge) Application {
|
||||||
|
appClient := &applicationClient{applicationPeers: applicationPeers{peers: make(map[string]peer.CwtchPeer)}, applicationBridge: applicationBridge{applicationCore: *newAppCore(acn, appDirectory), bridge: bridge}, appBus: event.NewEventManager()}
|
||||||
|
appClient.handle = appClient.handleEvent
|
||||||
|
|
||||||
|
go appClient.listen()
|
||||||
|
|
||||||
|
return appClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrimaryBus returns the bus the Application uses for events that aren't peer specific
|
||||||
|
func (ac *applicationClient) GetPrimaryBus() event.Manager {
|
||||||
|
return ac.appBus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *applicationClient) handleEvent(ev *event.Event) {
|
||||||
|
switch ev.EventType {
|
||||||
|
case event.NewPeer:
|
||||||
|
localID := ev.Data[event.Identity]
|
||||||
|
password := ev.Data[event.Password]
|
||||||
|
ac.newPeer(localID, password)
|
||||||
|
case event.PeerError:
|
||||||
|
ac.appBus.Publish(*ev)
|
||||||
|
case event.AppError:
|
||||||
|
ac.appBus.Publish(*ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *applicationClient) newPeer(localID, password string) {
|
||||||
|
profile, err := storage.ReadProfile(path.Join(ac.directory, "profiles", localID), password)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not read profile for NewPeer event: %v\n", err)
|
||||||
|
ac.appBus.Publish(event.NewEventList(event.PeerError, event.Error, fmt.Sprintf("Could not read profile for NewPeer event: %v\n", err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := ac.peers[profile.Onion]
|
||||||
|
if exists {
|
||||||
|
log.Errorf("profile for onion %v already exists", profile.Onion)
|
||||||
|
ac.appBus.Publish(event.NewEventList(event.PeerError, event.Error, fmt.Sprintf("profile for onion %v already exists", profile.Onion)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus := event.NewIPCEventManager(ac.bridge, profile.Onion)
|
||||||
|
peer := peer.FromProfile(profile)
|
||||||
|
peer.Init(ac.acn, eventBus)
|
||||||
|
|
||||||
|
ac.mutex.Lock()
|
||||||
|
defer ac.mutex.Unlock()
|
||||||
|
ac.peers[profile.Onion] = peer
|
||||||
|
ac.eventBuses[profile.Onion] = eventBus
|
||||||
|
ac.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePeer messages the service to create a new Peer with the given name
|
||||||
|
func (ac *applicationClient) CreatePeer(name string, password string) {
|
||||||
|
log.Infof("appClient CreatePeer %v\n", name)
|
||||||
|
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.CreatePeer, map[event.Field]string{event.ProfileName: name, event.Password: password})}
|
||||||
|
ac.bridge.Write(&message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadProfiles messages the service to load any profiles for the given password
|
||||||
|
func (ac *applicationClient) LoadProfiles(password string) {
|
||||||
|
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.LoadProfiles, map[event.Field]string{event.Password: password})}
|
||||||
|
ac.bridge.Write(&message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShutdownPeer shuts down a peer and removes it from the app's management
|
||||||
|
func (ac *applicationClient) ShutdownPeer(onion string) {
|
||||||
|
ac.mutex.Lock()
|
||||||
|
defer ac.mutex.Unlock()
|
||||||
|
ac.eventBuses[onion].Shutdown()
|
||||||
|
delete(ac.eventBuses, onion)
|
||||||
|
ac.peers[onion].Shutdown()
|
||||||
|
delete(ac.peers, onion)
|
||||||
|
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.ShutdownPeer, map[event.Field]string{event.Identity: onion})}
|
||||||
|
ac.bridge.Write(&message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the application lcienr and all front end peer components
|
||||||
|
func (ac *applicationClient) Shutdown() {
|
||||||
|
for id := range ac.peers {
|
||||||
|
ac.ShutdownPeer(id)
|
||||||
|
}
|
||||||
|
ac.applicationBridge.Shutdown()
|
||||||
|
ac.appBus.Shutdown()
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
|
"cwtch.im/cwtch/protocol/connections"
|
||||||
|
"cwtch.im/cwtch/storage"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DestApp should be used as a destination for IPC messages that are for the application itself an not a peer
|
||||||
|
DestApp = "app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type applicationService struct {
|
||||||
|
applicationBridge
|
||||||
|
|
||||||
|
storage map[string]storage.ProfileStore
|
||||||
|
engines map[string]connections.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplicationService is the back end of an application that manages engines and writing storage and communicates to an ApplicationClient by an IPCBridge
|
||||||
|
type ApplicationService interface {
|
||||||
|
Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppService returns an ApplicationService that runs the backend of an app and communicates with a client by the supplied IPCBridge
|
||||||
|
func NewAppService(acn connectivity.ACN, appDirectory string, bridge event.IPCBridge) ApplicationService {
|
||||||
|
appService := &applicationService{storage: make(map[string]storage.ProfileStore), engines: make(map[string]connections.Engine), applicationBridge: applicationBridge{applicationCore: *newAppCore(acn, appDirectory), bridge: bridge}}
|
||||||
|
appService.handle = appService.handleEvent
|
||||||
|
|
||||||
|
// Set up IPC
|
||||||
|
|
||||||
|
// attach to listener
|
||||||
|
go appService.listen()
|
||||||
|
|
||||||
|
return appService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *applicationService) handleEvent(ev *event.Event) {
|
||||||
|
log.Infof("app Service handleEvent %v\n", ev.EventType)
|
||||||
|
switch ev.EventType {
|
||||||
|
case event.CreatePeer:
|
||||||
|
profileName := ev.Data[event.ProfileName]
|
||||||
|
password := ev.Data[event.Password]
|
||||||
|
as.createPeer(profileName, password)
|
||||||
|
case event.LoadProfiles:
|
||||||
|
password := ev.Data[event.Password]
|
||||||
|
as.loadProfiles(password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *applicationService) createPeer(name, password string) {
|
||||||
|
log.Infof("app Service create peer %v %v\n", name, password)
|
||||||
|
profile, err := as.applicationCore.CreatePeer(name, password)
|
||||||
|
as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion])
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not create Peer: %v\n", err)
|
||||||
|
message := event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.PeerError, event.Error, err.Error())}
|
||||||
|
as.bridge.Write(&message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profileStore := storage.NewProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile)
|
||||||
|
|
||||||
|
blockedPeers := profile.BlockedPeers()
|
||||||
|
// TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key.
|
||||||
|
identity := identity.InitializeV3(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
|
||||||
|
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], blockedPeers)
|
||||||
|
|
||||||
|
as.storage[profile.Onion] = profileStore
|
||||||
|
as.engines[profile.Onion] = engine
|
||||||
|
|
||||||
|
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.LocalID, event.Password: password})}
|
||||||
|
as.bridge.Write(&message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *applicationService) loadProfiles(password string) {
|
||||||
|
count := 0
|
||||||
|
as.applicationCore.LoadProfiles(password, func(profile *model.Profile, profileStore storage.ProfileStore) {
|
||||||
|
blockedPeers := profile.BlockedPeers()
|
||||||
|
identity := identity.InitializeV3(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey)
|
||||||
|
engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], blockedPeers)
|
||||||
|
as.mutex.Lock()
|
||||||
|
as.storage[profile.Onion] = profileStore
|
||||||
|
as.engines[profile.Onion] = engine
|
||||||
|
as.mutex.Unlock()
|
||||||
|
message := event.IPCMessage{Dest: DestApp, Message: event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.LocalID, event.Password: password})}
|
||||||
|
as.bridge.Write(&message)
|
||||||
|
count++
|
||||||
|
})
|
||||||
|
if count == 0 {
|
||||||
|
message := event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.AppError, event.Error, event.AppErrLoaded0)}
|
||||||
|
as.bridge.Write(&message)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the application Service and all peer related backend parts
|
||||||
|
func (as *applicationService) Shutdown() {
|
||||||
|
for id := range as.engines {
|
||||||
|
as.engines[id].Shutdown()
|
||||||
|
as.storage[id].Shutdown()
|
||||||
|
as.eventBuses[id].Shutdown()
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,7 +47,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
botPeer := peer.NewCwtchPeer("servermon")
|
botPeer := peer.NewCwtchPeer("servermon")
|
||||||
botPeer.Init(acn, new(event.Manager))
|
botPeer.Init(acn, event.NewEventManager())
|
||||||
|
|
||||||
fmt.Printf("Connecting to %v...\n", serverAddr)
|
fmt.Printf("Connecting to %v...\n", serverAddr)
|
||||||
botPeer.JoinServer(serverAddr)
|
botPeer.JoinServer(serverAddr)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
app2 "cwtch.im/cwtch/app"
|
app2 "cwtch.im/cwtch/app"
|
||||||
|
"cwtch.im/cwtch/event"
|
||||||
peer2 "cwtch.im/cwtch/peer"
|
peer2 "cwtch.im/cwtch/peer"
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -207,6 +208,32 @@ func completer(d prompt.Document) []prompt.Suggest {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleAppEvents(em event.Manager) {
|
||||||
|
queue := event.NewEventQueue(100)
|
||||||
|
em.Subscribe(event.NewPeer, queue.EventChannel)
|
||||||
|
em.Subscribe(event.PeerError, queue.EventChannel)
|
||||||
|
|
||||||
|
for {
|
||||||
|
ev := queue.Next()
|
||||||
|
switch ev.EventType {
|
||||||
|
case event.NewPeer:
|
||||||
|
onion := ev.Data[event.Identity]
|
||||||
|
p := app.GetPeer(onion)
|
||||||
|
app.LaunchPeers()
|
||||||
|
|
||||||
|
fmt.Printf("\nLoaded profile %v (%v)\n", p.GetProfile().Name, p.GetProfile().Onion)
|
||||||
|
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
||||||
|
|
||||||
|
profiles := app.ListPeers()
|
||||||
|
fmt.Printf("\n%v profiles active now\n", len(profiles))
|
||||||
|
fmt.Printf("You should run `select-profile` to use a profile or `list-profiles` to view loaded profiles\n")
|
||||||
|
case event.PeerError:
|
||||||
|
err := ev.Data[event.Error]
|
||||||
|
fmt.Printf("\nError creating profile: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
cwtch :=
|
cwtch :=
|
||||||
|
@ -260,6 +287,7 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
app = app2.NewApp(acn, path.Join(usr.HomeDir, ".cwtch"))
|
app = app2.NewApp(acn, path.Join(usr.HomeDir, ".cwtch"))
|
||||||
|
go handleAppEvents(app.GetPrimaryBus())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error initializing application: %v", err)
|
log.Errorf("Error initializing application: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -340,17 +368,7 @@ func main() {
|
||||||
if failcount >= 3 {
|
if failcount >= 3 {
|
||||||
fmt.Printf("Error creating profile for %v: Your password entries must match!\n", name)
|
fmt.Printf("Error creating profile for %v: Your password entries must match!\n", name)
|
||||||
} else {
|
} else {
|
||||||
p, err := app.CreatePeer(name, password)
|
app.CreatePeer(name, password)
|
||||||
app.LaunchPeers()
|
|
||||||
if err == nil {
|
|
||||||
stopGroupFollow()
|
|
||||||
fmt.Printf("\nNew profile created for %v\n", name)
|
|
||||||
peer = p
|
|
||||||
suggestions = append(suggestionsBase, suggestionsSelectedProfile...)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
fmt.Printf("\nError creating profile for %v: %v\n", name, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Error creating New Profile, usage: %s\n", usages[commands[0]])
|
fmt.Printf("Error creating New Profile, usage: %s\n", usages[commands[0]])
|
||||||
|
@ -364,12 +382,10 @@ func main() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.LoadProfiles(string(bytePassword))
|
app.LoadProfiles(string(bytePassword))
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
app.LaunchPeers()
|
|
||||||
profiles := app.ListPeers()
|
|
||||||
fmt.Printf("\n%v profiles active now\n", len(profiles))
|
|
||||||
fmt.Printf("You should run `select-profile` to use a profile or `list-profiles` to view loaded profiles\n")
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\nError loading profiles: %v\n", err)
|
fmt.Printf("\nError loading profiles: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the Event Bus to Listen for Data Packets
|
// Setup the Event Bus to Listen for Data Packets
|
||||||
eventBus := new(event.Manager)
|
eventBus := event.NewEventManager()
|
||||||
eventBus.Initialize()
|
|
||||||
queue := event.NewEventQueue(100)
|
queue := event.NewEventQueue(100)
|
||||||
eventBus.Subscribe(event.NewMessageFromPeer, queue.EventChannel)
|
eventBus.Subscribe(event.NewMessageFromPeer, queue.EventChannel)
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the Event Buss and Initialize the Peer
|
// Set up the Event Buss and Initialize the Peer
|
||||||
eventBus := new(event.Manager)
|
eventBus := event.NewEventManager()
|
||||||
eventBus.Initialize()
|
|
||||||
bob := peer.NewCwtchPeer("bob")
|
bob := peer.NewCwtchPeer("bob")
|
||||||
bob.Init(acn, eventBus)
|
bob.Init(acn, eventBus)
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,10 @@ const (
|
||||||
EncryptedGroupMessage = Type("EncryptedGroupMessage")
|
EncryptedGroupMessage = Type("EncryptedGroupMessage")
|
||||||
NewMessageFromGroup = Type("NewMessageFromGroup")
|
NewMessageFromGroup = Type("NewMessageFromGroup")
|
||||||
|
|
||||||
// an error was encountered trying to send a particular message to a group
|
// an error was encountered trying to send a particular Message to a group
|
||||||
// attributes:
|
// attributes:
|
||||||
// GroupServer: The server the message was sent to
|
// GroupServer: The server the Message was sent to
|
||||||
// Signature: The signature of the message that failed to send
|
// Signature: The signature of the Message that failed to send
|
||||||
// Error: string describing the error
|
// Error: string describing the error
|
||||||
SendMessageToGroupError = Type("SendMessageToGroupError")
|
SendMessageToGroupError = Type("SendMessageToGroupError")
|
||||||
|
|
||||||
|
@ -92,6 +92,29 @@ const (
|
||||||
// GroupServer
|
// GroupServer
|
||||||
// ConnectionState
|
// ConnectionState
|
||||||
ServerStateChange = Type("GroupStateChange")
|
ServerStateChange = Type("GroupStateChange")
|
||||||
|
|
||||||
|
/***** Application client / service messages *****/
|
||||||
|
|
||||||
|
// ProfileName, Password
|
||||||
|
CreatePeer = Type("CreatePeer")
|
||||||
|
|
||||||
|
// service -> client: Identity(localId), Password
|
||||||
|
// app -> Identity(onion)
|
||||||
|
NewPeer = Type("NewPeer")
|
||||||
|
|
||||||
|
// Password
|
||||||
|
LoadProfiles = Type("LoadProfiles")
|
||||||
|
|
||||||
|
// Identity(onion)
|
||||||
|
ShutdownPeer = Type("ShutdownPeer")
|
||||||
|
|
||||||
|
Shutdown = Type("Shutdown")
|
||||||
|
|
||||||
|
// Error(err)
|
||||||
|
PeerError = Type("PeerError")
|
||||||
|
|
||||||
|
// Error(err)
|
||||||
|
AppError = Type("AppError")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Field defines common event attributes
|
// Field defines common event attributes
|
||||||
|
@ -113,6 +136,7 @@ const (
|
||||||
GroupInvite = Field("GroupInvite")
|
GroupInvite = Field("GroupInvite")
|
||||||
|
|
||||||
ProfileName = Field("ProfileName")
|
ProfileName = Field("ProfileName")
|
||||||
|
Password = Field("Password")
|
||||||
|
|
||||||
ConnectionState = Field("ConnectionState")
|
ConnectionState = Field("ConnectionState")
|
||||||
|
|
||||||
|
@ -121,3 +145,8 @@ const (
|
||||||
|
|
||||||
Error = Field("Error")
|
Error = Field("Error")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defining Common errors
|
||||||
|
const (
|
||||||
|
AppErrLoaded0 = "Loaded 0 profiles"
|
||||||
|
)
|
||||||
|
|
|
@ -18,8 +18,21 @@ func NewEvent(eventType Type, data map[Field]string) Event {
|
||||||
return Event{EventType: eventType, EventID: utils.GetRandNumber().String(), Data: data}
|
return Event{EventType: eventType, EventID: utils.GetRandNumber().String(), Data: data}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEventList creates a new event object with a unique ID and the given type and data supplied in a list format and composed into a map of Type:string
|
||||||
|
func NewEventList(eventType Type, args ...interface{}) Event {
|
||||||
|
data := map[Field]string{}
|
||||||
|
for i := 0; i < len(args); i += 2 {
|
||||||
|
key, kok := args[i].(Field)
|
||||||
|
val, vok := args[i+1].(string)
|
||||||
|
if kok && vok {
|
||||||
|
data[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Event{EventType: eventType, EventID: utils.GetRandNumber().String(), Data: data}
|
||||||
|
}
|
||||||
|
|
||||||
// Manager is an Event Bus which allows subsystems to subscribe to certain EventTypes and publish others.
|
// Manager is an Event Bus which allows subsystems to subscribe to certain EventTypes and publish others.
|
||||||
type Manager struct {
|
type manager struct {
|
||||||
subscribers map[Type][]chan Event
|
subscribers map[Type][]chan Event
|
||||||
events chan Event
|
events chan Event
|
||||||
mapMutex sync.Mutex
|
mapMutex sync.Mutex
|
||||||
|
@ -27,8 +40,23 @@ type Manager struct {
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manager is an interface for an event bus
|
||||||
|
type Manager interface {
|
||||||
|
Subscribe(Type, chan Event)
|
||||||
|
Publish(Event)
|
||||||
|
PublishLocal(Event)
|
||||||
|
Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventManager returns an initialized EventManager
|
||||||
|
func NewEventManager() Manager {
|
||||||
|
em := &manager{}
|
||||||
|
em.initialize()
|
||||||
|
return em
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize sets up the Manager.
|
// Initialize sets up the Manager.
|
||||||
func (em *Manager) Initialize() {
|
func (em *manager) initialize() {
|
||||||
em.subscribers = make(map[Type][]chan Event)
|
em.subscribers = make(map[Type][]chan Event)
|
||||||
em.events = make(chan Event)
|
em.events = make(chan Event)
|
||||||
em.internal = make(chan bool)
|
em.internal = make(chan bool)
|
||||||
|
@ -38,21 +66,26 @@ func (em *Manager) Initialize() {
|
||||||
|
|
||||||
// Subscribe takes an eventType and an Channel and associates them in the eventBus. All future events of that type
|
// Subscribe takes an eventType and an Channel and associates them in the eventBus. All future events of that type
|
||||||
// will be sent to the eventChannel.
|
// will be sent to the eventChannel.
|
||||||
func (em *Manager) Subscribe(eventType Type, eventChannel chan Event) {
|
func (em *manager) Subscribe(eventType Type, eventChannel chan Event) {
|
||||||
em.mapMutex.Lock()
|
em.mapMutex.Lock()
|
||||||
defer em.mapMutex.Unlock()
|
defer em.mapMutex.Unlock()
|
||||||
em.subscribers[eventType] = append(em.subscribers[eventType], eventChannel)
|
em.subscribers[eventType] = append(em.subscribers[eventType], eventChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish takes an Event and sends it to the internal eventBus where it is distributed to all Subscribers
|
// Publish takes an Event and sends it to the internal eventBus where it is distributed to all Subscribers
|
||||||
func (em *Manager) Publish(event Event) {
|
func (em *manager) Publish(event Event) {
|
||||||
if event.EventType != "" && em.closed != true {
|
if event.EventType != "" && em.closed != true {
|
||||||
em.events <- event
|
em.events <- event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish an event only locally, not going over an IPC bridge if there is one
|
||||||
|
func (em *manager) PublishLocal(event Event) {
|
||||||
|
em.Publish(event)
|
||||||
|
}
|
||||||
|
|
||||||
// eventBus is an internal function that is used to distribute events to all subscribers
|
// eventBus is an internal function that is used to distribute events to all subscribers
|
||||||
func (em *Manager) eventBus() {
|
func (em *manager) eventBus() {
|
||||||
for {
|
for {
|
||||||
event := <-em.events
|
event := <-em.events
|
||||||
|
|
||||||
|
@ -83,7 +116,7 @@ func (em *Manager) eventBus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown triggers, and waits for, the internal eventBus goroutine to finish
|
// Shutdown triggers, and waits for, the internal eventBus goroutine to finish
|
||||||
func (em *Manager) Shutdown() {
|
func (em *manager) Shutdown() {
|
||||||
em.events <- Event{}
|
em.events <- Event{}
|
||||||
em.closed = true
|
em.closed = true
|
||||||
// wait for eventBus to finish
|
// wait for eventBus to finish
|
||||||
|
|
|
@ -8,8 +8,7 @@ import (
|
||||||
|
|
||||||
// Most basic Manager Test, Initialize, Subscribe, Publish, Receive
|
// Most basic Manager Test, Initialize, Subscribe, Publish, Receive
|
||||||
func TestEventManager(t *testing.T) {
|
func TestEventManager(t *testing.T) {
|
||||||
eventManager := new(Manager)
|
eventManager := NewEventManager()
|
||||||
eventManager.Initialize()
|
|
||||||
|
|
||||||
// We need to make this buffer at least 1, otherwise we will log an error!
|
// We need to make this buffer at least 1, otherwise we will log an error!
|
||||||
testChan := make(chan Event, 1)
|
testChan := make(chan Event, 1)
|
||||||
|
@ -28,8 +27,7 @@ func TestEventManager(t *testing.T) {
|
||||||
|
|
||||||
// Most basic Manager Test, Initialize, Subscribe, Publish, Receive
|
// Most basic Manager Test, Initialize, Subscribe, Publish, Receive
|
||||||
func TestEventManagerOverflow(t *testing.T) {
|
func TestEventManagerOverflow(t *testing.T) {
|
||||||
eventManager := new(Manager)
|
eventManager := NewEventManager()
|
||||||
eventManager.Initialize()
|
|
||||||
|
|
||||||
// Explicitly setting this to 0 log an error!
|
// Explicitly setting this to 0 log an error!
|
||||||
testChan := make(chan Event)
|
testChan := make(chan Event)
|
||||||
|
@ -39,8 +37,7 @@ func TestEventManagerOverflow(t *testing.T) {
|
||||||
|
|
||||||
func TestEventManagerMultiple(t *testing.T) {
|
func TestEventManagerMultiple(t *testing.T) {
|
||||||
log.SetLevel(log.LevelDebug)
|
log.SetLevel(log.LevelDebug)
|
||||||
eventManager := new(Manager)
|
eventManager := NewEventManager()
|
||||||
eventManager.Initialize()
|
|
||||||
|
|
||||||
groupEventQueue := NewEventQueue(10)
|
groupEventQueue := NewEventQueue(10)
|
||||||
peerEventQueue := NewEventQueue(10)
|
peerEventQueue := NewEventQueue(10)
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
type ipcManager struct {
|
||||||
|
manager Manager
|
||||||
|
|
||||||
|
onion string
|
||||||
|
ipcBridge IPCBridge
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIPCEventManager returns an EvenetManager that also pipes events over and supplied IPCBridge
|
||||||
|
func NewIPCEventManager(bridge IPCBridge, onion string) Manager {
|
||||||
|
em := &ipcManager{onion: onion, ipcBridge: bridge, manager: NewEventManager()}
|
||||||
|
return em
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPCEventManagerFrom returns an IPCEventManger from the supplied manager and IPCBridge
|
||||||
|
func IPCEventManagerFrom(bridge IPCBridge, onion string, manager Manager) Manager {
|
||||||
|
em := &ipcManager{onion: onion, ipcBridge: bridge, manager: manager}
|
||||||
|
return em
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ipcm *ipcManager) Publish(ev Event) {
|
||||||
|
ipcm.manager.Publish(ev)
|
||||||
|
message := &IPCMessage{Dest: ipcm.onion, Message: ev}
|
||||||
|
ipcm.ipcBridge.Write(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ipcm *ipcManager) PublishLocal(ev Event) {
|
||||||
|
ipcm.manager.Publish(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ipcm *ipcManager) Subscribe(eventType Type, eventChan chan Event) {
|
||||||
|
ipcm.manager.Subscribe(eventType, eventChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ipcm *ipcManager) Shutdown() {
|
||||||
|
ipcm.manager.Shutdown()
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPCMessage is a wrapper for a regular eventMessage with a destination (onion|AppDest) so the other side of the bridge can route appropriately
|
||||||
|
type IPCMessage struct {
|
||||||
|
Dest string
|
||||||
|
Message Event
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeBridge struct {
|
||||||
|
in chan IPCMessage
|
||||||
|
out chan IPCMessage
|
||||||
|
closedChan chan bool
|
||||||
|
closed bool
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPCBridge is an interface to a IPC construct used to communicate IPCMessages
|
||||||
|
type IPCBridge interface {
|
||||||
|
Read() (IPCMessage, bool)
|
||||||
|
Write(message *IPCMessage)
|
||||||
|
Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePipeBridge returns a simple testing IPCBridge made from inprocess go channels
|
||||||
|
func MakePipeBridge() (b1, b2 IPCBridge) {
|
||||||
|
chan1 := make(chan IPCMessage)
|
||||||
|
chan2 := make(chan IPCMessage)
|
||||||
|
closed := make(chan bool)
|
||||||
|
|
||||||
|
a := &pipeBridge{in: chan1, out: chan2, closedChan: closed, closed: false}
|
||||||
|
b := &pipeBridge{in: chan2, out: chan1, closedChan: closed, closed: false}
|
||||||
|
|
||||||
|
go monitor(a, b)
|
||||||
|
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
func monitor(a, b *pipeBridge) {
|
||||||
|
<-a.closedChan
|
||||||
|
a.closed = true
|
||||||
|
b.closed = true
|
||||||
|
a.closedChan <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *pipeBridge) Read() (message IPCMessage, ok bool) {
|
||||||
|
message, ok = <-pb.in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *pipeBridge) Write(message *IPCMessage) {
|
||||||
|
pb.lock.Lock()
|
||||||
|
defer pb.lock.Unlock()
|
||||||
|
log.Infof("pb.Write: %v\n", message)
|
||||||
|
if !pb.closed {
|
||||||
|
pb.out <- *message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *pipeBridge) Shutdown() {
|
||||||
|
if !pb.closed {
|
||||||
|
close(pb.in)
|
||||||
|
close(pb.out)
|
||||||
|
pb.closedChan <- true
|
||||||
|
<-pb.closedChan
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,13 +25,13 @@ type cwtchPeer struct {
|
||||||
started bool
|
started bool
|
||||||
|
|
||||||
queue *event.Queue
|
queue *event.Queue
|
||||||
eventBus *event.Manager
|
eventBus event.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
|
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
|
||||||
// directly implement a cwtchPeer.
|
// directly implement a cwtchPeer.
|
||||||
type CwtchPeer interface {
|
type CwtchPeer interface {
|
||||||
Init(connectivity.ACN, *event.Manager)
|
Init(connectivity.ACN, event.Manager)
|
||||||
PeerWithOnion(string) *connections.PeerPeerConnection
|
PeerWithOnion(string) *connections.PeerPeerConnection
|
||||||
InviteOnionToGroup(string, string) error
|
InviteOnionToGroup(string, string) error
|
||||||
SendMessageToPeer(string, string) string
|
SendMessageToPeer(string, string) string
|
||||||
|
@ -83,7 +83,7 @@ func FromProfile(profile *model.Profile) CwtchPeer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init instantiates a cwtchPeer
|
// Init instantiates a cwtchPeer
|
||||||
func (cp *cwtchPeer) Init(acn connectivity.ACN, eventBus *event.Manager) {
|
func (cp *cwtchPeer) Init(acn connectivity.ACN, eventBus event.Manager) {
|
||||||
cp.queue = event.NewEventQueue(100)
|
cp.queue = event.NewEventQueue(100)
|
||||||
go cp.eventHandler()
|
go cp.eventHandler()
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ type engine struct {
|
||||||
blocked sync.Map
|
blocked sync.Map
|
||||||
|
|
||||||
// Pointer to the Global Event Manager
|
// Pointer to the Global Event Manager
|
||||||
eventManager *event.Manager
|
eventManager event.Manager
|
||||||
|
|
||||||
// Required for listen(), inaccessible from identity
|
// Required for listen(), inaccessible from identity
|
||||||
privateKey ed25519.PrivateKey
|
privateKey ed25519.PrivateKey
|
||||||
|
@ -45,7 +45,7 @@ type engine struct {
|
||||||
type Engine interface {
|
type Engine interface {
|
||||||
Identity() identity.Identity
|
Identity() identity.Identity
|
||||||
ACN() connectivity.ACN
|
ACN() connectivity.ACN
|
||||||
EventManager() *event.Manager
|
EventManager() event.Manager
|
||||||
|
|
||||||
GetPeerHandler(string) *CwtchPeerHandler
|
GetPeerHandler(string) *CwtchPeerHandler
|
||||||
ContactRequest(string, string) string
|
ContactRequest(string, string) string
|
||||||
|
@ -57,7 +57,7 @@ type Engine interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProtocolEngine initializes a new engine that runs Cwtch using the given parameters
|
// 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 {
|
func NewProtocolEngine(identity identity.Identity, privateKey ed25519.PrivateKey, acn connectivity.ACN, eventManager event.Manager, blockedPeers []string) Engine {
|
||||||
engine := new(engine)
|
engine := new(engine)
|
||||||
engine.identity = identity
|
engine.identity = identity
|
||||||
engine.privateKey = privateKey
|
engine.privateKey = privateKey
|
||||||
|
@ -92,7 +92,7 @@ func (e *engine) Identity() identity.Identity {
|
||||||
return e.identity
|
return e.identity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *engine) EventManager() *event.Manager {
|
func (e *engine) EventManager() event.Manager {
|
||||||
return e.eventManager
|
return e.eventManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ func (cpi *CwtchPeerInstance) Init(rai *application.Instance, ra *application.Ri
|
||||||
// CwtchPeerHandler encapsulates handling of incoming CwtchPackets
|
// CwtchPeerHandler encapsulates handling of incoming CwtchPackets
|
||||||
type CwtchPeerHandler struct {
|
type CwtchPeerHandler struct {
|
||||||
Onion string
|
Onion string
|
||||||
EventBus *event.Manager
|
EventBus event.Manager
|
||||||
DataHandler func(string, []byte) []byte
|
DataHandler func(string, []byte) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,7 @@ func TestPeerPeerConnection(t *testing.T) {
|
||||||
|
|
||||||
profile := model.GenerateNewProfile("alice")
|
profile := model.GenerateNewProfile("alice")
|
||||||
hostname := identity.Hostname()
|
hostname := identity.Hostname()
|
||||||
manager := &event.Manager{}
|
manager := event.NewEventManager()
|
||||||
manager.Initialize()
|
|
||||||
engine := NewProtocolEngine(identity, priv, connectivity.LocalProvider(), manager, nil)
|
engine := NewProtocolEngine(identity, priv, connectivity.LocalProvider(), manager, nil)
|
||||||
ppc := NewPeerPeerConnection("127.0.0.1:5452|"+hostname, engine)
|
ppc := NewPeerPeerConnection("127.0.0.1:5452|"+hostname, engine)
|
||||||
|
|
||||||
|
|
|
@ -76,8 +76,7 @@ func TestPeerServerConnection(t *testing.T) {
|
||||||
<-listenChan
|
<-listenChan
|
||||||
onionAddr := identity.Hostname()
|
onionAddr := identity.Hostname()
|
||||||
|
|
||||||
manager := &event.Manager{}
|
manager := event.NewEventManager()
|
||||||
manager.Initialize()
|
|
||||||
engine := NewProtocolEngine(identity, priv, connectivity.LocalProvider(), manager, nil)
|
engine := NewProtocolEngine(identity, priv, connectivity.LocalProvider(), manager, nil)
|
||||||
|
|
||||||
psc := NewPeerServerConnection(engine, "127.0.0.1:5451|"+onionAddr)
|
psc := NewPeerServerConnection(engine, "127.0.0.1:5451|"+onionAddr)
|
||||||
|
|
|
@ -19,7 +19,7 @@ type profileStore struct {
|
||||||
directory string
|
directory string
|
||||||
password string
|
password string
|
||||||
profile *model.Profile
|
profile *model.Profile
|
||||||
eventManager *event.Manager
|
eventManager event.Manager
|
||||||
queue *event.Queue
|
queue *event.Queue
|
||||||
writer bool
|
writer bool
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ type ProfileStore interface {
|
||||||
|
|
||||||
// NewProfileWriterStore returns a profile store backed by a filestore listening for events and saving them
|
// NewProfileWriterStore returns a profile store backed by a filestore listening for events and saving them
|
||||||
// directory should be $appDir/profiles/$rand
|
// directory should be $appDir/profiles/$rand
|
||||||
func NewProfileWriterStore(eventManager *event.Manager, directory, password string, profile *model.Profile) ProfileStore {
|
func NewProfileWriterStore(eventManager event.Manager, directory, password string, profile *model.Profile) ProfileStore {
|
||||||
os.Mkdir(directory, 0700)
|
os.Mkdir(directory, 0700)
|
||||||
ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: profile, eventManager: eventManager, streamStores: map[string]StreamStore{}, writer: true}
|
ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: profile, eventManager: eventManager, streamStores: map[string]StreamStore{}, writer: true}
|
||||||
ps.queue = event.NewEventQueue(100)
|
ps.queue = event.NewEventQueue(100)
|
||||||
|
@ -56,13 +56,20 @@ func NewProfileWriterStore(eventManager *event.Manager, directory, password stri
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProfileReaderStore returns a profile store backed by a filestore
|
// ReadProfile reads a profile from storqage and returns the profile
|
||||||
// directory should be $appDir/profiles/$rand
|
// directory should be $appDir/profiles/$rand
|
||||||
func NewProfileReaderStore(directory, password string, profile *model.Profile) ProfileStore {
|
func ReadProfile(directory, password string) (*model.Profile, error) {
|
||||||
os.Mkdir(directory, 0700)
|
os.Mkdir(directory, 0700)
|
||||||
ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: profile, eventManager: nil, streamStores: map[string]StreamStore{}, writer: true}
|
ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: nil, eventManager: nil, streamStores: map[string]StreamStore{}, writer: true}
|
||||||
|
|
||||||
return ps
|
err := ps.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
profile := ps.GetProfileCopy()
|
||||||
|
|
||||||
|
return profile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProfile creates a new profile for use in the profile store.
|
// NewProfile creates a new profile for use in the profile store.
|
||||||
|
|
|
@ -17,8 +17,7 @@ const testMessage = "Hello from storage"
|
||||||
|
|
||||||
func TestProfileStoreWriteRead(t *testing.T) {
|
func TestProfileStoreWriteRead(t *testing.T) {
|
||||||
os.RemoveAll(testingDir)
|
os.RemoveAll(testingDir)
|
||||||
eventBus := new(event.Manager)
|
eventBus := event.NewEventManager()
|
||||||
eventBus.Initialize()
|
|
||||||
profile := NewProfile(testProfileName)
|
profile := NewProfile(testProfileName)
|
||||||
ps1 := NewProfileWriterStore(eventBus, testingDir, password, profile)
|
ps1 := NewProfileWriterStore(eventBus, testingDir, password, profile)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
app2 "cwtch.im/cwtch/app"
|
app2 "cwtch.im/cwtch/app"
|
||||||
|
"cwtch.im/cwtch/event"
|
||||||
"cwtch.im/cwtch/model"
|
"cwtch.im/cwtch/model"
|
||||||
"cwtch.im/cwtch/peer"
|
"cwtch.im/cwtch/peer"
|
||||||
"cwtch.im/cwtch/protocol/connections"
|
"cwtch.im/cwtch/protocol/connections"
|
||||||
|
@ -100,11 +101,23 @@ func waitForPeerPeerConnection(t *testing.T, peera peer.CwtchPeer, peerb peer.Cw
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func waitGetPeer(app app2.Application, name string) peer.CwtchPeer {
|
||||||
|
for true {
|
||||||
|
for id, n := range app.ListPeers() {
|
||||||
|
if n == name {
|
||||||
|
return app.GetPeer(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestCwtchPeerIntegration(t *testing.T) {
|
func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
// Hide logging "noise"
|
|
||||||
numGoRoutinesStart := runtime.NumGoroutine()
|
numGoRoutinesStart := runtime.NumGoroutine()
|
||||||
|
|
||||||
log.AddEverythingFromPattern("connectivity")
|
log.AddEverythingFromPattern("connectivity")
|
||||||
|
//log.SetLevel(log.LevelDebug)
|
||||||
acn, err := connectivity.StartTor(".", "")
|
acn, err := connectivity.StartTor(".", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not start Tor: %v", err)
|
t.Fatalf("Could not start Tor: %v", err)
|
||||||
|
@ -137,29 +150,36 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
|
|
||||||
app := app2.NewApp(acn, "./storage")
|
app := app2.NewApp(acn, "./storage")
|
||||||
|
|
||||||
|
bridge1, bridge2 := event.MakePipeBridge()
|
||||||
|
appClient := app2.NewAppClient(acn, "./storage", bridge1)
|
||||||
|
appService := app2.NewAppService(acn, "./storage", bridge2)
|
||||||
|
|
||||||
|
numGoRoutinesPostAppStart := runtime.NumGoroutine()
|
||||||
|
|
||||||
// ***** cwtchPeer setup *****
|
// ***** cwtchPeer setup *****
|
||||||
|
|
||||||
// It's important that each Peer have their own EventBus
|
|
||||||
/*aliceEventBus := new(event.Manager)
|
|
||||||
aliceEventBus.Initialize()
|
|
||||||
bobEventBus := new(event.Manager)
|
|
||||||
bobEventBus.Initialize()
|
|
||||||
carolEventBus := new(event.Manager)
|
|
||||||
carolEventBus.Initialize()*/
|
|
||||||
|
|
||||||
fmt.Println("Creating Alice...")
|
fmt.Println("Creating Alice...")
|
||||||
alice, _ := app.CreatePeer("alice", "asdfasdf")
|
app.CreatePeer("alice", "asdfasdf")
|
||||||
fmt.Println("Alice created:", alice.GetProfile().Onion)
|
|
||||||
|
|
||||||
fmt.Println("Creating Bob...")
|
fmt.Println("Creating Bob...")
|
||||||
bob, _ := app.CreatePeer("bob", "asdfasdf")
|
app.CreatePeer("bob", "asdfasdf")
|
||||||
fmt.Println("Bob created:", bob.GetProfile().Onion)
|
|
||||||
|
|
||||||
fmt.Println("Creating Carol...")
|
fmt.Println("Creating Carol...")
|
||||||
carol, _ := app.CreatePeer("Carol", "asdfasdf")
|
appClient.CreatePeer("carol", "asdfasdf")
|
||||||
|
|
||||||
|
alice := waitGetPeer(app, "alice")
|
||||||
|
fmt.Println("Alice created:", alice.GetProfile().Onion)
|
||||||
|
|
||||||
|
bob := waitGetPeer(app, "bob")
|
||||||
|
fmt.Println("Bob created:", bob.GetProfile().Onion)
|
||||||
|
|
||||||
|
carol := waitGetPeer(appClient, "carol")
|
||||||
fmt.Println("Carol created:", carol.GetProfile().Onion)
|
fmt.Println("Carol created:", carol.GetProfile().Onion)
|
||||||
|
|
||||||
|
//fmt.Println("Carol created:", carol.GetProfile().Onion)
|
||||||
|
|
||||||
app.LaunchPeers()
|
app.LaunchPeers()
|
||||||
|
appClient.LaunchPeers()
|
||||||
|
|
||||||
fmt.Println("Waiting for Alice, Bob, and Carol to connect with onion network...")
|
fmt.Println("Waiting for Alice, Bob, and Carol to connect with onion network...")
|
||||||
time.Sleep(time.Second * 90)
|
time.Sleep(time.Second * 90)
|
||||||
|
@ -367,10 +387,19 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
numGoRoutinesPostServerShutdown := runtime.NumGoroutine()
|
numGoRoutinesPostServerShutdown := runtime.NumGoroutine()
|
||||||
|
|
||||||
fmt.Println("Shutting down Carol...")
|
fmt.Println("Shutting down Carol...")
|
||||||
app.ShutdownPeer(carol.GetProfile().Onion)
|
appClient.ShutdownPeer(carol.GetProfile().Onion)
|
||||||
time.Sleep(time.Second * 3)
|
time.Sleep(time.Second * 3)
|
||||||
numGoRoutinesPostCarol := runtime.NumGoroutine()
|
numGoRoutinesPostCarol := runtime.NumGoroutine()
|
||||||
|
|
||||||
|
fmt.Println("Shutting down apps...")
|
||||||
|
app.Shutdown()
|
||||||
|
appClient.Shutdown()
|
||||||
|
appService.Shutdown()
|
||||||
|
|
||||||
|
bridge1.Shutdown()
|
||||||
|
bridge2.Shutdown()
|
||||||
|
numGoRoutinesPostAppShutdown := runtime.NumGoroutine()
|
||||||
|
|
||||||
fmt.Println("Shutting down ACN...")
|
fmt.Println("Shutting down ACN...")
|
||||||
acn.Close()
|
acn.Close()
|
||||||
time.Sleep(time.Second * 2) // Server ^^ has a 5 second loop attempting reconnect before exiting
|
time.Sleep(time.Second * 2) // Server ^^ has a 5 second loop attempting reconnect before exiting
|
||||||
|
@ -380,10 +409,10 @@ func TestCwtchPeerIntegration(t *testing.T) {
|
||||||
// Very useful if we are leaking any.
|
// Very useful if we are leaking any.
|
||||||
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||||
|
|
||||||
fmt.Printf("numGoRoutinesStart: %v\nnumGoRoutinesPostServer: %v\nnumGoRoutinesPostPeerStart: %v\nnumGoRoutinesPostPeerAndServerConnect: %v\n"+
|
fmt.Printf("numGoRoutinesStart: %v\nnumGoRoutinesPostServer: %v\nnumGoRoutinesPostAppStart: %v\nnumGoRoutinesPostPeerStart: %v\nnumGoRoutinesPostPeerAndServerConnect: %v\n"+
|
||||||
"numGoRoutinesPostAlice: %v\nnumGoRotinesPostCarolConnect: %v\nnumGoRoutinesPostBob: %v\nnumGoRoutinesPostServerShutdown: %v\nnumGoRoutinesPostCarol: %v\nnumGoRoutinesPostACN: %v\n",
|
"numGoRoutinesPostAlice: %v\nnumGoRotinesPostCarolConnect: %v\nnumGoRoutinesPostBob: %v\nnumGoRoutinesPostServerShutdown: %v\nnumGoRoutinesPostCarol: %v\nnumGoRoutinesPostAppShutdown: %v\nnumGoRoutinesPostACN: %v\n",
|
||||||
numGoRoutinesStart, numGoRoutinesPostServer, numGoRoutinesPostPeerStart, numGoRoutinesPostServerConnect,
|
numGoRoutinesStart, numGoRoutinesPostServer, numGoRoutinesPostAppStart, numGoRoutinesPostPeerStart, numGoRoutinesPostServerConnect,
|
||||||
numGoRoutinesPostAlice, numGoRotinesPostCarolConnect, numGoRoutinesPostBob, numGoRoutinesPostServerShutdown, numGoRoutinesPostCarol, numGoRoutinesPostACN)
|
numGoRoutinesPostAlice, numGoRotinesPostCarolConnect, numGoRoutinesPostBob, numGoRoutinesPostServerShutdown, numGoRoutinesPostCarol, numGoRoutinesPostAppShutdown, numGoRoutinesPostACN)
|
||||||
|
|
||||||
if numGoRoutinesStart != numGoRoutinesPostACN {
|
if numGoRoutinesStart != numGoRoutinesPostACN {
|
||||||
t.Errorf("Number of GoRoutines at start (%v) does not match number of goRoutines after cleanup of peers and servers (%v), clean up failed, leak detected!", numGoRoutinesStart, numGoRoutinesPostACN)
|
t.Errorf("Number of GoRoutines at start (%v) does not match number of goRoutines after cleanup of peers and servers (%v), clean up failed, leak detected!", numGoRoutinesStart, numGoRoutinesPostACN)
|
||||||
|
|
Loading…
Reference in New Issue