180 lines
6.3 KiB
Go
180 lines
6.3 KiB
Go
package connections
|
|
|
|
import (
|
|
"cwtch.im/cwtch/protocol/groups"
|
|
"encoding/json"
|
|
"errors"
|
|
"git.openprivacy.ca/cwtch.im/tapir"
|
|
"git.openprivacy.ca/cwtch.im/tapir/applications"
|
|
"git.openprivacy.ca/cwtch.im/tapir/networks/tor"
|
|
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
|
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
|
|
"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
|
|
}
|