259 lines
7.9 KiB

package server
import (
tor2 ""
const (
// ServerConfigFile is the standard filename for a server's config to be written to in a directory
ServerConfigFile = "serverConfig.json"
// Server encapsulates a complete, compliant Cwtch server.
type Server interface {
Identity() primitives.Identity
Run(acn connectivity.ACN) error
KeyBundle() *model.KeyBundle
CheckStatus() (bool, error)
GetStatistics() Statistics
Delete(password string) error
Onion() string
ServerBundle() string
TofuBundle() string
GetAttribute(string) string
SetAttribute(string, string)
type server struct {
config *Config
service tapir.Service
messageStore storage.MessageStoreInterface
metricsPack metrics.Monitors
tokenTapirService tapir.Service
tokenServer *privacypass.TokenServer
tokenService primitives.Identity
tokenServicePrivKey ed25519.PrivateKey
tokenServiceStopped bool
onionServiceStopped bool
running bool
lock sync.RWMutex
// NewServer creates and configures a new server based on the supplied configuration
func NewServer(serverConfig *Config) Server {
server := new(server)
server.running = false
server.config = serverConfig
server.tokenService = server.config.TokenServiceIdentity()
server.tokenServicePrivKey = server.config.TokenServerPrivateKey
bs := new(persistence.BoltPersistence)
bs.Open(path.Join(serverConfig.ConfigDir, "tokens.db"))
server.tokenServer = privacypass.NewTokenServerFromStore(&serverConfig.TokenServiceK, bs)
log.Infof("Y: %v", server.tokenServer.Y)
return server
// Identity returns the main onion identity of the server
func (s *server) Identity() primitives.Identity {
return s.config.Identity()
// helper fn to pass to metrics
func (s *server) getStorageTotalMessageCount() int {
if s.messageStore != nil {
return s.messageStore.MessagesCount()
return 0
// helper fn to pass to storage
func (s *server) incMessageCount() {
if s.metricsPack.MessageCounter != nil {
// Run starts a server with the given privateKey
func (s *server) Run(acn connectivity.ACN) error {
defer s.lock.Unlock()
if s.running {
return nil
identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey)
service := new(tor2.BaseOnionService)
service.Init(acn, s.config.PrivateKey, &identity)
s.service = service
log.Infof("cwtch server running on cwtch:%s\n", s.Onion())
if s.config.ServerReporting.LogMetricsToFile {
s.metricsPack.Start(service, s.getStorageTotalMessageCount, s.config.ConfigDir, s.config.ServerReporting.LogMetricsToFile)
var err error
s.messageStore, err = storage.InitializeSqliteMessageStore(path.Join(s.config.ConfigDir, "cwtch.messages"), s.incMessageCount)
if err != nil {
return fmt.Errorf("could not open database: %v", err)
s.tokenTapirService = new(tor2.BaseOnionService)
s.tokenTapirService.Init(acn, s.tokenServicePrivKey, &s.tokenService)
tokenApplication := new(applications.TokenApplication)
tokenApplication.TokenService = s.tokenServer
powTokenApp := new(applications.ApplicationChain).
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
ChainApplication(tokenApplication, applications.HasTokensCapability)
go func() {
s.tokenServiceStopped = true
go func() {
s.service.Listen(NewTokenBoardServer(s.messageStore, s.tokenServer))
s.onionServiceStopped = true
s.running = true
return nil
// KeyBundle provides the signed keybundle of the server
func (s *server) KeyBundle() *model.KeyBundle {
kb := model.NewKeyBundle()
identity := s.config.Identity()
kb.Keys[model.KeyTypeServerOnion] = model.Key(identity.Hostname())
kb.Keys[model.KeyTypeTokenOnion] = model.Key(s.tokenService.Hostname())
kb.Keys[model.KeyTypePrivacyPass] = model.Key(s.tokenServer.Y.String())
return kb
// CheckStatus returns true if the server is running and/or an error if any part of the server needs to be restarted.
func (s *server) CheckStatus() (bool, error) {
defer s.lock.RUnlock()
if s.onionServiceStopped || s.tokenServiceStopped {
return s.running, fmt.Errorf("one of more server components are down: onion:%v token service: %v", s.onionServiceStopped, s.tokenServiceStopped)
return s.running, nil
// Stop turns off the server so it cannot receive connections and frees most resourses.
// The server is still in a reRunable state and tokenServer still has an active persistance
func (s *server) Stop() {
log.Infof("Shutting down server")
defer s.lock.Unlock()
if s.running {
log.Infof("Closing Token server Database...")
s.running = false
// Destroy frees the last of the resources the server has active (toklenServer persistance) leaving it un-re-runable and completely shutdown
func (s *server) Destroy() {
defer s.lock.Unlock()
// Statistics is an encapsulation of information about the server that an operator might want to know at a glance.
type Statistics struct {
TotalMessages int
TotalConnections int
// GetStatistics is a stub method for providing some high level information about
// the server operation to bundling applications (e.g. the UI)
func (s *server) GetStatistics() Statistics {
if s.running {
return Statistics{
TotalMessages: s.messageStore.MessagesCount(),
TotalConnections: s.service.Metrics().ConnectionCount,
return Statistics{}
func (s *server) Delete(password string) error {
if s.config.Encrypted && !s.config.CheckPassword(password) {
log.Errorf("encryped and checkpassword failed")
return errors.New("cannot delete server, passwords do not match")
return nil
func (s *server) Onion() string {
return s.config.Onion()
// ServerBundle returns a bundle of the server keys required to access it (torv3 keys are addresses)
func (s *server) ServerBundle() string {
bundle := s.KeyBundle().Serialize()
return fmt.Sprintf("server:%s", base64.StdEncoding.EncodeToString(bundle))
// TofuBundle returns a Server Bundle + a newly created group invite
func (s *server) TofuBundle() string {
group, _ := model.NewGroup(tor.GetTorV3Hostname(s.config.PublicKey))
invite, err := group.Invite()
if err != nil {
bundle := s.KeyBundle().Serialize()
return fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString(bundle), invite)
// GetAttribute gets a server attribute
func (s *server) GetAttribute(key string) string {
return s.config.GetAttribute(key)
// SetAttribute sets a server attribute
func (s *server) SetAttribute(key, val string) {
s.config.SetAttribute(key, val)
// SetMonitorLogging turns on or off the monitor logging suite, and logging to a file in the server dir
func (s *server) SetMonitorLogging(do bool) {
s.config.ServerReporting.LogMetricsToFile = do
if do {
s.metricsPack.Start(s.service, s.getStorageTotalMessageCount, s.config.ConfigDir, s.config.ServerReporting.LogMetricsToFile)
} else {