2019-06-05 18:44:26 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-06-10 20:36:18 +00:00
|
|
|
"bytes"
|
|
|
|
"compress/gzip"
|
2019-06-05 18:44:26 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"crypto/sha512"
|
|
|
|
"cwtch.im/tapir"
|
2019-06-05 19:02:03 +00:00
|
|
|
"cwtch.im/tapir/applications"
|
|
|
|
"cwtch.im/tapir/networks/tor"
|
2019-06-05 18:44:26 +00:00
|
|
|
"cwtch.im/tapir/primitives"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/log"
|
|
|
|
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
|
|
|
|
"golang.org/x/crypto/ed25519"
|
2019-06-10 20:36:18 +00:00
|
|
|
"io/ioutil"
|
2019-06-05 18:44:26 +00:00
|
|
|
"os"
|
2019-06-10 20:36:18 +00:00
|
|
|
"time"
|
2019-06-05 18:44:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// This example implements a basic notification application which allows peers to notify each other of new messages without downloading
|
|
|
|
// the entire contents of the server.
|
|
|
|
// NOTE: Very Incomplete Prototype.
|
|
|
|
|
|
|
|
// Notification contains a Topic string and a Message.
|
|
|
|
type Notification struct {
|
2019-06-05 19:02:03 +00:00
|
|
|
Topic string // A hex encoded string of the hash of the topic string
|
2019-06-05 18:44:26 +00:00
|
|
|
Message string
|
|
|
|
}
|
|
|
|
|
2019-06-05 19:02:03 +00:00
|
|
|
// NotificationClient allows publishing and reading from the notifications server
|
2019-06-05 18:44:26 +00:00
|
|
|
type NotificationClient struct {
|
2019-06-05 19:02:03 +00:00
|
|
|
applications.AuthApp
|
2019-06-05 18:44:26 +00:00
|
|
|
connection *tapir.Connection
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInstance should always return a new instantiation of the application.
|
|
|
|
func (nc NotificationClient) NewInstance() tapir.Application {
|
2019-06-05 19:02:03 +00:00
|
|
|
app := new(NotificationClient)
|
2019-06-05 18:44:26 +00:00
|
|
|
return app
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init is run when the connection is first started.
|
2019-06-05 19:02:03 +00:00
|
|
|
func (nc *NotificationClient) Init(connection *tapir.Connection) {
|
2019-06-05 18:44:26 +00:00
|
|
|
// First run the Authentication App
|
|
|
|
nc.AuthApp.Init(connection)
|
2019-06-05 19:02:03 +00:00
|
|
|
if connection.HasCapability(applications.AuthCapability) {
|
2019-06-05 18:44:26 +00:00
|
|
|
nc.connection = connection
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Publish transforms the given topic string into a hashed ID, and sends the ID along with the message
|
|
|
|
// NOTE: Server learns the hash of the topic (and therefore can correlate repeated use of the same topic)
|
|
|
|
func (nc NotificationClient) Publish(topic string, message string) {
|
|
|
|
log.Debugf("Sending Publish Request")
|
|
|
|
hashedTopic := sha512.Sum512([]byte(topic))
|
2019-06-06 16:41:03 +00:00
|
|
|
data, _ := json.Marshal(notificationRequest{RequestType: "Publish", RequestData: map[string]string{"Topic": hex.EncodeToString(hashedTopic[:])}})
|
2019-06-05 18:44:26 +00:00
|
|
|
nc.connection.Send([]byte(data))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check returns true if the server might have notifications related to the topic.
|
|
|
|
// This check reveals nothing about the topic to the server.
|
|
|
|
func (nc NotificationClient) Check(topic string) bool {
|
|
|
|
log.Debugf("Sending Filter Request")
|
|
|
|
// Get an updated bloom filter
|
2019-06-06 16:41:03 +00:00
|
|
|
data, _ := json.Marshal(notificationRequest{RequestType: "BloomFilter", RequestData: map[string]string{}})
|
2019-06-05 18:44:26 +00:00
|
|
|
nc.connection.Send(data)
|
|
|
|
response := nc.connection.Expect()
|
2019-06-10 20:36:18 +00:00
|
|
|
var bf []primitives.BloomFilter
|
|
|
|
r, _ := gzip.NewReader(bytes.NewReader(response))
|
|
|
|
bfb, _ := ioutil.ReadAll(r)
|
|
|
|
json.Unmarshal(bfb, &bf)
|
2019-06-05 18:44:26 +00:00
|
|
|
|
|
|
|
// Check the topic handle in the bloom filter
|
|
|
|
hashedTopic := sha512.Sum512([]byte(topic))
|
2019-06-10 20:36:18 +00:00
|
|
|
return bf[time.Now().Hour()].Check(hashedTopic[:])
|
2019-06-05 18:44:26 +00:00
|
|
|
}
|
|
|
|
|
2019-06-05 19:02:03 +00:00
|
|
|
type notificationRequest struct {
|
2019-06-05 18:44:26 +00:00
|
|
|
RequestType string
|
|
|
|
RequestData map[string]string
|
|
|
|
}
|
|
|
|
|
2019-06-05 19:02:03 +00:00
|
|
|
// NotificationsServer implements the metadata resistant notifications server
|
2019-06-05 18:44:26 +00:00
|
|
|
type NotificationsServer struct {
|
2019-06-05 19:02:03 +00:00
|
|
|
applications.AuthApp
|
2019-06-10 20:36:18 +00:00
|
|
|
Filter []*primitives.BloomFilter
|
|
|
|
timeProvider primitives.TimeProvider
|
2019-06-05 18:44:26 +00:00
|
|
|
}
|
|
|
|
|
2019-06-10 20:36:18 +00:00
|
|
|
const DefaultNumberOfBuckets = 24 // 1 per hour of the day
|
|
|
|
|
2019-06-05 18:44:26 +00:00
|
|
|
// NewInstance should always return a new instantiation of the application.
|
|
|
|
func (ns NotificationsServer) NewInstance() tapir.Application {
|
2019-06-05 19:02:03 +00:00
|
|
|
app := new(NotificationsServer)
|
2019-06-10 20:36:18 +00:00
|
|
|
|
|
|
|
app.timeProvider = new(primitives.OSTimeProvider)
|
|
|
|
app.Filter = make([]*primitives.BloomFilter, DefaultNumberOfBuckets)
|
|
|
|
for i := range app.Filter {
|
|
|
|
app.Filter[i] = new(primitives.BloomFilter)
|
|
|
|
app.Filter[i].Init(1024)
|
|
|
|
}
|
2019-06-05 18:44:26 +00:00
|
|
|
return app
|
|
|
|
}
|
|
|
|
|
2019-06-10 20:36:18 +00:00
|
|
|
// Configure overrides the default parameters for the Notification Server
|
|
|
|
func (ns NotificationsServer) Configure(timeProvider primitives.TimeProvider) {
|
|
|
|
ns.timeProvider = timeProvider
|
|
|
|
}
|
|
|
|
|
2019-06-05 19:02:03 +00:00
|
|
|
// Init initializes the application.
|
2019-06-05 18:44:26 +00:00
|
|
|
func (ns NotificationsServer) Init(connection *tapir.Connection) {
|
|
|
|
// First run the Authentication App
|
|
|
|
ns.AuthApp.Init(connection)
|
2019-06-05 19:02:03 +00:00
|
|
|
if connection.HasCapability(applications.AuthCapability) {
|
2019-06-05 18:44:26 +00:00
|
|
|
for {
|
|
|
|
request := connection.Expect()
|
2019-06-05 19:02:03 +00:00
|
|
|
var nr notificationRequest
|
2019-06-05 18:44:26 +00:00
|
|
|
json.Unmarshal(request, &nr)
|
|
|
|
log.Debugf("Received Request %v", nr)
|
|
|
|
switch nr.RequestType {
|
|
|
|
case "Publish":
|
2019-06-10 20:36:18 +00:00
|
|
|
log.Debugf("Received Publish Request")
|
|
|
|
topic := nr.RequestData["Topic"]
|
|
|
|
// message := nr.RequestData["Message"]
|
|
|
|
topicID, err := hex.DecodeString(topic)
|
|
|
|
if err == nil {
|
|
|
|
currentBucket := ns.timeProvider.GetCurrentTime().Hour()
|
|
|
|
ns.Filter[currentBucket].Insert(topicID)
|
|
|
|
}
|
2019-06-05 18:44:26 +00:00
|
|
|
case "BloomFilter":
|
2019-06-10 20:36:18 +00:00
|
|
|
log.Debugf("Received Filter Request")
|
|
|
|
response, _ := json.Marshal(ns.Filter)
|
|
|
|
var b bytes.Buffer
|
|
|
|
w := gzip.NewWriter(&b)
|
|
|
|
w.Write(response)
|
|
|
|
w.Close()
|
|
|
|
connection.Send(b.Bytes())
|
2019-06-05 18:44:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
|
|
log.SetLevel(log.LevelDebug)
|
|
|
|
|
|
|
|
// Connect to Tor
|
|
|
|
var acn connectivity.ACN
|
|
|
|
acn, _ = connectivity.StartTor("./", "")
|
|
|
|
acn.WaitTillBootstrapped()
|
|
|
|
|
|
|
|
// Generate Server Keys
|
|
|
|
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
|
|
|
|
sk := ed25519.PrivateKey(privateKey)
|
|
|
|
pk := ed25519.PublicKey(pubkey)
|
|
|
|
id := identity.InitializeV3("server", &sk, &pk)
|
|
|
|
|
|
|
|
// Init a Client to Connect to the Server
|
|
|
|
go client(acn, pubkey)
|
|
|
|
|
2019-07-16 19:14:42 +00:00
|
|
|
rm
|
2019-06-05 18:44:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Client will Connect and launch it's own Echo App goroutine.
|
|
|
|
func client(acn connectivity.ACN, key ed25519.PublicKey) {
|
|
|
|
pubkey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
|
|
|
|
sk := ed25519.PrivateKey(privateKey)
|
|
|
|
pk := ed25519.PublicKey(pubkey)
|
|
|
|
id := identity.InitializeV3("client", &sk, &pk)
|
|
|
|
var client tapir.Service
|
2019-06-05 19:02:03 +00:00
|
|
|
client = new(tor.BaseOnionService)
|
2019-06-05 18:44:26 +00:00
|
|
|
client.Init(acn, sk, id)
|
|
|
|
|
|
|
|
cid, _ := client.Connect(utils.GetTorV3Hostname(key), new(NotificationClient))
|
|
|
|
|
2019-06-05 19:02:03 +00:00
|
|
|
conn, err := client.WaitForCapabilityOrClose(cid, applications.AuthCapability)
|
2019-06-05 18:44:26 +00:00
|
|
|
if err == nil {
|
2019-06-05 19:02:03 +00:00
|
|
|
log.Debugf("Client has Auth: %v", conn.HasCapability(applications.AuthCapability))
|
2019-06-05 18:44:26 +00:00
|
|
|
nc := conn.App.(*NotificationClient)
|
|
|
|
|
|
|
|
// Basic Demonstration of Notification
|
|
|
|
log.Infof("Publishing to #astronomy: %v", nc.Check("#astronomy"))
|
|
|
|
nc.Publish("#astronomy", "New #Astronomy Post!")
|
|
|
|
log.Infof("Checking #astronomy: %v", nc.Check("#astronomy"))
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Exit(0)
|
|
|
|
}
|