forked from cwtch.im/cwtch
1
0
Fork 0
cwtch/protocol/connections/tokenboardclientapp.go

180 lines
6.3 KiB
Go

package connections
import (
"cwtch.im/cwtch/protocol/groups"
"cwtch.im/tapir"
"cwtch.im/tapir/applications"
"cwtch.im/tapir/networks/tor"
"cwtch.im/tapir/primitives"
"cwtch.im/tapir/primitives/privacypass"
"encoding/json"
"errors"
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/log"
"github.com/gtank/ristretto255"
)
// NewTokenBoardClient generates a new Client for Token Board
func NewTokenBoardClient(acn connectivity.ACN, Y *ristretto255.Element, tokenServiceOnion string, groupMessageHandler func(server string, gm *groups.EncryptedGroupMessage), serverSyncedHandler func(server string)) tapir.Application {
tba := new(TokenBoardClient)
tba.acn = acn
tba.tokenService = privacypass.NewTokenServer()
tba.tokenService.Y = Y
tba.tokenServiceOnion = tokenServiceOnion
tba.receiveGroupMessageHandler = groupMessageHandler
tba.serverSyncedHandler = serverSyncedHandler
return tba
}
// TokenBoardClient defines a client for the TokenBoard server
type TokenBoardClient struct {
applications.AuthApp
connection tapir.Connection
receiveGroupMessageHandler func(server string, gm *groups.EncryptedGroupMessage)
serverSyncedHandler func(server string)
// Token service handling
acn connectivity.ACN
tokens []*privacypass.Token
tokenService *privacypass.TokenServer
tokenServiceOnion string
}
// NewInstance Client a new TokenBoardApp
func (ta *TokenBoardClient) NewInstance() tapir.Application {
tba := new(TokenBoardClient)
tba.serverSyncedHandler = ta.serverSyncedHandler
tba.receiveGroupMessageHandler = ta.receiveGroupMessageHandler
tba.acn = ta.acn
tba.tokenService = ta.tokenService
tba.tokenServiceOnion = ta.tokenServiceOnion
return tba
}
// Init initializes the cryptographic TokenBoardApp
func (ta *TokenBoardClient) Init(connection tapir.Connection) {
ta.AuthApp.Init(connection)
if connection.HasCapability(applications.AuthCapability) {
ta.connection = connection
ta.connection.SetCapability(groups.CwtchServerSyncedCapability)
log.Debugf("Successfully Initialized Connection")
go ta.Listen()
ta.Replay()
} else {
connection.Close()
}
}
// Listen processes the messages for this application
func (ta *TokenBoardClient) Listen() {
for {
log.Debugf("Client waiting...")
data := ta.connection.Expect()
if len(data) == 0 {
log.Debugf("Server closed the connection...")
return // connection is closed
}
// We always expect the server to follow protocol, and the second it doesn't we close the connection
// TODO issue an error so the client is aware
var message groups.Message
if err := json.Unmarshal(data, &message); err != nil {
log.Debugf("Server sent an unexpected message, closing the connection: %v", err)
ta.connection.Close()
return
}
switch message.MessageType {
case groups.NewMessageMessage:
if message.NewMessage != nil {
ta.receiveGroupMessageHandler(ta.connection.Hostname(), &message.NewMessage.EGM)
} else {
// TODO: Send this error to the UI
log.Debugf("Server sent an unexpected NewMessage, closing the connection: %s", data)
ta.connection.Close()
return
}
case groups.PostResultMessage:
// TODO handle failure
case groups.ReplayResultMessage:
if message.ReplayResult != nil {
log.Debugf("Replaying %v Messages...", message.ReplayResult.NumMessages)
for i := 0; i < message.ReplayResult.NumMessages; i++ {
data := ta.connection.Expect()
egm := &groups.EncryptedGroupMessage{}
if err := json.Unmarshal(data, egm); err == nil {
ta.receiveGroupMessageHandler(ta.connection.Hostname(), egm)
} else {
// TODO: Send this error to the UI
log.Debugf("Server sent an unexpected EncryptedGroupMessage, closing the connection: %v", err)
ta.connection.Close()
return
}
}
ta.serverSyncedHandler(ta.connection.Hostname())
}
}
}
}
// Replay posts a Replay Message to the server.
func (ta *TokenBoardClient) Replay() {
// TODO - Allow configurable ranges
data, _ := json.Marshal(groups.Message{MessageType: groups.ReplayRequestMessage, ReplayRequest: &groups.ReplayRequest{LastCommit: []byte{}}})
ta.connection.Send(data)
}
// PurchaseTokens purchases the given number of tokens from the server (using the provided payment handler)
func (ta *TokenBoardClient) PurchaseTokens() {
ta.MakePayment()
}
// Post sends a Post Request to the server
func (ta *TokenBoardClient) Post(ct []byte, sig []byte) bool {
egm := groups.EncryptedGroupMessage{Ciphertext: ct, Signature: sig}
token, err := ta.NextToken(egm.ToBytes(), ta.connection.Hostname())
if err == nil {
data, _ := json.Marshal(groups.Message{MessageType: groups.PostRequestMessage, PostRequest: &groups.PostRequest{EGM: egm, Token: token}})
log.Debugf("Message Length: %s %v", data, len(data))
ta.connection.Send(data)
return true
}
log.Debugf("No Valid Tokens: %v", err)
return false
}
// MakePayment uses the PoW based token protocol to obtain more tokens
func (ta *TokenBoardClient) MakePayment() {
log.Debugf("Making a Payment %v", ta)
id, sk := primitives.InitializeEphemeralIdentity()
var client tapir.Service
client = new(tor.BaseOnionService)
client.Init(ta.acn, sk, &id)
tokenApplication := new(applications.TokenApplication)
tokenApplication.TokenService = ta.tokenService
powTokenApp := new(applications.ApplicationChain).
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
ChainApplication(tokenApplication, applications.HasTokensCapability)
client.Connect(ta.tokenServiceOnion, powTokenApp)
conn, err := client.WaitForCapabilityOrClose(ta.tokenServiceOnion, applications.HasTokensCapability)
if err == nil {
powtapp, _ := conn.App().(*applications.TokenApplication)
ta.tokens = append(ta.tokens, powtapp.Tokens...)
log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit())
conn.Close()
return
}
log.Debugf("Error making payment: to %v %v", ta.tokenServiceOnion, err)
}
// NextToken retrieves the next token
func (ta *TokenBoardClient) NextToken(data []byte, hostname string) (privacypass.SpentToken, error) {
if len(ta.tokens) == 0 {
return privacypass.SpentToken{}, errors.New("No more tokens")
}
token := ta.tokens[0]
ta.tokens = ta.tokens[1:]
return token.SpendToken(append(data, hostname...)), nil
}