package application import ( "git.openprivacy.ca/openprivacy/libricochet-go" "git.openprivacy.ca/openprivacy/libricochet-go/connection" "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/identity" "git.openprivacy.ca/openprivacy/libricochet-go/log" "net" "sync" ) const ( // RicochetPort is the default port used by ricochet applications RicochetPort = 9878 ) // RicochetApplication bundles many useful constructs that are // likely standard in a ricochet application type RicochetApplication struct { contactManager ContactManagerInterface v3identity identity.Identity name string ls connectivity.ListenService acn connectivity.ACN instances []*Instance lock sync.Mutex aif InstanceFactory } // Init initializes the underlying RicochetApplication datastructure, making it ready for use func (ra *RicochetApplication) Init(acn connectivity.ACN, name string, v3identity identity.Identity, af InstanceFactory, cm ContactManagerInterface) { ra.acn = acn ra.name = name ra.v3identity = v3identity ra.aif = af ra.contactManager = cm } // TODO: Reimplement OnJoin, OnLeave Events. func (ra *RicochetApplication) handleConnection(conn net.Conn) { rc, err := goricochet.NegotiateVersionInbound(conn) if err != nil { log.Errorln("There was an error") conn.Close() return } ich := connection.HandleInboundConnection(rc) err = ich.ProcessAuthAsV3Server(ra.v3identity, ra.contactManager.LookupContactV3) if err != nil { log.Errorf("There was an error authenticating the connection: %v", err) conn.Close() return } rai := ra.aif.GetApplicationInstance(rc) ra.lock.Lock() ra.instances = append(ra.instances, rai) ra.lock.Unlock() rc.Process(rai) // rc.Process ends when the connection ends. // Remove it from the application's list of instances ra.lock.Lock() for i, x := range ra.instances { if x == rai { ra.instances = append(ra.instances[:i], ra.instances[i+1:]...) break } } ra.lock.Unlock() } // HandleApplicationInstance delegates handling of a given Instance to the Application. func (ra *RicochetApplication) HandleApplicationInstance(rai *Instance) { ra.lock.Lock() ra.instances = append(ra.instances, rai) ra.lock.Unlock() } // Open a connection to another Ricochet peer at onionAddress. Infof they are unknown to use, use requestMessage (otherwise can be blank) func (ra *RicochetApplication) Open(onionAddress string, requestMessage string) (*Instance, error) { rc, err := goricochet.Open(ra.acn, onionAddress) if err != nil { log.Errorf("Error in application.Open(): %v\n", err) return nil, err } och := connection.HandleOutboundConnection(rc) _, err = och.ProcessAuthAsV3Client(ra.v3identity) if err != nil { log.Errorf("There was an error authenticating the connection: %v", err) return nil, err } rai := ra.aif.GetApplicationInstance(rc) go rc.Process(rai) ra.HandleApplicationInstance(rai) return rai, nil } // Broadcast performs the given function do() over all application instance (all connected peers) func (ra *RicochetApplication) Broadcast(do func(rai *Instance)) { ra.lock.Lock() for _, rai := range ra.instances { do(rai) } ra.lock.Unlock() } // Shutdown stops a RicochetApplication, terminating all child processes and resources func (ra *RicochetApplication) Shutdown() { ra.lock.Lock() ra.ls.Close() for _, instance := range ra.instances { instance.Connection.Close() } ra.lock.Unlock() } // Close kills a connection by a given Onion Address func (ra *RicochetApplication) Close(onion string) { ra.lock.Lock() for _, instance := range ra.instances { if instance.RemoteHostname == onion { instance.Connection.Close() } } ra.lock.Unlock() } // ConnectionCount returns the number of concurrent connections to the application func (ra *RicochetApplication) ConnectionCount() int { return len(ra.instances) } // Run handles a Listen object and Accepts and handles new connections func (ra *RicochetApplication) Run(ls connectivity.ListenService) { if !ra.v3identity.Initialized() || ra.contactManager == nil { return } ra.lock.Lock() ra.ls = ls ra.lock.Unlock() var err error for err == nil { conn, err := ra.ls.Accept() if err == nil { go ra.handleConnection(conn) } else { return } } }