From 8a5717b73c376c98e3768af5b48d0289ecec7a8f Mon Sep 17 00:00:00 2001 From: Samuel Hawksby-Robinson Date: Fri, 9 Jul 2021 15:47:26 +0100 Subject: [PATCH] Commit of work so far --- app/keys.go | 80 ++++++++++++++++++++++++++ app/keys_test.go | 95 +++++++++++++++++++++++++++++++ app/main.go | 48 ++++++++++++++++ app/peer_discovery_test.go | 112 +++++++++++++++++++++++++++++++++++++ app/zmq4_test.go | 82 +++++++++++++++++++++++++++ app/zyre_test.go | 72 ++++++++++++++++++++++++ main.go | 11 ++++ main/main.go | 56 +++++++++++++++++++ 8 files changed, 556 insertions(+) create mode 100644 app/keys.go create mode 100644 app/keys_test.go create mode 100644 app/main.go create mode 100644 app/peer_discovery_test.go create mode 100644 app/zmq4_test.go create mode 100644 app/zyre_test.go create mode 100644 main.go create mode 100644 main/main.go diff --git a/app/keys.go b/app/keys.go new file mode 100644 index 0000000..6e987bc --- /dev/null +++ b/app/keys.go @@ -0,0 +1,80 @@ +package app + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io" + "math/big" + "os" + "time" +) + +const ( + OrganisationName = "Status IM" +) + +func getCertTemplate() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{OrganisationName}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } +} + +func generateCert(certWriter io.Writer, key *ecdsa.PrivateKey) error { + certTemplate := getCertTemplate() + certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &key.PublicKey, key) + if err != nil { + return err + } + + return pem.Encode(certWriter, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) +} + +func generateKey(keyWriter io.Writer, key *ecdsa.PrivateKey) error { + b, err := x509.MarshalECPrivateKey(key) + if err != nil { + return err + } + + return pem.Encode(keyWriter, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) +} + +func GenerateKeyAndCert(certWriter io.Writer, keyWriter io.Writer) error { + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + + err = generateCert(certWriter, privKey) + if err != nil { + return err + } + + err = generateKey(keyWriter, privKey) + if err != nil { + return err + } + + return nil +} + +func DeleteKeyAndCert() error { + err := os.Remove(TLSKeyName) + if err != nil { + return err + } + + return os.Remove(TLSCertName) +} \ No newline at end of file diff --git a/app/keys_test.go b/app/keys_test.go new file mode 100644 index 0000000..07b18f5 --- /dev/null +++ b/app/keys_test.go @@ -0,0 +1,95 @@ +package app + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "testing" +) + +func TestGenerateKeyAndCert(t *testing.T) { + crtWriter := bytes.NewBuffer([]byte{}) + keyWriter := bytes.NewBuffer([]byte{}) + + err := GenerateKeyAndCert(crtWriter, keyWriter) + if err != nil { + t.Error(err) + } + + expected := []*struct { + Name string + Bytes []byte + Len int + BeginWith string + EndWith string + PemBlock *pem.Block + }{ + { + "certificate", + crtWriter.Bytes(), + 509, + "-----BEGIN CERTIFICATE-----", + "-----END CERTIFICATE-----", + nil, + }, + { + "private key", + keyWriter.Bytes(), + 227, + "-----BEGIN EC PRIVATE KEY-----", + "-----END EC PRIVATE KEY-----", + nil, + }, + } + + for _, e := range expected { + if len(e.Bytes) != e.Len { + t.Errorf("%s pem bytes should be %d in length, received %d", + e.Name, + e.Len, + len(e.Bytes), + ) + } + + bwl := len(e.BeginWith) + if string(e.Bytes[:bwl]) != e.BeginWith { + t.Errorf("%s pem should begin with '%s', recieved '%s'", + e.Name, + e.BeginWith, + string(e.Bytes[:bwl]), + ) + } + + ewl := len(e.EndWith) + if string(e.Bytes[len(e.Bytes)-ewl-1:len(e.Bytes)-1]) != e.EndWith{ + t.Errorf("%s pem should end with '%s', recieved '%s'", + e.Name, + e.EndWith, + e.Bytes[len(e.Bytes)-ewl-1:len(e.Bytes)-1], + ) + } + + pb, r := pem.Decode(e.Bytes) + if len(r) != 0 { + t.Errorf("%s pem decode should be empty, remaining bytes %v", e.Name, r) + } + e.PemBlock = pb + } + + _, err = x509.ParseCertificate(expected[0].PemBlock.Bytes) + if err != nil { + t.Error(err) + } + + _, err = x509.ParseECPrivateKey(expected[1].PemBlock.Bytes) + if err != nil { + t.Error(err) + } +} + +func TestDeleteKeyAndCert(t *testing.T) { + err := DeleteKeyAndCert() + if err != nil { + t.Error(err) + } +} \ No newline at end of file diff --git a/app/main.go b/app/main.go new file mode 100644 index 0000000..84c1e32 --- /dev/null +++ b/app/main.go @@ -0,0 +1,48 @@ +package app + +import ( + "bytes" + "crypto/rand" + "crypto/tls" +) + +const ( + KeysDir = "./keys/" + TLSCertName = KeysDir + "tls.crt" + TLSKeyName = KeysDir + "tls.key" +) + +type App struct { + CertPemBytes []byte + KeyPemBytes []byte + TLSConfig tls.Config +} + +func (a *App) Init() error { + return a.generateKeyAndCert() +} + +func (a *App) generateKeyAndCert() error { + crtWriter := bytes.NewBuffer([]byte{}) + keyWriter := bytes.NewBuffer([]byte{}) + + err := GenerateKeyAndCert(crtWriter, keyWriter) + if err != nil { + return err + } + + a.KeyPemBytes = keyWriter.Bytes() + a.CertPemBytes = crtWriter.Bytes() + return nil +} + +func (a *App) MakeTLS() error { + cert, err := tls.X509KeyPair(a.CertPemBytes, a.KeyPemBytes) + if err != nil { + return err + } + + a.TLSConfig = tls.Config{Certificates: []tls.Certificate{cert}, Rand: rand.Reader} + + return nil +} \ No newline at end of file diff --git a/app/peer_discovery_test.go b/app/peer_discovery_test.go new file mode 100644 index 0000000..30e235b --- /dev/null +++ b/app/peer_discovery_test.go @@ -0,0 +1,112 @@ +package app + +import ( + "fmt" + "log" + mrand "math/rand" + "sync" + "testing" + "time" + + "github.com/schollz/peerdiscovery" +) + +func TestPeerDiscovery(t *testing.T) { + //wg := new(sync.WaitGroup) + + for x:=0;x<10;x++{ + t.Logf("attempting discovery %d", x+1) + ds, err := peerdiscovery.Discover(peerdiscovery.Settings{Limit: 1}) + if err != nil { + t.Error(err) + } + + //t.Logf("Read peer list of %s", "peer 1") + for _, d := range ds { + fmt.Printf("discovered '%s'\n", d.Address) + } + } + + /*wg.Add(1) + go peerDiscovery(t, "peer 1", wg) + + wg.Add(1) + go peerDiscovery(t, "peer 2", wg) + */ + + //wg.Wait() +} + +func peerDiscovery(t *testing.T, name string, wg *sync.WaitGroup) { + t.Logf("Begin discovery %s", name) + ds, err := peerdiscovery.Discover(peerdiscovery.Settings{Limit: 1}) + if err != nil { + t.Error(err) + } + + t.Logf("Read peer list of %s", name) + for _, d := range ds { + fmt.Printf("discovered '%s'\n", d.Address) + } + wg.Done() +} + +func TestAnother(t *testing.T) { + fmt.Println("Scanning for 10 seconds to find LAN peers") + pl := randStringBytesMaskImprSrc(10) + fmt.Printf("Payload sending : '%s'\n", pl) + + // discover peers + discoveries, err := peerdiscovery.Discover(peerdiscovery.Settings{ + Limit: -1, + Payload: []byte(pl), + Delay: 500 * time.Millisecond, + TimeLimit: 10 * time.Second, + Notify: func(d peerdiscovery.Discovered) { + log.Println(d) + }, + }) + + // print out results + if err != nil { + log.Fatal(err) + } else { + if len(discoveries) > 0 { + fmt.Printf("Found %d other computers\n", len(discoveries)) + for i, d := range discoveries { + fmt.Printf("%d) '%s' with payload '%s'\n", i, d.Address, d.Payload) + } + } else { + fmt.Println("Found no devices. You need to run this on another computer at the same time.") + } + } +} + +// src is seeds the random generator for generating random strings +var src = mrand.NewSource(time.Now().UnixNano()) + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} diff --git a/app/zmq4_test.go b/app/zmq4_test.go new file mode 100644 index 0000000..4007432 --- /dev/null +++ b/app/zmq4_test.go @@ -0,0 +1,82 @@ +package app + +import ( + "context" + "log" + "testing" + "time" + + "github.com/go-zeromq/zmq4" +) + +func zmq4Req() { + logger := log.New(log.Writer(), "rrclient: ", log.LstdFlags) + + req := zmq4.NewReq(context.Background()) + defer req.Close() + + err := req.Dial("tcp://localhost:5559") + if err != nil { + logger.Fatalf("could not dial: %v", err) + } + + for i := 0; i < 10; i++ { + err := req.Send(zmq4.NewMsgString("Hello")) + if err != nil { + logger.Fatalf("could not send greeting: %v", err) + } + + msg, err := req.Recv() + if err != nil { + logger.Fatalf("could not recv greeting: %v", err) + } + logger.Printf("received reply %d [%s]\n", i, msg.Frames[0]) + } +} + +func TestZMQ4Req(t *testing.T) { + zmq4Req() +} + +func zmq4Rep() { + logger := log.New(log.Writer(), "rrworker: ", log.LstdFlags) + + // Socket to talk to clients + rep := zmq4.NewRep(context.Background()) + defer rep.Close() + + err := rep.Listen("tcp://*:5559") + if err != nil { + logger.Fatalf("could not dial: %v", err) + } + + for { + // Wait for next request from client + msg, err := rep.Recv() + if err != nil { + logger.Fatalf("could not recv request: %v", err) + } + + logger.Printf("received request: [%s]\n", msg.Frames[0]) + + // Do some 'work' + time.Sleep(time.Second) + + // Send reply back to client + err = rep.Send(zmq4.NewMsgString("World")) + if err != nil { + logger.Fatalf("could not send reply: %v", err) + } + } +} + +func TestZMQ4Rep(t *testing.T) { + zmq4Rep() +} + +func TestZMQ4ReqRep(t *testing.T){ + go zmq4Rep() + go zmq4Req() + + time.Sleep(15 * time.Second) +} diff --git a/app/zyre_test.go b/app/zyre_test.go new file mode 100644 index 0000000..7554194 --- /dev/null +++ b/app/zyre_test.go @@ -0,0 +1,72 @@ +package app + +import ( + "context" + "github.com/davecgh/go-spew/spew" + "github.com/go-zeromq/zyre" + "sync" + "testing" + "time" +) + +func TestZyre(t *testing.T) { + z1, cncl1 := makeNode() + defer cncl1() + + z2, cncl2 := makeNode() + defer cncl2() + + z1.SetVerbose() + z2.SetVerbose() + + err := z1.Start() + if err != nil { + spew.Dump(err) + } + + defer z1.Stop() + + err = z2.Start() + if err != nil { + spew.Dump(err) + } + + defer z2.Stop() + + wg := new(sync.WaitGroup) + //wg.Add(1) + go watchEvents(t, wg, z1) + //wg.Add(1) + go watchEvents(t, wg, z2) + + z1.Join("TALK") + z2.Join("TALK") + + time.Sleep(time.Second) + z1.Shout("TALK", []byte("I am here")) + time.Sleep(time.Second) + z1.Shout("TALK", []byte("Hi world")) + + //wg.Wait() + time.Sleep(time.Second) + + spew.Dump(z1.Peers(), z2.Peers()) +} + +func makeNode() (*zyre.Zyre, context.CancelFunc) { + ctx, cncl := context.WithCancel(context.Background()) + z := zyre.NewZyre(ctx) + + return z, cncl +} + +func watchEvents(t *testing.T, wg *sync.WaitGroup, z *zyre.Zyre) { + select { + case e := <- z.Events(): + t.Log(spew.Sdump(e)) + if string(e.Msg) == "Hi world" { + //wg.Done() + return + } + } +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..226bc7d --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +package main + +import "github.com/status-im/tcp-pair-sync-prototype/app" + +func main() { + a := new(app.App) + err := a.Init() + if err != nil { + panic(err) + } +} diff --git a/main/main.go b/main/main.go new file mode 100644 index 0000000..77b5751 --- /dev/null +++ b/main/main.go @@ -0,0 +1,56 @@ +// Copyright 2020 The go-zeromq Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( +"bufio" +"context" +"flag" +"fmt" +"log" +"os" + +"github.com/go-zeromq/zyre" +) + +func chat(ctx context.Context, input <-chan string, name string) { + node := zyre.NewZyre(ctx) + defer node.Stop() + + err := node.Start() + if err != nil { + log.Fatalln(err) + } + node.Join("CHAT") + + for { + select { + case e := <-node.Events(): + fmt.Printf("\r%s%s> ", string(e.Msg), name) + case msg := <-input: + node.Shout("CHAT", []byte(msg)) + } + } +} + +func main() { + input := make(chan string) + name := flag.String("name", "Zyre", "Your name in the chat session") + flag.Parse() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go chat(ctx, input, *name) + fmt.Printf("%s> ", *name) + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + input <- fmt.Sprintf("%s: %s\n", *name, scanner.Text()) + fmt.Printf("%s> ", *name) + } + if err := scanner.Err(); err != nil { + log.Fatalln("reading standard input:", err) + } +}