aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBernd Fix <brf@hoi-polloi.org>2022-06-01 20:59:52 +0200
committerBernd Fix <brf@hoi-polloi.org>2022-06-01 20:59:52 +0200
commit913c80f317270b41f339048afa5d63278eecf8f4 (patch)
tree7ef1db049af2f4b009d97ab7240bb4744be9dfb6 /src
parent205cad60026bf0af1cd2712a8faa4bce08eafb1d (diff)
downloadgnunet-go-913c80f317270b41f339048afa5d63278eecf8f4.tar.gz
gnunet-go-913c80f317270b41f339048afa5d63278eecf8f4.zip
Reworked gnunet/transport and gnunet/service.
Diffstat (limited to 'src')
-rw-r--r--src/cmd/peer_mockup/main.go68
-rw-r--r--src/cmd/peer_mockup/peers.go46
-rw-r--r--src/cmd/peer_mockup/process.go128
-rw-r--r--src/cmd/revoke-zonekey/main.go202
-rwxr-xr-xsrc/gnunet/build.sh3
-rw-r--r--src/gnunet/cmd/.gitignore (renamed from src/cmd/.gitignore)0
-rw-r--r--src/gnunet/cmd/gnunet-service-dht-test-go/main.go152
-rw-r--r--src/gnunet/cmd/gnunet-service-gns-go/main.go (renamed from src/cmd/gnunet-service-gns-go/main.go)31
-rw-r--r--src/gnunet/cmd/gnunet-service-revocation-go/main.go (renamed from src/cmd/gnunet-service-revocation-go/main.go)29
-rw-r--r--src/gnunet/cmd/peer_mockup/main.go178
-rw-r--r--src/gnunet/cmd/revoke-zonekey/main.go336
-rw-r--r--src/gnunet/cmd/vanityid/main.go (renamed from src/cmd/vanityid/main.go)0
-rw-r--r--src/gnunet/config/config.go68
-rw-r--r--src/gnunet/config/gnunet-config.json48
-rw-r--r--src/gnunet/core/core.go234
-rw-r--r--src/gnunet/core/core_test.go150
-rw-r--r--src/gnunet/core/event.go109
-rw-r--r--src/gnunet/core/peer.go108
-rw-r--r--src/gnunet/core/peer_test.go72
-rw-r--r--src/gnunet/crypto/gns.go9
-rw-r--r--src/gnunet/crypto/hash.go16
-rw-r--r--src/gnunet/go.mod26
-rw-r--r--src/gnunet/go.sum128
-rw-r--r--src/gnunet/message/factory.go4
-rw-r--r--src/gnunet/message/message.go8
-rw-r--r--src/gnunet/message/msg_dht.go95
-rw-r--r--src/gnunet/message/msg_gns.go94
-rw-r--r--src/gnunet/message/msg_hello.go103
-rw-r--r--src/gnunet/message/msg_namecache.go12
-rw-r--r--src/gnunet/message/msg_transport.go82
-rw-r--r--src/gnunet/modules.go28
-rw-r--r--src/gnunet/service/client.go30
-rw-r--r--src/gnunet/service/connection.go280
-rw-r--r--src/gnunet/service/context.go87
-rw-r--r--src/gnunet/service/dht/blocks/generic.go196
-rw-r--r--src/gnunet/service/dht/blocks/generic_test.go67
-rw-r--r--src/gnunet/service/dht/blocks/gns.go172
-rw-r--r--src/gnunet/service/dht/blocks/hello.go226
-rw-r--r--src/gnunet/service/dht/blocks/hello_test.go44
-rw-r--r--src/gnunet/service/dht/blocks/types.go (renamed from src/gnunet/transport/session.go)15
-rw-r--r--src/gnunet/service/dht/bloomfilter.go123
-rw-r--r--src/gnunet/service/dht/dhtstore_test.go89
-rw-r--r--src/gnunet/service/dht/module.go99
-rw-r--r--src/gnunet/service/dht/routingtable.go305
-rw-r--r--src/gnunet/service/dht/routingtable_test.go140
-rw-r--r--src/gnunet/service/dht/service.go134
-rw-r--r--src/gnunet/service/gns/block_handler.go18
-rw-r--r--src/gnunet/service/gns/dns.go4
-rw-r--r--src/gnunet/service/gns/module.go135
-rw-r--r--src/gnunet/service/gns/service.go235
-rw-r--r--src/gnunet/service/module.go70
-rw-r--r--src/gnunet/service/namecache/module.go31
-rw-r--r--src/gnunet/service/revocation/module.go100
-rw-r--r--src/gnunet/service/revocation/pow.go46
-rw-r--r--src/gnunet/service/revocation/pow_test.go8
-rw-r--r--src/gnunet/service/revocation/service.go192
-rw-r--r--src/gnunet/service/service.go200
-rw-r--r--src/gnunet/service/store.go379
-rwxr-xr-xsrc/gnunet/test.sh3
-rw-r--r--src/gnunet/transport/channel.go213
-rw-r--r--src/gnunet/transport/channel_netw.go285
-rw-r--r--src/gnunet/transport/channel_test.go232
-rw-r--r--src/gnunet/transport/connection.go108
-rw-r--r--src/gnunet/transport/endpoint.go282
-rw-r--r--src/gnunet/transport/reader_writer.go157
-rw-r--r--src/gnunet/transport/transport.go151
-rw-r--r--src/gnunet/util/address.go141
-rw-r--r--src/gnunet/util/array.go65
-rw-r--r--src/gnunet/util/database.go159
-rw-r--r--src/gnunet/util/fs.go2
-rw-r--r--src/gnunet/util/key_value_store.go188
-rw-r--r--src/gnunet/util/misc.go75
-rw-r--r--src/gnunet/util/peer_id.go11
-rw-r--r--src/gnunet/util/time.go17
74 files changed, 5686 insertions, 2395 deletions
diff --git a/src/cmd/peer_mockup/main.go b/src/cmd/peer_mockup/main.go
deleted file mode 100644
index 59bc002..0000000
--- a/src/cmd/peer_mockup/main.go
+++ /dev/null
@@ -1,68 +0,0 @@
1package main
2
3import (
4 "encoding/hex"
5 "flag"
6 "fmt"
7
8 "github.com/bfix/gospel/logger"
9 "gnunet/core"
10 "gnunet/transport"
11)
12
13var (
14 p *core.Peer // local peer (with private key)
15 t *core.Peer // remote peer
16)
17
18func main() {
19 // handle command line arguments
20 var (
21 asServer bool
22 err error
23 ch transport.Channel
24 )
25 flag.BoolVar(&asServer, "s", false, "accept incoming connections")
26 flag.Parse()
27
28 // setup peer instances from static data
29 if err = setupPeers(false); err != nil {
30 fmt.Println(err.Error())
31 return
32 }
33
34 fmt.Println("======================================================================")
35 fmt.Println("GNUnet peer mock-up (EXPERIMENTAL) (c) 2018,2019 by Bernd Fix, >Y<")
36 fmt.Printf(" Identity '%s'\n", p.GetIDString())
37 fmt.Printf(" [%s]\n", hex.EncodeToString(p.GetID()))
38 fmt.Println("======================================================================")
39
40 if asServer {
41 // run as server
42 fmt.Println("Waiting for connections...")
43 hdlr := make(chan transport.Channel)
44 go func() {
45 for {
46 select {
47 case ch = <-hdlr:
48 mc := transport.NewMsgChannel(ch)
49 if err = process(mc, t, p); err != nil {
50 logger.Println(logger.ERROR, err.Error())
51 }
52 }
53 }
54 }()
55 _, err = transport.NewChannelServer("tcp+0.0.0.0:2086", hdlr)
56 } else {
57 // connect to peer
58 fmt.Println("Connecting to target peer")
59 if ch, err = transport.NewChannel("tcp+172.17.0.5:2086"); err != nil {
60 logger.Println(logger.ERROR, err.Error())
61 }
62 mc := transport.NewMsgChannel(ch)
63 err = process(mc, p, t)
64 }
65 if err != nil {
66 fmt.Println(err)
67 }
68}
diff --git a/src/cmd/peer_mockup/peers.go b/src/cmd/peer_mockup/peers.go
deleted file mode 100644
index 16d3c87..0000000
--- a/src/cmd/peer_mockup/peers.go
+++ /dev/null
@@ -1,46 +0,0 @@
1package main
2
3import (
4 "github.com/bfix/gospel/data"
5 "gnunet/core"
6 "gnunet/util"
7)
8
9func setupPeers(rnd bool) (err error) {
10
11 //------------------------------------------------------------------
12 // create local peer
13 //------------------------------------------------------------------
14 secret := []byte{
15 0x78, 0xde, 0xcf, 0xc0, 0x26, 0x9e, 0x62, 0x3d,
16 0x17, 0x24, 0xe6, 0x1b, 0x98, 0x25, 0xec, 0x2f,
17 0x40, 0x6b, 0x1e, 0x39, 0xa5, 0x19, 0xac, 0x9b,
18 0xb2, 0xdd, 0xf4, 0x6c, 0x12, 0x83, 0xdb, 0x86,
19 }
20 if rnd {
21 util.RndArray(secret)
22 }
23 p, err = core.NewPeer(secret, true)
24 if err != nil {
25 return
26 }
27 addr, _ := data.Marshal(util.NewIPAddress([]byte{172, 17, 0, 6}, 2086))
28 p.AddAddress(util.NewAddress("tcp", addr))
29
30 //------------------------------------------------------------------
31 // create remote peer
32 //------------------------------------------------------------------
33 id := []byte{
34 0x92, 0xdc, 0xbf, 0x39, 0x40, 0x2d, 0xc6, 0x3c,
35 0x97, 0xa6, 0x81, 0xe0, 0xfc, 0xd8, 0x7c, 0x74,
36 0x17, 0xd3, 0xa3, 0x8c, 0x52, 0xfd, 0xe0, 0x49,
37 0xbc, 0xd0, 0x1c, 0x0a, 0x0b, 0x8c, 0x02, 0x51,
38 }
39 t, err = core.NewPeer(id, false)
40 if err != nil {
41 return
42 }
43 addr, _ = data.Marshal(util.NewIPAddress([]byte{172, 17, 0, 5}, 2086))
44 t.AddAddress(util.NewAddress("tcp", addr))
45 return
46}
diff --git a/src/cmd/peer_mockup/process.go b/src/cmd/peer_mockup/process.go
deleted file mode 100644
index 510fc48..0000000
--- a/src/cmd/peer_mockup/process.go
+++ /dev/null
@@ -1,128 +0,0 @@
1package main
2
3import (
4 "errors"
5 "fmt"
6
7 "gnunet/core"
8 "gnunet/crypto"
9 "gnunet/message"
10 "gnunet/transport"
11 "gnunet/util"
12
13 "github.com/bfix/gospel/concurrent"
14)
15
16var (
17 sig = concurrent.NewSignaller()
18)
19
20func process(ch *transport.MsgChannel, from, to *core.Peer) (err error) {
21 // create a new connection instance
22 c := transport.NewConnection(ch, from, to)
23 defer c.Close()
24
25 // read and push next message
26 in := make(chan message.Message)
27 go func() {
28 for {
29 msg, err := c.Receive(sig)
30 if err != nil {
31 fmt.Printf("Receive: %s\n", err.Error())
32 return
33 }
34 in <- msg
35 }
36 }()
37
38 // are we initiating the connection?
39 init := (from == p)
40 if init {
41 peerid := util.NewPeerID(p.GetID())
42 c.Send(message.NewTransportTcpWelcomeMsg(peerid), sig)
43 }
44
45 // remember peer addresses (only ONE!)
46 pAddr := p.GetAddressList()[0]
47 tAddr := t.GetAddressList()[0]
48
49 send := make(map[uint16]bool)
50 //received := make(map[uint16]bool)
51 pending := make(map[uint16]message.Message)
52
53 // process loop
54 for {
55 select {
56 case m := <-in:
57 switch msg := m.(type) {
58
59 case *message.TransportTcpWelcomeMsg:
60 peerid := util.NewPeerID(p.GetID())
61 if init {
62 c.Send(message.NewHelloMsg(peerid), sig)
63 target := util.NewPeerID(t.GetID())
64 c.Send(message.NewTransportPingMsg(target, tAddr), sig)
65 } else {
66 c.Send(message.NewTransportTcpWelcomeMsg(peerid), sig)
67 }
68
69 case *message.HelloMsg:
70
71 case *message.TransportPingMsg:
72 mOut := message.NewTransportPongMsg(msg.Challenge, pAddr)
73 if err := mOut.Sign(p.PrvKey()); err != nil {
74 return err
75 }
76 c.Send(mOut, sig)
77
78 case *message.TransportPongMsg:
79 rc, err := msg.Verify(t.PubKey())
80 if err != nil {
81 return err
82 }
83 if !rc {
84 return errors.New("PONG verification failed")
85 }
86 send[message.TRANSPORT_PONG] = true
87 if mOut, ok := pending[message.TRANSPORT_SESSION_SYN]; ok {
88 c.Send(mOut, sig)
89 }
90
91 case *message.SessionSynMsg:
92 mOut := message.NewSessionSynAckMsg()
93 mOut.Timestamp = msg.Timestamp
94 if send[message.TRANSPORT_PONG] {
95 c.Send(mOut, sig)
96 } else {
97 pending[message.TRANSPORT_SESSION_SYN] = mOut
98 }
99
100 case *message.SessionQuotaMsg:
101 c.SetBandwidth(msg.Quota)
102
103 case *message.SessionAckMsg:
104
105 case *message.SessionKeepAliveMsg:
106 c.Send(message.NewSessionKeepAliveRespMsg(msg.Nonce), sig)
107
108 case *message.EphemeralKeyMsg:
109 rc, err := msg.Verify(t.PubKey())
110 if err != nil {
111 return err
112 }
113 if !rc {
114 return errors.New("EPHKEY verification failed")
115 }
116 t.SetEphKeyMsg(msg)
117 c.Send(p.EphKeyMsg(), sig)
118 secret := crypto.SharedSecret(p.EphPrvKey(), t.EphKeyMsg().Public())
119 c.SharedSecret(util.Clone(secret.Bits[:]))
120
121 default:
122 fmt.Printf("!!! %v\n", msg)
123 }
124 default:
125 }
126 }
127 return nil
128}
diff --git a/src/cmd/revoke-zonekey/main.go b/src/cmd/revoke-zonekey/main.go
deleted file mode 100644
index 7deeda6..0000000
--- a/src/cmd/revoke-zonekey/main.go
+++ /dev/null
@@ -1,202 +0,0 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package main
20
21import (
22 "context"
23 "encoding/hex"
24 "flag"
25 "log"
26 "os"
27 "os/signal"
28 "sync"
29 "syscall"
30
31 "gnunet/service/revocation"
32 "gnunet/util"
33
34 "github.com/bfix/gospel/data"
35)
36
37func main() {
38 log.Println("*** Compute revocation data for a zone key")
39 log.Println("*** Copyright (c) 2020, Bernd Fix >Y<")
40 log.Println("*** This is free software distributed under the Affero GPL v3.")
41
42 // handle command line arguments
43 var (
44 verbose bool // be verbose with messages
45 bits int // number of leading zero-bit requested
46 zonekey string // zonekey to be revoked
47 filename string // name of file for persistance
48 )
49 flag.IntVar(&bits, "b", 25, "Number of leading zero bits")
50 flag.BoolVar(&verbose, "v", false, "verbose output")
51 flag.StringVar(&zonekey, "z", "", "Zone key to be revoked")
52 flag.StringVar(&filename, "f", "", "Name of file to store revocation")
53 flag.Parse()
54
55 if len(filename) == 0 {
56 log.Fatal("Missing '-f' argument (filename fot revocation data)")
57 }
58
59 // define layout of persistant data
60 var revData struct {
61 Rd *revocation.RevDataCalc // Revocation data
62 T util.RelativeTime // time spend in calculations
63 Last uint64 // last value used for PoW test
64 Numbits uint8 // number of leading zero-bits
65 }
66 dataBuf := make([]byte, 450)
67
68 // read revocation object from file
69 file, err := os.Open(filename)
70 cont := true
71 if err != nil {
72 if len(zonekey) != 52 {
73 log.Fatal("Missing or invalid zonekey and no file specified -- aborting")
74 }
75 keyData, err := util.DecodeStringToBinary(zonekey, 32)
76 if err != nil {
77 log.Fatal("Invalid zonekey: " + err.Error())
78 }
79 revData.Rd = revocation.NewRevDataCalc(keyData)
80 revData.Numbits = uint8(bits)
81 revData.T = util.NewRelativeTime(0)
82 cont = false
83 } else {
84 n, err := file.Read(dataBuf)
85 if err != nil {
86 log.Fatal("Error reading file: " + err.Error())
87 }
88 if n != len(dataBuf) {
89 log.Fatal("File corrupted -- aborting")
90 }
91 if err = data.Unmarshal(&revData, dataBuf); err != nil {
92 log.Fatal("File corrupted: " + err.Error())
93 }
94 bits = int(revData.Numbits)
95 if err = file.Close(); err != nil {
96 log.Fatal("Error closing file: " + err.Error())
97 }
98 }
99
100 if cont {
101 log.Printf("Revocation calculation started at %s\n", revData.Rd.Timestamp.String())
102 log.Printf("Time spent on calculation: %s\n", revData.T.String())
103 log.Printf("Last tested PoW value: %d\n", revData.Last)
104 log.Println("Continuing...")
105 } else {
106 log.Println("Starting new revocation calculation...")
107 }
108 log.Println("Press ^C to abort...")
109
110 // pre-set difficulty
111 log.Printf("Difficulty: %d\n", bits)
112 if bits < 25 {
113 log.Println("WARNING: difficulty is less than 25!")
114 }
115
116 // Start or continue calculation
117 ctx, cancelFcn := context.WithCancel(context.Background())
118 wg := new(sync.WaitGroup)
119 wg.Add(1)
120 go func() {
121 defer wg.Done()
122 cb := func(average float64, last uint64) {
123 log.Printf("Improved PoW: %f average zero bits, %d steps\n", average, last)
124 }
125
126 startTime := util.AbsoluteTimeNow()
127 average, last := revData.Rd.Compute(ctx, bits, revData.Last, cb)
128 if average < float64(bits) {
129 log.Printf("Incomplete revocation: Only %f zero bits on average!\n", average)
130 } else {
131 log.Println("Revocation data object:")
132 log.Println(" 0x" + hex.EncodeToString(revData.Rd.Blob()))
133 log.Println("Status:")
134 rc := revData.Rd.Verify(false)
135 switch {
136 case rc == -1:
137 log.Println(" Missing/invalid signature")
138 case rc == -2:
139 log.Println(" Expired revocation")
140 case rc == -3:
141 log.Println(" Wrong PoW sequence order")
142 case rc < 25:
143 log.Println(" Difficulty to small")
144 default:
145 log.Printf(" Difficulty: %d\n", rc)
146 }
147 }
148 if !cont || last != revData.Last {
149 revData.Last = last
150 revData.T = util.AbsoluteTimeNow().Diff(startTime)
151
152 log.Println("Writing revocation data to file...")
153 file, err := os.Create(filename)
154 if err != nil {
155 log.Fatal("Can't write to output file: " + err.Error())
156 }
157 buf, err := data.Marshal(&revData)
158 if err != nil {
159 log.Fatal("Internal error: " + err.Error())
160 }
161 if len(buf) != len(dataBuf) {
162 log.Fatalf("Internal error: Buffer mismatch %d != %d", len(buf), len(dataBuf))
163 }
164 n, err := file.Write(buf)
165 if err != nil {
166 log.Fatal("Can't write to output file: " + err.Error())
167 }
168 if n != len(dataBuf) {
169 log.Fatal("Can't write data to output file!")
170 }
171 if err = file.Close(); err != nil {
172 log.Fatal("Error closing file: " + err.Error())
173 }
174 }
175 }()
176
177 go func() {
178 // handle OS signals
179 sigCh := make(chan os.Signal, 5)
180 signal.Notify(sigCh)
181 loop:
182 for {
183 select {
184 // handle OS signals
185 case sig := <-sigCh:
186 switch sig {
187 case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
188 log.Printf("Terminating (on signal '%s')\n", sig)
189 cancelFcn()
190 break loop
191 case syscall.SIGHUP:
192 log.Println("SIGHUP")
193 case syscall.SIGURG:
194 // TODO: https://github.com/golang/go/issues/37942
195 default:
196 log.Println("Unhandled signal: " + sig.String())
197 }
198 }
199 }
200 }()
201 wg.Wait()
202}
diff --git a/src/gnunet/build.sh b/src/gnunet/build.sh
new file mode 100755
index 0000000..5ec677f
--- /dev/null
+++ b/src/gnunet/build.sh
@@ -0,0 +1,3 @@
1#!/bin/bash
2
3go install -v -gcflags "-N -l" ./...
diff --git a/src/cmd/.gitignore b/src/gnunet/cmd/.gitignore
index 1933786..1933786 100644
--- a/src/cmd/.gitignore
+++ b/src/gnunet/cmd/.gitignore
diff --git a/src/gnunet/cmd/gnunet-service-dht-test-go/main.go b/src/gnunet/cmd/gnunet-service-dht-test-go/main.go
new file mode 100644
index 0000000..cd2bd1a
--- /dev/null
+++ b/src/gnunet/cmd/gnunet-service-dht-test-go/main.go
@@ -0,0 +1,152 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package main
20
21import (
22 "context"
23 "flag"
24 "os"
25 "os/signal"
26 "strings"
27 "syscall"
28 "time"
29
30 "gnunet/config"
31 "gnunet/core"
32 "gnunet/rpc"
33 "gnunet/service"
34 "gnunet/service/dht"
35
36 "github.com/bfix/gospel/logger"
37)
38
39func main() {
40 defer func() {
41 logger.Println(logger.INFO, "[dht] Bye.")
42 // flush last messages
43 logger.Flush()
44 }()
45 logger.Println(logger.INFO, "[dht] Starting service...")
46
47 var (
48 cfgFile string
49 socket string
50 param string
51 err error
52 logLevel int
53 rpcEndp string
54 )
55 // handle command line arguments
56 flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet configuration file")
57 flag.StringVar(&socket, "s", "", "GNS service socket")
58 flag.StringVar(&param, "p", "", "socket parameters (<key>=<value>,...)")
59 flag.IntVar(&logLevel, "L", logger.INFO, "DHT log level (default: INFO)")
60 flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
61 flag.Parse()
62
63 // read configuration file and set missing arguments.
64 if err = config.ParseConfig(cfgFile); err != nil {
65 logger.Printf(logger.ERROR, "[dht] Invalid configuration file: %s\n", err.Error())
66 return
67 }
68
69 // apply configuration
70 logger.SetLogLevel(logLevel)
71 if len(socket) == 0 {
72 socket = config.Cfg.GNS.Service.Socket
73 }
74 params := make(map[string]string)
75 if len(param) == 0 {
76 for _, p := range strings.Split(param, ",") {
77 kv := strings.SplitN(p, "=", 2)
78 params[kv[0]] = kv[1]
79 }
80 } else {
81 params = config.Cfg.GNS.Service.Params
82 }
83
84 // instantiate core service
85 ctx, cancel := context.WithCancel(context.Background())
86 var local *core.Peer
87 if local, err = core.NewLocalPeer(config.Cfg.Local); err != nil {
88 logger.Printf(logger.ERROR, "[dht] No local peer: %s\n", err.Error())
89 return
90 }
91 var c *core.Core
92 if c, err = core.NewCore(ctx, local); err != nil {
93 logger.Printf(logger.ERROR, "[dht] core failed: %s\n", err.Error())
94 return
95 }
96
97 // start a new DHT service
98 dht := dht.NewService(ctx, c)
99 srv := service.NewSocketHandler("dht", dht)
100 if err = srv.Start(ctx, socket, params); err != nil {
101 logger.Printf(logger.ERROR, "[dht] Failed to start DHT service: '%s'", err.Error())
102 return
103 }
104
105 // start JSON-RPC server on request
106 if len(rpcEndp) > 0 {
107 parts := strings.Split(rpcEndp, ":")
108 if parts[0] != "tcp" {
109 logger.Println(logger.ERROR, "[dht] RPC must have a TCP/IP endpoint")
110 return
111 }
112 config.Cfg.RPC.Endpoint = parts[1]
113 if err = rpc.Start(ctx); err != nil {
114 logger.Printf(logger.ERROR, "[dht] RPC failed to start: %s", err.Error())
115 return
116 }
117 rpc.Register(dht)
118 }
119
120 // handle OS signals
121 sigCh := make(chan os.Signal, 5)
122 signal.Notify(sigCh)
123
124 // heart beat
125 tick := time.NewTicker(5 * time.Minute)
126
127loop:
128 for {
129 select {
130 // handle OS signals
131 case sig := <-sigCh:
132 switch sig {
133 case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
134 logger.Printf(logger.INFO, "[dht] Terminating service (on signal '%s')\n", sig)
135 break loop
136 case syscall.SIGHUP:
137 logger.Println(logger.INFO, "[dht] SIGHUP")
138 case syscall.SIGURG:
139 // TODO: https://github.com/golang/go/issues/37942
140 default:
141 logger.Println(logger.INFO, "[dht] Unhandled signal: "+sig.String())
142 }
143 // handle heart beat
144 case now := <-tick.C:
145 logger.Println(logger.INFO, "[dht] Heart beat at "+now.String())
146 }
147 }
148
149 // terminating service
150 cancel()
151 srv.Stop()
152}
diff --git a/src/cmd/gnunet-service-gns-go/main.go b/src/gnunet/cmd/gnunet-service-gns-go/main.go
index 57eec7e..6eb027b 100644
--- a/src/cmd/gnunet-service-gns-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-gns-go/main.go
@@ -45,14 +45,16 @@ func main() {
45 45
46 var ( 46 var (
47 cfgFile string 47 cfgFile string
48 srvEndp string 48 socket string
49 param string
49 err error 50 err error
50 logLevel int 51 logLevel int
51 rpcEndp string 52 rpcEndp string
52 ) 53 )
53 // handle command line arguments 54 // handle command line arguments
54 flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet configuration file") 55 flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet configuration file")
55 flag.StringVar(&srvEndp, "s", "", "GNS service end-point") 56 flag.StringVar(&socket, "s", "", "GNS service socket")
57 flag.StringVar(&param, "p", "", "socket parameters (<key>=<value>,...)")
56 flag.IntVar(&logLevel, "L", logger.INFO, "GNS log level (default: INFO)") 58 flag.IntVar(&logLevel, "L", logger.INFO, "GNS log level (default: INFO)")
57 flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)") 59 flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
58 flag.Parse() 60 flag.Parse()
@@ -63,26 +65,33 @@ func main() {
63 return 65 return
64 } 66 }
65 67
66 // apply configuration 68 // apply configuration (from file and command-line)
67 logger.SetLogLevel(logLevel) 69 logger.SetLogLevel(logLevel)
68 if len(srvEndp) == 0 { 70 if len(socket) == 0 {
69 srvEndp = config.Cfg.GNS.Endpoint 71 socket = config.Cfg.GNS.Service.Socket
72 }
73 params := make(map[string]string)
74 if len(param) == 0 {
75 for _, p := range strings.Split(param, ",") {
76 kv := strings.SplitN(p, "=", 2)
77 params[kv[0]] = kv[1]
78 }
79 } else {
80 params = config.Cfg.GNS.Service.Params
70 } 81 }
71 82
72 // start a new GNS service 83 // start a new GNS service
84 ctx, cancel := context.WithCancel(context.Background())
73 gns := gns.NewService() 85 gns := gns.NewService()
74 srv := service.NewServiceImpl("gns", gns) 86 srv := service.NewSocketHandler("gns", gns)
75 if err = srv.Start(srvEndp); err != nil { 87 if err = srv.Start(ctx, socket, params); err != nil {
76 logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error()) 88 logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error())
77 return 89 return
78 } 90 }
79 91
80 // start JSON-RPC server on request 92 // start JSON-RPC server on request
81 var cancel func() = func() {}
82 if len(rpcEndp) > 0 { 93 if len(rpcEndp) > 0 {
83 var ctx context.Context 94 parts := strings.Split(rpcEndp, ":")
84 ctx, cancel = context.WithCancel(context.Background())
85 parts := strings.Split(rpcEndp, "+")
86 if parts[0] != "tcp" { 95 if parts[0] != "tcp" {
87 logger.Println(logger.ERROR, "[gns] RPC must have a TCP/IP endpoint") 96 logger.Println(logger.ERROR, "[gns] RPC must have a TCP/IP endpoint")
88 return 97 return
diff --git a/src/cmd/gnunet-service-revocation-go/main.go b/src/gnunet/cmd/gnunet-service-revocation-go/main.go
index ec5ce73..e21732c 100644
--- a/src/cmd/gnunet-service-revocation-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-revocation-go/main.go
@@ -45,14 +45,16 @@ func main() {
45 45
46 var ( 46 var (
47 cfgFile string 47 cfgFile string
48 srvEndp string 48 socket string
49 param string
49 err error 50 err error
50 logLevel int 51 logLevel int
51 rpcEndp string 52 rpcEndp string
52 ) 53 )
53 // handle command line arguments 54 // handle command line arguments
54 flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet configuration file") 55 flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet configuration file")
55 flag.StringVar(&srvEndp, "s", "", "REVOCATION service end-point") 56 flag.StringVar(&socket, "s", "", "GNS service socket")
57 flag.StringVar(&param, "p", "", "socket parameters (<key>=<value>,...)")
56 flag.IntVar(&logLevel, "L", logger.INFO, "REVOCATION log level (default: INFO)") 58 flag.IntVar(&logLevel, "L", logger.INFO, "REVOCATION log level (default: INFO)")
57 flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)") 59 flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
58 flag.Parse() 60 flag.Parse()
@@ -65,24 +67,31 @@ func main() {
65 67
66 // apply configuration 68 // apply configuration
67 logger.SetLogLevel(logLevel) 69 logger.SetLogLevel(logLevel)
68 if len(srvEndp) == 0 { 70 if len(socket) == 0 {
69 srvEndp = config.Cfg.GNS.Endpoint 71 socket = config.Cfg.GNS.Service.Socket
72 }
73 params := make(map[string]string)
74 if len(param) == 0 {
75 for _, p := range strings.Split(param, ",") {
76 kv := strings.SplitN(p, "=", 2)
77 params[kv[0]] = kv[1]
78 }
79 } else {
80 params = config.Cfg.GNS.Service.Params
70 } 81 }
71 82
72 // start a new REVOCATION service 83 // start a new REVOCATION service
84 ctx, cancel := context.WithCancel(context.Background())
73 rvc := revocation.NewService() 85 rvc := revocation.NewService()
74 srv := service.NewServiceImpl("revocation", rvc) 86 srv := service.NewSocketHandler("revocation", rvc)
75 if err = srv.Start(srvEndp); err != nil { 87 if err = srv.Start(ctx, socket, params); err != nil {
76 logger.Printf(logger.ERROR, "[revocation] Error: '%s'\n", err.Error()) 88 logger.Printf(logger.ERROR, "[revocation] Error: '%s'\n", err.Error())
77 return 89 return
78 } 90 }
79 91
80 // start JSON-RPC server on request 92 // start JSON-RPC server on request
81 var cancel func() = func() {}
82 if len(rpcEndp) > 0 { 93 if len(rpcEndp) > 0 {
83 var ctx context.Context 94 parts := strings.Split(rpcEndp, ":")
84 ctx, cancel = context.WithCancel(context.Background())
85 parts := strings.Split(rpcEndp, "+")
86 if parts[0] != "tcp" { 95 if parts[0] != "tcp" {
87 logger.Println(logger.ERROR, "[revocation] RPC must have a TCP/IP endpoint") 96 logger.Println(logger.ERROR, "[revocation] RPC must have a TCP/IP endpoint")
88 return 97 return
diff --git a/src/gnunet/cmd/peer_mockup/main.go b/src/gnunet/cmd/peer_mockup/main.go
new file mode 100644
index 0000000..4288fb1
--- /dev/null
+++ b/src/gnunet/cmd/peer_mockup/main.go
@@ -0,0 +1,178 @@
1package main
2
3import (
4 "context"
5 "flag"
6 "fmt"
7 "os"
8 "os/signal"
9 "syscall"
10 "time"
11
12 "gnunet/config"
13 "gnunet/core"
14 "gnunet/crypto"
15 "gnunet/message"
16 "gnunet/service"
17
18 "github.com/bfix/gospel/logger"
19)
20
21var (
22 // configuration for local node
23 localCfg = &config.NodeConfig{
24 PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
25 Endpoints: []string{
26 "udp:127.0.0.1:2086",
27 },
28 }
29 // configuration for remote node
30 remoteCfg = "3GXXMNb5YpIUO7ejIR2Yy0Cf5texuLfDjHkXcqbPxkc="
31 remoteAddr = "udp:172.17.0.5:2086"
32
33 // top-level variables used accross functions
34 local *core.Peer // local peer (with private key)
35 remote *core.Peer // remote peer
36 c *core.Core
37 secret *crypto.HashCode
38)
39
40func main() {
41 ctx, cancel := context.WithCancel(context.Background())
42 defer cancel()
43
44 // handle command line arguments
45 var (
46 asServer bool
47 err error
48 )
49 flag.BoolVar(&asServer, "s", false, "wait for incoming connections")
50 flag.Parse()
51
52 // setup peer and core instances
53 if local, err = core.NewLocalPeer(localCfg); err != nil {
54 fmt.Println("local failed: " + err.Error())
55 return
56 }
57 if c, err = core.NewCore(ctx, local); err != nil {
58 fmt.Println("core failed: " + err.Error())
59 return
60 }
61 if remote, err = core.NewPeer(remoteCfg); err != nil {
62 fmt.Println("remote failed: " + err.Error())
63 return
64 }
65
66 fmt.Println("======================================================================")
67 fmt.Println("GNUnet peer mock-up (EXPERIMENTAL) (c) 2018-2022 by Bernd Fix, >Y<")
68 fmt.Printf(" Identity '%s'\n", local.GetIDString())
69 fmt.Printf(" [%s]\n", local.GetID().String())
70 fmt.Println("======================================================================")
71
72 // handle messages coming from network
73 module := service.NewModuleImpl()
74 listener := module.Run(ctx, process, nil)
75 c.Register("mockup", listener)
76
77 if !asServer {
78 // we start the message exchange
79 c.Send(ctx, remote.GetID(), message.NewTransportTCPWelcomeMsg(c.PeerID()))
80 }
81
82 // handle OS signals
83 sigCh := make(chan os.Signal, 5)
84 signal.Notify(sigCh)
85
86 // heart beat
87 tick := time.NewTicker(5 * time.Minute)
88
89loop:
90 for {
91 select {
92 // handle OS signals
93 case sig := <-sigCh:
94 switch sig {
95 case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
96 logger.Printf(logger.INFO, "Terminating service (on signal '%s')\n", sig)
97 break loop
98 case syscall.SIGHUP:
99 logger.Println(logger.INFO, "SIGHUP")
100 case syscall.SIGURG:
101 // TODO: https://github.com/golang/go/issues/37942
102 default:
103 logger.Println(logger.INFO, "Unhandled signal: "+sig.String())
104 }
105 // handle heart beat
106 case now := <-tick.C:
107 logger.Println(logger.INFO, "Heart beat at "+now.String())
108 }
109 }
110 // terminate pending routines
111 cancel()
112}
113
114// process incoming messages and send responses; it is used for protocol exploration only.
115// it tries to mimick the message flow between "real" GNUnet peers.
116func process(ctx context.Context, ev *core.Event) {
117
118 logger.Printf(logger.DBG, "<<< %s", ev.Msg.String())
119
120 switch msg := ev.Msg.(type) {
121
122 case *message.TransportTCPWelcomeMsg:
123 c.Send(ctx, ev.Peer, message.NewTransportPingMsg(ev.Peer, nil))
124
125 case *message.HelloMsg:
126
127 case *message.TransportPingMsg:
128 mOut := message.NewTransportPongMsg(msg.Challenge, nil)
129 if err := mOut.Sign(local.PrvKey()); err != nil {
130 logger.Println(logger.ERROR, "PONG: signing failed")
131 return
132 }
133 c.Send(ctx, ev.Peer, mOut)
134 logger.Printf(logger.DBG, ">>> %s", mOut)
135
136 case *message.TransportPongMsg:
137 rc, err := msg.Verify(remote.PubKey())
138 if err != nil {
139 logger.Println(logger.ERROR, "PONG verification: "+err.Error())
140 }
141 if !rc {
142 logger.Println(logger.ERROR, "PONG verification failed")
143 }
144
145 case *message.SessionSynMsg:
146 mOut := message.NewSessionSynAckMsg()
147 mOut.Timestamp = msg.Timestamp
148 c.Send(ctx, ev.Peer, mOut)
149 logger.Printf(logger.DBG, ">>> %s", mOut)
150
151 case *message.SessionQuotaMsg:
152
153 case *message.SessionAckMsg:
154
155 case *message.SessionKeepAliveMsg:
156 mOut := message.NewSessionKeepAliveRespMsg(msg.Nonce)
157 c.Send(ctx, ev.Peer, mOut)
158 logger.Printf(logger.DBG, ">>> %s", mOut)
159
160 case *message.EphemeralKeyMsg:
161 rc, err := msg.Verify(remote.PubKey())
162 if err != nil {
163 logger.Println(logger.ERROR, "EPHKEY verification: "+err.Error())
164 return
165 } else if !rc {
166 logger.Println(logger.ERROR, "EPHKEY verification failed")
167 return
168 }
169 remote.SetEphKeyMsg(msg)
170 mOut := local.EphKeyMsg()
171 c.Send(ctx, ev.Peer, mOut)
172 logger.Printf(logger.DBG, ">>> %s", mOut)
173 secret = crypto.SharedSecret(local.EphPrvKey(), remote.EphKeyMsg().Public())
174
175 default:
176 fmt.Printf("!!! %v\n", msg)
177 }
178}
diff --git a/src/gnunet/cmd/revoke-zonekey/main.go b/src/gnunet/cmd/revoke-zonekey/main.go
new file mode 100644
index 0000000..298a7e4
--- /dev/null
+++ b/src/gnunet/cmd/revoke-zonekey/main.go
@@ -0,0 +1,336 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package main
20
21import (
22 "context"
23 "encoding/base64"
24 "flag"
25 "fmt"
26 "log"
27 "os"
28 "os/signal"
29 "sync"
30 "syscall"
31
32 "gnunet/crypto"
33 "gnunet/service/revocation"
34 "gnunet/util"
35
36 "github.com/bfix/gospel/data"
37)
38
39//----------------------------------------------------------------------
40// Data structure used to calculate a valid revocation for a given
41// zone key.
42//----------------------------------------------------------------------
43
44// State of RevData calculation
45const (
46 S_NEW = iota // start new PoW calculation
47 S_CONT // continue PoW calculation
48 S_DONE // PoW calculation done
49 S_SIGNED // revocation data signed
50)
51
52// RevData is the storage layout for persistent data used by this program.
53// Data is read from and written to a file
54type RevData struct {
55 Rd *revocation.RevDataCalc `` // Revocation data
56 T util.RelativeTime `` // time spend in calculations
57 Last uint64 `order:"big"` // last value used for PoW test
58 Numbits uint8 `` // number of leading zero-bits (difficulty)
59 State uint8 `` // processing state
60}
61
62// ReadRevData restores revocation data from perstistent storage. If no
63// stored data is found, a new revocation data structure is returned.
64func ReadRevData(filename string, bits int, zk *crypto.ZoneKey) (rd *RevData, err error) {
65 // create new initialized revocation instance with no PoWs.
66 rd = &RevData{
67 Rd: revocation.NewRevDataCalc(zk),
68 Numbits: uint8(bits),
69 T: util.NewRelativeTime(0),
70 State: S_NEW,
71 }
72
73 // read revocation object from file. If the file does not exist, a new
74 // calculation is started; otherwise the old calculation will continue.
75 var file *os.File
76 if file, err = os.Open(filename); err != nil {
77 return
78 }
79 // read existing file
80 dataBuf := make([]byte, rd.size())
81 var n int
82 if n, err = file.Read(dataBuf); err != nil {
83 err = fmt.Errorf("Error reading file: " + err.Error())
84 return
85 }
86 if n != len(dataBuf) {
87 err = fmt.Errorf("File size mismatch")
88 return
89 }
90 if err = data.Unmarshal(&rd, dataBuf); err != nil {
91 err = fmt.Errorf("File corrupted: " + err.Error())
92 return
93 }
94 if !zk.Equal(&rd.Rd.RevData.ZoneKeySig.ZoneKey) {
95 err = fmt.Errorf("Zone key mismatch")
96 return
97 }
98 bits = int(rd.Numbits)
99 if err = file.Close(); err != nil {
100 err = fmt.Errorf("Error closing file: " + err.Error())
101 }
102 return
103}
104
105// Write revocation data to file
106func (r *RevData) Write(filename string) (err error) {
107 var file *os.File
108 if file, err = os.Create(filename); err != nil {
109 return fmt.Errorf("Can't write to output file: " + err.Error())
110 }
111 var buf []byte
112 if buf, err = data.Marshal(r); err != nil {
113 return fmt.Errorf("Internal error: " + err.Error())
114 }
115 if len(buf) != r.size() {
116 return fmt.Errorf("Internal error: Buffer mismatch %d != %d", len(buf), r.size())
117 }
118 var n int
119 if n, err = file.Write(buf); err != nil {
120 return fmt.Errorf("Can't write to output file: " + err.Error())
121 }
122 if n != len(buf) {
123 return fmt.Errorf("Can't write data to output file!")
124 }
125 if err = file.Close(); err != nil {
126 return fmt.Errorf("Error closing file: " + err.Error())
127 }
128 return
129}
130
131// size of the RevData instance in bytes.
132func (r *RevData) size() int {
133 return 18 + r.Rd.Size()
134}
135
136// revoke-zonekey generates a revocation message in a multi-step/multi-state
137// process run stand-alone from other GNUnet services:
138//
139// (1) Generate the desired PoWs for the public zone key:
140// This process can be started, stopped and resumed, so the long
141// calculation time (usually days or even weeks) can be interruped if
142// desired. For security reasons you should only pass the "-z" argument to
143// this step but not the "-k" argument (private key) as it is not required
144// to calculate the PoWs.
145//
146//
147// (2) A fully generated PoW set can be signed with the private key to create
148// the final revocation data to be send out. This requires to pass the "-k"
149// and "-z" argument.
150//
151// The two steps can be run (sequentially) on separate machines; step one requires
152// computing power nd memory and step two requires a trusted environment.
153func main() {
154 log.Println("*** Compute revocation data for a zone key")
155 log.Println("*** Copyright (c) 2020-2022, Bernd Fix >Y<")
156 log.Println("*** This is free software distributed under the Affero GPL v3.")
157
158 //------------------------------------------------------------------
159 // handle command line arguments
160 //------------------------------------------------------------------
161 var (
162 verbose bool // be verbose with messages
163 bits int // number of leading zero-bit requested
164 zonekey string // zonekey to be revoked
165 prvkey string // private zonekey (base64-encoded key data)
166 testing bool // test mode (no minimum difficulty)
167 filename string // name of file for persistance
168 )
169 minDiff := revocation.MinDifficulty
170 flag.IntVar(&bits, "b", minDiff+1, "Number of leading zero bits")
171 flag.StringVar(&zonekey, "z", "", "Zone key to be revoked (zone ID)")
172 flag.StringVar(&prvkey, "k", "", "Private zone key (base54-encoded)")
173 flag.StringVar(&filename, "f", "", "Name of file to store revocation")
174 flag.BoolVar(&verbose, "v", false, "verbose output")
175 flag.BoolVar(&testing, "t", false, "test-mode only")
176 flag.Parse()
177
178 // check arguments (difficulty, zonekey and filename)
179 if bits < minDiff {
180 if testing {
181 log.Printf("WARNING: difficulty is less than %d!", minDiff)
182 } else {
183 log.Printf("INFO: difficulty set to %d (required minimum)", minDiff)
184 bits = minDiff
185 }
186 }
187 if len(filename) == 0 {
188 log.Fatal("Missing '-f' argument (filename for revocation data)")
189 }
190
191 //------------------------------------------------------------------
192 // Handle zone keys.
193 //------------------------------------------------------------------
194 var (
195 keyData []byte // binary key data
196 zk *crypto.ZoneKey // GNUnet zone key
197 sk *crypto.ZonePrivate // GNUnet private zone key
198 err error
199 )
200 // reconstruct public key
201 if keyData, err = util.DecodeStringToBinary(zonekey, 32); err != nil {
202 log.Fatal("Invalid zonekey encoding: " + err.Error())
203 }
204 if zk, err = crypto.NewZoneKey(keyData); err != nil {
205 log.Fatal("Invalid zonekey format: " + err.Error())
206 }
207 // reconstruct private key (optional)
208 if len(prvkey) > 0 {
209 if keyData, err = base64.StdEncoding.DecodeString(prvkey); err != nil {
210 log.Fatal("Invalid private zonekey encoding: " + err.Error())
211 }
212 if sk, err = crypto.NewZonePrivate(zk.Type, keyData); err != nil {
213 log.Fatal("Invalid zonekey format: " + err.Error())
214 }
215 // verify consistency
216 if !zk.Equal(sk.Public()) {
217 log.Fatal("Public and private zone keys don't match.")
218 }
219 }
220
221 //------------------------------------------------------------------
222 // Read revocation data from file to continue calculation or to sign
223 // the revocation. If no file exists, a new (empty) instance is
224 // returned.
225 //------------------------------------------------------------------
226 rd, err := ReadRevData(filename, bits, zk)
227
228 // handle revocation data state
229 switch rd.State {
230 case S_NEW:
231 log.Println("Starting new revocation calculation...")
232 rd.State = S_CONT
233
234 case S_CONT:
235 log.Printf("Revocation calculation started at %s\n", rd.Rd.Timestamp.String())
236 log.Printf("Time spent on calculation: %s\n", rd.T.String())
237 log.Printf("Last tested PoW value: %d\n", rd.Last)
238 log.Println("Continuing...")
239
240 case S_DONE:
241 // calculation complete: sign with private key
242 if sk == nil {
243 log.Fatal("Need to sign revocation: private key is missing.")
244 }
245 log.Println("Signing revocation with private key")
246 if err := rd.Rd.Sign(sk); err != nil {
247 log.Fatal("Failed to sign revocation: " + err.Error())
248 }
249 // write final revocation
250 rd.State = S_SIGNED
251 if err = rd.Write(filename); err != nil {
252 log.Fatal("Failed to write revocation: " + err.Error())
253 }
254 log.Println("Revocation complete and ready for (later) use.")
255 return
256 }
257 // Continue (or start) calculation
258 log.Println("Press ^C to abort...")
259 log.Printf("Difficulty: %d\n", bits)
260
261 ctx, cancelFcn := context.WithCancel(context.Background())
262 wg := new(sync.WaitGroup)
263 wg.Add(1)
264 go func() {
265 defer wg.Done()
266 // show progress messages
267 cb := func(average float64, last uint64) {
268 log.Printf("Improved PoW: %.2f average zero bits, %d steps\n", average, last)
269 }
270
271 // calculate revocation data until the required difficulty is met
272 // or the process is terminated by the user (by pressing ^C).
273 startTime := util.AbsoluteTimeNow()
274 average, last := rd.Rd.Compute(ctx, bits, rd.Last, cb)
275
276 // check achieved diffiulty (average)
277 if average < float64(bits) {
278 // The calculation was interrupted; we still need to compute
279 // more and better PoWs...
280 log.Printf("Incomplete revocation: Only %f zero bits on average!\n", average)
281 rd.State = S_CONT
282 } else {
283 // we have reached the required PoW difficulty
284 rd.State = S_DONE
285 // check if we have a valid revocation.
286 log.Println("Revocation calculation complete:")
287 diff, rc := rd.Rd.Verify(false)
288 switch {
289 case rc == -1:
290 log.Println(" Missing/invalid signature")
291 case rc == -2:
292 log.Println(" Expired revocation")
293 case rc == -3:
294 log.Println(" Wrong PoW sequence order")
295 case diff < float64(revocation.MinAvgDifficulty):
296 log.Println(" Difficulty to small")
297 default:
298 log.Printf(" Difficulty is %.2f\n", diff)
299 }
300 }
301 // update elapsed time
302 rd.T.Add(util.AbsoluteTimeNow().Diff(startTime))
303 rd.Last = last
304
305 log.Println("Writing revocation data to file...")
306 if err = rd.Write(filename); err != nil {
307 log.Fatal("Can't write to file: " + err.Error())
308 }
309 }()
310
311 go func() {
312 // handle OS signals
313 sigCh := make(chan os.Signal, 5)
314 signal.Notify(sigCh)
315 loop:
316 for {
317 select {
318 // handle OS signals
319 case sig := <-sigCh:
320 switch sig {
321 case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
322 log.Printf("Terminating (on signal '%s')\n", sig)
323 cancelFcn()
324 break loop
325 case syscall.SIGHUP:
326 log.Println("SIGHUP")
327 case syscall.SIGURG:
328 // TODO: https://github.com/golang/go/issues/37942
329 default:
330 log.Println("Unhandled signal: " + sig.String())
331 }
332 }
333 }
334 }()
335 wg.Wait()
336}
diff --git a/src/cmd/vanityid/main.go b/src/gnunet/cmd/vanityid/main.go
index 938df61..938df61 100644
--- a/src/cmd/vanityid/main.go
+++ b/src/gnunet/cmd/vanityid/main.go
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index 914a017..41a65f0 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -28,56 +28,96 @@ import (
28 "github.com/bfix/gospel/logger" 28 "github.com/bfix/gospel/logger"
29) 29)
30 30
31/////////////////////////////////////////////////////////////////////// 31//----------------------------------------------------------------------
32// Configuration for local node
33//----------------------------------------------------------------------
34
35// NodeConfig holds parameters for the local node instance
36type NodeConfig struct {
37 PrivateSeed string `json:"privateSeed"` // Node private key seed (base64)
38 Endpoints []string `json:"endpoints"` // list of endpoints available
39}
40
41//----------------------------------------------------------------------
42// Bootstrap configuration
43//----------------------------------------------------------------------
44
45// BootstrapConfig holds parameters for the initial connection to the network.
46type BootstrapConfig struct {
47 Nodes []string `json:"nodes"` // bootstrap nodes
48}
49
50//----------------------------------------------------------------------
32// RPC configuration 51// RPC configuration
52//----------------------------------------------------------------------
33 53
34// RPCConfig contains parameters for the JSON-RPC service 54// RPCConfig contains parameters for the JSON-RPC service
35type RPCConfig struct { 55type RPCConfig struct {
36 Endpoint string `json:"endpoint"` // end-point of JSON-RPC service 56 Endpoint string `json:"endpoint"` // endpoint for JSON-RPC service
57}
58
59//----------------------------------------------------------------------
60// Generic service endpoint configuration (socket)
61//----------------------------------------------------------------------
62
63type ServiceConfig struct {
64 Socket string `json:"socket"` // socket file name
65 Params map[string]string `json:"params"` // socket parameters
37} 66}
38 67
39/////////////////////////////////////////////////////////////////////// 68//----------------------------------------------------------------------
40// GNS configuration 69// GNS configuration
70//----------------------------------------------------------------------
41 71
42// GNSConfig contains parameters for the GNU Name System service 72// GNSConfig contains parameters for the GNU Name System service
43type GNSConfig struct { 73type GNSConfig struct {
44 Endpoint string `json:"endpoint"` // end-point of GNS service 74 Service *ServiceConfig `json:"service"` // socket for GNS service
45 DHTReplLevel int `json:"dhtReplLevel"` // DHT replication level 75 DHTReplLevel int `json:"dhtReplLevel"` // DHT replication level
46 MaxDepth int `json:"maxDepth"` // maximum recursion depth in resolution 76 MaxDepth int `json:"maxDepth"` // maximum recursion depth in resolution
47} 77}
48 78
49/////////////////////////////////////////////////////////////////////// 79//----------------------------------------------------------------------
50// DHT configuration 80// DHT configuration
81//----------------------------------------------------------------------
51 82
52// DHTConfig contains parameters for the distributed hash table (DHT) 83// DHTConfig contains parameters for the distributed hash table (DHT)
53type DHTConfig struct { 84type DHTConfig struct {
54 Endpoint string `json:"endpoint"` // end-point of DHT service 85 Service *ServiceConfig `json:"service"` // socket for DHT service
86 Storage string `json:"storage"` // filesystem storage location
87 Cache string `json:"cache"` // key/value cache
55} 88}
56 89
57/////////////////////////////////////////////////////////////////////// 90//----------------------------------------------------------------------
58// Namecache configuration 91// Namecache configuration
92//----------------------------------------------------------------------
59 93
60// NamecacheConfig contains parameters for the local name cache 94// NamecacheConfig contains parameters for the local name cache
61type NamecacheConfig struct { 95type NamecacheConfig struct {
62 Endpoint string `json:"endpoint"` // end-point of Namecache service 96 Service *ServiceConfig `json:"service"` // socket for Namecache service
97 Storage string `json:"storage"` // key/value cache
63} 98}
64 99
65/////////////////////////////////////////////////////////////////////// 100//----------------------------------------------------------------------
66// Revocation configuration 101// Revocation configuration
102//----------------------------------------------------------------------
67 103
68// RevocationConfig contains parameters for the key revocation service 104// RevocationConfig contains parameters for the key revocation service
69type RevocationConfig struct { 105type RevocationConfig struct {
70 Endpoint string `json:"endpoint"` // end-point of Revocation service 106 Service *ServiceConfig `json:"service"` // socket for Revocation service
71 Storage string `json:"storage"` // persistance mechanism for revocation data 107 Storage string `json:"storage"` // persistance mechanism for revocation data
72} 108}
73 109
74/////////////////////////////////////////////////////////////////////// 110//----------------------------------------------------------------------
111// Combined configuration
112//----------------------------------------------------------------------
75 113
76// Environment settings 114// Environment settings
77type Environment map[string]string 115type Environment map[string]string
78 116
79// Config is the aggregated configuration for GNUnet. 117// Config is the aggregated configuration for GNUnet.
80type Config struct { 118type Config struct {
119 Local *NodeConfig `json:"local"`
120 Bootstrap *BootstrapConfig `json:"bootstrap"`
81 Env Environment `json:"environ"` 121 Env Environment `json:"environ"`
82 RPC *RPCConfig `json:"rpc"` 122 RPC *RPCConfig `json:"rpc"`
83 DHT *DHTConfig `json:"dht"` 123 DHT *DHTConfig `json:"dht"`
diff --git a/src/gnunet/config/gnunet-config.json b/src/gnunet/config/gnunet-config.json
index daf65f9..941cf21 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -1,24 +1,58 @@
1{ 1{
2 "local": {
3 "privateSeed": "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
4 "endpoints": [
5 "r5n+ip+udp:127.0.0.1:6666"
6 ]
7 },
8 "bootstrap": {
9 "nodes": [
10 "gnunet://hello/7KTBJ90340HF1Q2GB0A57E2XJER4FDHX8HP5GHEB9125VPWPD27G/BNMDFN6HJCPWSPNBSEC06MC1K8QN1Z2DHRQSRXDTFR7FTBD4JHNBJ2RJAAEZ31FWG1Q3PMN3PXGZQ3Q7NTNEKQZFA7TE2Y46FM8E20R/1653499308?r5n%2Bip%2Budp%3A127.0.0.1%3A7654"
11 ]
12 },
2 "environ": { 13 "environ": {
3 "TMP": "/tmp", 14 "TMP": "/tmp",
4 "RT_SYS": "${TMP}/gnunet-system-runtime" 15 "RT_SYS": "${TMP}/gnunet-system-runtime"
5 }, 16 },
6 "dht": { 17 "dht": {
7 "endpoint": "unix+${RT_SYS}/gnunet-service-dht.sock" 18 "service": {
19 "socket": "${RT_SYS}/gnunet-service-dht.sock",
20 "params": {
21 "perm": "0770"
22 }
23 },
24 "storage": "dht_file_store+/var/lib/gnunet/dht/store",
25 "cache": "dht_file_cache+/var/lib/gnunet/dht/cache+1000"
8 }, 26 },
9 "gns": { 27 "gns": {
10 "endpoint": "unix+${RT_SYS}/gnunet-service-gns-go.sock+perm=0770", 28 "service": {
29 "socket": "${RT_SYS}/gnunet-service-gns-go.sock",
30 "params": {
31 "perm": "0770"
32 }
33 },
11 "dhtReplLevel": 10, 34 "dhtReplLevel": 10,
12 "maxDepth": 250 35 "maxDepth": 250
13 }, 36 },
14 "namecache": { 37 "namecache": {
15 "endpoint": "unix+${RT_SYS}/gnunet-service-namecache.sock" 38 "service": {
39 "socket": "${RT_SYS}/gnunet-service-namecache.sock",
40 "params": {
41 "perm": "0770"
42 }
43 },
44 "storage": "dht_file_cache:/var/lib/gnunet/namecache:1000"
16 }, 45 },
17 "revocation": { 46 "revocation": {
18 "endpoint": "unix+${RT_SYS}/gnunet-service-revocation-go.sock+perm=0770", 47 "service": {
19 "storage": "redis+localhost:6397++15" 48 "socket": "${RT_SYS}/gnunet-service-revocation-go.sock",
49 "params": {
50 "perm": "0770"
51 }
52 },
53 "storage": "redis:localhost:6397::15"
20 }, 54 },
21 "rpc": { 55 "rpc": {
22 "endpoint": "tcp+127.0.0.1:80" 56 "endpoint": "tcp:127.0.0.1:80"
23 } 57 }
24} 58} \ No newline at end of file
diff --git a/src/gnunet/core/core.go b/src/gnunet/core/core.go
new file mode 100644
index 0000000..c3bf355
--- /dev/null
+++ b/src/gnunet/core/core.go
@@ -0,0 +1,234 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package core
20
21import (
22 "context"
23 "gnunet/message"
24 "gnunet/service/dht/blocks"
25 "gnunet/transport"
26 "gnunet/util"
27 "net"
28 "time"
29
30 "github.com/bfix/gospel/data"
31)
32
33// Core service
34type Core struct {
35 // local peer instance
36 local *Peer
37
38 // incoming messages from transport
39 incoming chan *transport.TransportMessage
40
41 // reference to transport implementation
42 trans *transport.Transport
43
44 // registered listeners
45 listeners map[string]*Listener
46
47 // list of known peers with addresses
48 peers *util.PeerAddrList
49}
50
51//----------------------------------------------------------------------
52
53// NewCore creates and runs a new core instance.
54func NewCore(ctx context.Context, local *Peer) (c *Core, err error) {
55 // create new core instance
56 incoming := make(chan *transport.TransportMessage)
57 c = &Core{
58 local: local,
59 incoming: incoming,
60 listeners: make(map[string]*Listener),
61 trans: transport.NewTransport(ctx, incoming),
62 peers: util.NewPeerAddrList(),
63 }
64 // add all local peer endpoints to transport.
65 for _, addr := range local.addrList {
66 if _, err = c.trans.AddEndpoint(ctx, addr); err != nil {
67 return
68 }
69 }
70 // run message pump
71 go func() {
72 // wait for incoming messages
73 for {
74 select {
75 // get (next) message from transport
76 case tm := <-c.incoming:
77 var ev *Event
78
79 // inspect message for peer state events
80 m, err := tm.Message()
81 if err == nil {
82 switch msg := m.(type) {
83 case *message.HelloMsg:
84 // keep peer addresses
85 for _, addr := range msg.Addresses {
86 a := &util.Address{
87 Netw: addr.Transport,
88 Address: addr.Address,
89 Expires: addr.ExpireOn,
90 }
91 c.Learn(ctx, msg.PeerID, a)
92 }
93 // generate EV_CONNECT event
94 ev = new(Event)
95 ev.ID = EV_CONNECT
96 ev.Peer = tm.Peer
97 ev.Msg = msg
98 c.dispatch(ev)
99 }
100 }
101 // generate EV_MESSAGE event
102 ev = new(Event)
103 ev.ID = EV_MESSAGE
104 ev.Peer = tm.Peer
105 ev.Msg, _ = tm.Message()
106 c.dispatch(ev)
107
108 // wait for termination
109 case <-ctx.Done():
110 return
111 }
112 }
113 }()
114 return
115}
116
117//----------------------------------------------------------------------
118
119// Send is a function that allows the local peer to send a protocol
120// message to a remote peer.
121func (c *Core) Send(ctx context.Context, peer *util.PeerID, msg message.Message) error {
122 // TODO: select best endpoint protocol for transport; now fixed to UDP
123 netw := "udp"
124 addr := c.peers.Get(peer.String(), netw)
125 payload, err := data.Marshal(msg)
126 if err != nil {
127 return err
128 }
129 tm := transport.NewTransportMessage(c.PeerID(), payload)
130 return c.trans.Send(ctx, addr, tm)
131}
132
133// Learn a (new) address for peer
134func (c *Core) Learn(ctx context.Context, peer *util.PeerID, addr *util.Address) (err error) {
135 if c.peers.Add(peer.String(), addr) == 1 {
136 // new peer id: send HELLO message to newly added peer
137 node := c.local
138 var hello *blocks.HelloBlock
139 hello, err = node.HelloData(time.Hour)
140 if err != nil {
141 return
142 }
143 msg := message.NewHelloMsg(node.GetID())
144 for _, a := range hello.Addresses() {
145 ha := message.NewHelloAddress(a)
146 msg.AddAddress(ha)
147 }
148 err = c.Send(ctx, peer, msg)
149 }
150 return
151}
152
153// PeerID returns the peer id of the local node.
154func (c *Core) PeerID() *util.PeerID {
155 return c.local.GetID()
156}
157
158// TryConnect is a function which allows the local peer to attempt the
159// establishment of a connection to another peer using an address.
160// When the connection attempt is successful, information on the new
161// peer is offered through the PEER_CONNECTED signal.
162func (c *Core) TryConnect(peer *util.PeerID, addr net.Addr) error {
163 // select endpoint for address
164 if ep := c.findEndpoint(peer, addr); ep == nil {
165 return transport.ErrTransNoEndpoint
166 }
167 return nil
168}
169
170func (c *Core) findEndpoint(peer *util.PeerID, addr net.Addr) transport.Endpoint {
171 return nil
172}
173
174// Hold is a function which tells the underlay to keep a hold on to a
175// connection to a peer P. Underlays are usually limited in the number
176// of active connections. With this function the DHT can indicate to the
177// underlay which connections should preferably be preserved.
178func (c *Core) Hold(peer *util.PeerID) {}
179
180// Drop is a function which tells the underlay to drop the connection to a
181// peer P. This function is only there for symmetry and used during the
182// peer's shutdown to release all of the remaining HOLDs. As R5N always
183// prefers the longest-lived connections, it would never drop an active
184// connection that it has called HOLD() on before. Nevertheless, underlay
185// implementations should not rely on this always being true. A call to
186// DROP() also does not imply that the underlay must close the connection:
187// it merely removes the preference to preserve the connection that was
188// established by HOLD().
189func (c *Core) Drop(peer *util.PeerID) {}
190
191// L2NSE is ESTIMATE_NETWORK_SIZE(), a procedure that provides estimates
192// on the base-2 logarithm of the network size L2NSE, that is the base-2
193// logarithm number of peers in the network, for use by the routing
194// algorithm.
195func (c *Core) L2NSE() float64 {
196 return 0.
197}
198
199//----------------------------------------------------------------------
200// Event listener and event dispatch.
201//----------------------------------------------------------------------
202
203// Register a named event listener.
204func (c *Core) Register(name string, l *Listener) {
205 c.listeners[name] = l
206}
207
208// Unregister named event listener.
209func (c *Core) Unregister(name string) *Listener {
210 if l, ok := c.listeners[name]; ok {
211 delete(c.listeners, name)
212 return l
213 }
214 return nil
215}
216
217// internal: dispatch event to listeners
218func (c *Core) dispatch(ev *Event) {
219 // dispatch event to listeners
220 for _, l := range c.listeners {
221 if l.filter.CheckEvent(ev.ID) {
222 mt := ev.Msg.Header().MsgType
223 if ev.ID == EV_MESSAGE {
224 if mt != 0 && !l.filter.CheckMsgType(mt) {
225 // skip event
226 return
227 }
228 }
229 go func() {
230 l.ch <- ev
231 }()
232 }
233 }
234}
diff --git a/src/gnunet/core/core_test.go b/src/gnunet/core/core_test.go
new file mode 100644
index 0000000..102abf1
--- /dev/null
+++ b/src/gnunet/core/core_test.go
@@ -0,0 +1,150 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package core
20
21import (
22 "context"
23 "gnunet/config"
24 "gnunet/util"
25 "testing"
26 "time"
27)
28
29var (
30 peer1Cfg = &config.NodeConfig{
31 PrivateSeed: "iYK1wSi5XtCP774eNFk1LYXqKlOPEpwKBw+2/bMkE24=",
32 Endpoints: []string{"udp://127.0.0.1:20861"},
33 }
34
35 peer2Cfg = &config.NodeConfig{
36 PrivateSeed: "Bv9umksEO51jjWWrOGEH+4r8wl9Vi+LItpdBpTOi2PE=",
37 Endpoints: []string{"udp://127.0.0.1:20862"},
38 }
39)
40
41//----------------------------------------------------------------------
42// create and run a node with given spec
43//----------------------------------------------------------------------
44
45type TestNode struct {
46 id int
47 t *testing.T
48 peer *Peer
49 core *Core
50 addr *util.Address
51}
52
53func (n *TestNode) Learn(ctx context.Context, peer *util.PeerID, addr *util.Address) {
54 n.t.Logf("[%d] Learning %s for %s", n.id, addr.StringAll(), peer.String())
55 n.core.Learn(ctx, peer, addr)
56}
57
58func NewTestNode(t *testing.T, ctx context.Context, cfg *config.NodeConfig) (node *TestNode, err error) {
59
60 // create test node
61 node = new(TestNode)
62 node.t = t
63 node.id = util.NextID()
64
65 // create peer object
66 if node.peer, err = NewLocalPeer(cfg); err != nil {
67 return
68 }
69 t.Logf("[%d] Node %s starting", node.id, node.peer.GetID())
70
71 // create core service
72 if node.core, err = NewCore(ctx, node.peer); err != nil {
73 return
74 }
75 for _, addr := range node.core.trans.Endpoints() {
76 s := addr.Network() + ":" + addr.String()
77 if node.addr, err = util.ParseAddress(s); err != nil {
78 continue
79 }
80 t.Logf("[%d] Listening on %s", node.id, s)
81 }
82
83 // register as event listener
84 incoming := make(chan *Event)
85 node.core.Register("test", NewListener(incoming, nil))
86
87 // heart beat
88 tick := time.NewTicker(5 * time.Minute)
89
90 // run event handler
91 go func() {
92 for {
93 select {
94 // show incoming event
95 case ev := <-incoming:
96 switch ev.ID {
97 case EV_CONNECT:
98 t.Logf("[%d] <<< Peer %s connected", node.id, ev.Peer)
99 case EV_DISCONNECT:
100 t.Logf("[%d] <<< Peer %s diconnected", node.id, ev.Peer)
101 case EV_MESSAGE:
102 t.Logf("[%d] <<< Msg from %s of type %d", node.id, ev.Peer, ev.Msg.Header().MsgType)
103 }
104
105 // handle termination signal
106 case <-ctx.Done():
107 t.Logf("[%d] Shutting down node", node.id)
108 return
109
110 // handle heart beat
111 case now := <-tick.C:
112 t.Logf("[%d] Heart beat at %s", node.id, now.String())
113 }
114 }
115 }()
116 return
117}
118
119//----------------------------------------------------------------------
120// Two node GNUnet (smallest and simplest network)
121//----------------------------------------------------------------------
122
123// TestCoreSimple test a two node network
124func TestCoreSimple(t *testing.T) {
125
126 // setup execution context
127 ctx, cancel := context.WithCancel(context.Background())
128 defer func() {
129 cancel()
130 time.Sleep(time.Second)
131 }()
132
133 // create and run nodes
134 node1, err := NewTestNode(t, ctx, peer1Cfg)
135 if err != nil {
136 t.Fatal(err)
137 }
138 node2, err := NewTestNode(t, ctx, peer2Cfg)
139 if err != nil {
140 t.Fatal(err)
141 }
142
143 // learn peer addresses (triggers HELLO)
144 for _, addr := range node2.core.trans.Endpoints() {
145 node1.Learn(ctx, node2.peer.GetID(), util.NewAddressWrap(addr))
146 }
147
148 // wait for 5 seconds
149 time.Sleep(5 * time.Second)
150}
diff --git a/src/gnunet/core/event.go b/src/gnunet/core/event.go
new file mode 100644
index 0000000..4eab112
--- /dev/null
+++ b/src/gnunet/core/event.go
@@ -0,0 +1,109 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package core
20
21import (
22 "gnunet/message"
23 "gnunet/util"
24)
25
26//----------------------------------------------------------------------
27// Core events and listeners
28//----------------------------------------------------------------------
29
30// Event types
31const (
32 EV_CONNECT = iota // peer connected
33 EV_DISCONNECT // peer disconnected
34 EV_MESSAGE // incoming message
35)
36
37// EventFilter is a filter for events a listener is interested in.
38// The filter works on event types; if EV_MESSAGE is set, messages
39// can be filtered by message type also.
40type EventFilter struct {
41 evTypes map[int]bool
42 msgTypes map[uint16]bool
43}
44
45// NewEventFilter creates a new empty filter instance.
46func NewEventFilter() *EventFilter {
47 return &EventFilter{
48 evTypes: make(map[int]bool),
49 msgTypes: make(map[uint16]bool),
50 }
51}
52
53// AddEvent add an event id to filter
54func (f *EventFilter) AddEvent(ev int) {
55 f.evTypes[ev] = true
56}
57
58// AddMsgType adds a message type to filter
59func (f *EventFilter) AddMsgType(mt uint16) {
60 f.evTypes[EV_MESSAGE] = true
61 f.msgTypes[mt] = true
62}
63
64// CheckEvent returns true if an event id is matched
65// by the filter or the filter is empty.
66func (f *EventFilter) CheckEvent(ev int) bool {
67 if len(f.evTypes) == 0 {
68 return true
69 }
70 _, ok := f.evTypes[ev]
71 return ok
72}
73
74// CheckMsgType returns true if a message type is matched
75// by the filter or the filter is empty.
76func (f *EventFilter) CheckMsgType(mt uint16) bool {
77 if len(f.msgTypes) == 0 {
78 return true
79 }
80 _, ok := f.msgTypes[mt]
81 return ok
82}
83
84// Event sent to listeners
85type Event struct {
86 ID int // event type
87 Peer *util.PeerID // remote peer
88 Msg message.Message // GNUnet message (can be nil)
89}
90
91//----------------------------------------------------------------------
92
93// Listener for network events
94type Listener struct {
95 ch chan *Event // listener channel
96 filter *EventFilter // event filter settimgs
97}
98
99// NewListener for given filter and receiving channel
100func NewListener(ch chan *Event, f *EventFilter) *Listener {
101 if f == nil {
102 // set empty default filter
103 f = NewEventFilter()
104 }
105 return &Listener{
106 ch: ch,
107 filter: f,
108 }
109}
diff --git a/src/gnunet/core/peer.go b/src/gnunet/core/peer.go
index f81bab8..2b6fe74 100644
--- a/src/gnunet/core/peer.go
+++ b/src/gnunet/core/peer.go
@@ -1,5 +1,5 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang. 1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y< 2// Copyright (C) 2019-2022 Bernd Fix >Y<
3// 3//
4// gnunet-go is free software: you can redistribute it and/or modify it 4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published 5// under the terms of the GNU Affero General Public License as published
@@ -19,14 +19,30 @@
19package core 19package core
20 20
21import ( 21import (
22 "encoding/base64"
22 "fmt" 23 "fmt"
24 "time"
23 25
26 "gnunet/config"
24 "gnunet/message" 27 "gnunet/message"
28 "gnunet/service/dht/blocks"
25 "gnunet/util" 29 "gnunet/util"
26 30
27 "github.com/bfix/gospel/crypto/ed25519" 31 "github.com/bfix/gospel/crypto/ed25519"
28) 32)
29 33
34//----------------------------------------------------------------------
35// GNUnet P2P network node (local or remote):
36//
37// * A LOCAL node has a long-term EdDSA key pair used for signing. The
38// public key is the node identifier (PeerID).
39// Local nodes hold additional attributes like ephemeral keys for message
40// exchange or a list of network addresses the node can be reached on.
41//
42// * A REMOTE node only has a public EdDSA key used by the local node
43// to verify signatures from the remote node.
44//----------------------------------------------------------------------
45
30// Peer represents a node in the GNUnet P2P network. 46// Peer represents a node in the GNUnet P2P network.
31type Peer struct { 47type Peer struct {
32 prv *ed25519.PrivateKey // node private key (long-term signing key) 48 prv *ed25519.PrivateKey // node private key (long-term signing key)
@@ -37,27 +53,87 @@ type Peer struct {
37 ephMsg *message.EphemeralKeyMsg // ephemeral signing key message 53 ephMsg *message.EphemeralKeyMsg // ephemeral signing key message
38} 54}
39 55
40// NewPeer instantiates a new peer object from given data. If a local peer 56//----------------------------------------------------------------------
41// is created, the data is the seed for generating the private key of the node; 57// Create new peer objects
42// for a remote peer the data is the binary representation of its public key. 58//----------------------------------------------------------------------
43func NewPeer(data []byte, local bool) (p *Peer, err error) { 59
60// NewLocalPeer creates a new local node from configuration data.
61func NewLocalPeer(cfg *config.NodeConfig) (p *Peer, err error) {
44 p = new(Peer) 62 p = new(Peer)
45 if local { 63
46 p.prv = ed25519.NewPrivateKeyFromSeed(data) 64 // get the key material for local node
47 p.pub = p.prv.Public() 65 var data []byte
48 p.ephPrv, p.ephMsg, err = message.NewEphemeralKey(p.pub.Bytes(), p.prv) 66 if data, err = base64.StdEncoding.DecodeString(cfg.PrivateSeed); err != nil {
49 if err != nil { 67 return
68 }
69 p.prv = ed25519.NewPrivateKeyFromSeed(data)
70 p.pub = p.prv.Public()
71 p.idString = util.EncodeBinaryToString(p.pub.Bytes())
72 p.ephPrv, p.ephMsg, err = message.NewEphemeralKey(p.pub.Bytes(), p.prv)
73 if err != nil {
74 return
75 }
76 // set the endpoint addresses for local node
77 p.addrList = make([]*util.Address, len(cfg.Endpoints))
78 var addr *util.Address
79 for i, a := range cfg.Endpoints {
80 if addr, err = util.ParseAddress(a); err != nil {
50 return 81 return
51 } 82 }
52 } else { 83 addr.Expires = util.NewAbsoluteTime(time.Now().Add(12 * time.Hour))
53 p.prv = nil 84 p.addrList[i] = addr
54 p.pub = ed25519.NewPublicKeyFromBytes(data)
55 } 85 }
86 return
87}
88
89// NewPeer instantiates a new (remote) peer object from given peer ID string.
90func NewPeer(peerID string) (p *Peer, err error) {
91 p = new(Peer)
92
93 // get the key material for local node
94 var data []byte
95 if data, err = util.DecodeStringToBinary(peerID, 32); err != nil {
96 return
97 }
98 p.prv = nil
99 p.pub = ed25519.NewPublicKeyFromBytes(data)
56 p.idString = util.EncodeBinaryToString(p.pub.Bytes()) 100 p.idString = util.EncodeBinaryToString(p.pub.Bytes())
57 p.addrList = make([]*util.Address, 0) 101 p.addrList = make([]*util.Address, 0)
58 return 102 return
59} 103}
60 104
105//----------------------------------------------------------------------
106//----------------------------------------------------------------------
107
108// Address returns a peer address for the given transport protocol
109func (p *Peer) Address(transport string) *util.Address {
110 for _, addr := range p.addrList {
111 // skip expired entries
112 if addr.Expires.Expired() {
113 continue
114 }
115 // filter by transport protocol
116 if len(transport) > 0 && transport != addr.Netw {
117 continue
118 }
119 return addr
120 }
121 return nil
122}
123
124// HelloData returns the current HELLO data for the peer
125func (p *Peer) HelloData(ttl time.Duration) (h *blocks.HelloBlock, err error) {
126 // assemble HELLO data
127 h = new(blocks.HelloBlock)
128 h.PeerID = p.GetID()
129 h.Expire = util.NewAbsoluteTime(time.Now().Add(ttl))
130 h.SetAddresses(p.addrList)
131
132 // sign data
133 err = h.Sign(p.prv)
134 return
135}
136
61// EphKeyMsg returns a new initialized message to negotiate session keys. 137// EphKeyMsg returns a new initialized message to negotiate session keys.
62func (p *Peer) EphKeyMsg() *message.EphemeralKeyMsg { 138func (p *Peer) EphKeyMsg() *message.EphemeralKeyMsg {
63 return p.ephMsg 139 return p.ephMsg
@@ -84,8 +160,10 @@ func (p *Peer) PubKey() *ed25519.PublicKey {
84} 160}
85 161
86// GetID returns the node ID (public key) in binary format 162// GetID returns the node ID (public key) in binary format
87func (p *Peer) GetID() []byte { 163func (p *Peer) GetID() *util.PeerID {
88 return p.pub.Bytes() 164 return &util.PeerID{
165 Key: util.Clone(p.pub.Bytes()),
166 }
89} 167}
90 168
91// GetIDString returns the string representation of the public key of the node. 169// GetIDString returns the string representation of the public key of the node.
diff --git a/src/gnunet/core/peer_test.go b/src/gnunet/core/peer_test.go
new file mode 100644
index 0000000..28b328f
--- /dev/null
+++ b/src/gnunet/core/peer_test.go
@@ -0,0 +1,72 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package core
20
21import (
22 "gnunet/config"
23 "gnunet/service/dht/blocks"
24 "testing"
25 "time"
26)
27
28// test data
29var (
30 cfg = &config.NodeConfig{
31 PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
32 Endpoints: []string{
33 "r5n+ip+udp://127.0.0.1:6666",
34 },
35 }
36 TTL = 6 * time.Hour
37)
38
39func TestPeerHello(t *testing.T) {
40
41 // generate new local node
42 node, err := NewLocalPeer(cfg)
43 if err != nil {
44 t.Fatal(err)
45 }
46
47 // get HELLO data for the node
48 h, err := node.HelloData(TTL)
49
50 // convert to URL and back
51 u := h.URL()
52 t.Log(u)
53 h2, err := blocks.ParseHelloURL(u)
54 if err != nil {
55 t.Fatal(err)
56 }
57 u2 := h2.URL()
58 t.Log(u2)
59
60 // check if HELLO data is the same
61 if !h.Equals(h2) {
62 t.Fatal("HELLO data mismatch")
63 }
64 // verify signature
65 ok, err := h.Verify()
66 if err != nil {
67 t.Fatal(err)
68 }
69 if !ok {
70 t.Fatal("failed to verify signature")
71 }
72}
diff --git a/src/gnunet/crypto/gns.go b/src/gnunet/crypto/gns.go
index 6aa7972..f4c5627 100644
--- a/src/gnunet/crypto/gns.go
+++ b/src/gnunet/crypto/gns.go
@@ -58,7 +58,8 @@ import (
58// example the RSA crypto scheme is outlined: 58// example the RSA crypto scheme is outlined:
59// 59//
60// (1) Register/define a new GNS_TYPE_RSAKEY 60// (1) Register/define a new GNS_TYPE_RSAKEY
61// (2) Add ZONE_RSAKEY to the "Zone types" declarations below. 61// (2) Add ZONE_RSAKEY and GNS_TYPE_RSAKEY to the "Zone types"
62// declarations in this file.
62// (3) Code the implementation in a file named `gns_rsakey.go`: 63// (3) Code the implementation in a file named `gns_rsakey.go`:
63// You have to implement three interfaces (ZonePrivateImpl, 64// You have to implement three interfaces (ZonePrivateImpl,
64// ZoneKeyImpl and ZoneSigImpl) in three separate custom types. 65// ZoneKeyImpl and ZoneSigImpl) in three separate custom types.
@@ -145,6 +146,12 @@ type ZoneSigImpl interface {
145var ( 146var (
146 ZONE_PKEY = uint32(enums.GNS_TYPE_PKEY) 147 ZONE_PKEY = uint32(enums.GNS_TYPE_PKEY)
147 ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY) 148 ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY)
149
150 // register available zone types for BlockHandler
151 ZoneTypes = []int{
152 enums.GNS_TYPE_PKEY,
153 enums.GNS_TYPE_EDKEY,
154 }
148) 155)
149 156
150//---------------------------------------------------------------------- 157//----------------------------------------------------------------------
diff --git a/src/gnunet/crypto/hash.go b/src/gnunet/crypto/hash.go
index bc715d4..049a8ef 100644
--- a/src/gnunet/crypto/hash.go
+++ b/src/gnunet/crypto/hash.go
@@ -19,6 +19,7 @@
19package crypto 19package crypto
20 20
21import ( 21import (
22 "bytes"
22 "crypto/sha512" 23 "crypto/sha512"
23 24
24 "gnunet/util" 25 "gnunet/util"
@@ -29,11 +30,20 @@ type HashCode struct {
29 Bits []byte `size:"64"` 30 Bits []byte `size:"64"`
30} 31}
31 32
32// NewHashCode creates a new, uninitalized hash value 33// Equals tests if two hash results are equal.
33func NewHashCode() *HashCode { 34func (hc *HashCode) Equals(n *HashCode) bool {
34 return &HashCode{ 35 return bytes.Equal(hc.Bits, n.Bits)
36}
37
38// NewHashCode creates a new (initalized) hash value
39func NewHashCode(buf []byte) *HashCode {
40 hc := &HashCode{
35 Bits: make([]byte, 64), 41 Bits: make([]byte, 64),
36 } 42 }
43 if buf != nil {
44 util.CopyAlignedBlock(hc.Bits, buf)
45 }
46 return hc
37} 47}
38 48
39// Hash returns the SHA-512 hash value of a given blob 49// Hash returns the SHA-512 hash value of a given blob
diff --git a/src/gnunet/go.mod b/src/gnunet/go.mod
index 8527379..7383eaf 100644
--- a/src/gnunet/go.mod
+++ b/src/gnunet/go.mod
@@ -1,21 +1,25 @@
1module gnunet 1module gnunet
2 2
3go 1.17 3go 1.18
4 4
5require ( 5require (
6 github.com/bfix/gospel v1.2.10 6 github.com/bfix/gospel v1.2.11
7 github.com/go-redis/redis/v8 v8.5.0 7 github.com/go-redis/redis/v8 v8.11.5
8 github.com/go-sql-driver/mysql v1.5.0 8 github.com/go-sql-driver/mysql v1.6.0
9 github.com/gorilla/mux v1.8.0 9 github.com/gorilla/mux v1.8.0
10 github.com/mattn/go-sqlite3 v1.14.6 10 github.com/mattn/go-sqlite3 v1.14.13
11 github.com/miekg/dns v1.1.26 11 github.com/miekg/dns v1.1.49
12 golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed 12 golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
13) 13)
14 14
15require ( 15require (
16 github.com/cespare/xxhash/v2 v2.1.1 // indirect 16 github.com/cespare/xxhash/v2 v2.1.2 // indirect
17 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 17 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
18 go.opentelemetry.io/otel v0.16.0 // indirect 18 golang.org/x/mod v0.4.2 // indirect
19 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 19 golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
20 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect 20 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
21 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
22 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
21) 23)
24
25replace github.com/bfix/gospel v1.2.11 => /vault/prj/libs/Go/Gospel
diff --git a/src/gnunet/go.sum b/src/gnunet/go.sum
index 4fa501c..ea3c328 100644
--- a/src/gnunet/go.sum
+++ b/src/gnunet/go.sum
@@ -1,123 +1,67 @@
1github.com/bfix/gospel v1.2.10 h1:a8l/sET2y+FVKIO5M1l5hdTlqLxstvkhp+b6FpAkxOU= 1github.com/bfix/gospel v1.2.11 h1:z/c6MFNq/lz4mO8+PK60a3NvH+lbTKAlLCShuFFZUvg=
2github.com/bfix/gospel v1.2.10/go.mod h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI= 2github.com/bfix/gospel v1.2.11/go.mod h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
3github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 3github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
4github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 4github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
6github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 5github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
8github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 6github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
9github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
10github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 7github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
11github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 8github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
12github.com/go-redis/redis/v8 v8.5.0 h1:L3r1Q3I5WOUdXZGCP6g44EruKh0u3n6co5Hl5xWkdGA= 9github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
13github.com/go-redis/redis/v8 v8.5.0/go.mod h1:YmEcgBDttjnkbMzDAhDtQxY9yVA7jMN6PCR5HeMvqFE= 10github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
14github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 11github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
15github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
16github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
17github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
18github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
19github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
20github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
21github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
22github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
23github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
24github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
25github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
26github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
27github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
28github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 12github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
29github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 13github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
30github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
31github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= 14github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
32github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= 15github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
33github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= 16github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
34github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 17github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
35github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= 18github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
36github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 19github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
37github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 20github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
38github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 21github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
39github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 22github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
40github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 23github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
41github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
42github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
43github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
44github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
45github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
46github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
47github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
48github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
49github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
50github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
51github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
52github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
53go.opentelemetry.io/otel v0.16.0 h1:uIWEbdeb4vpKPGITLsRVUS44L5oDbDUCZxn8lkxhmgw=
54go.opentelemetry.io/otel v0.16.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA=
55golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
56golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
57golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 25golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
58golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
59golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 26golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
60golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= 27golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
61golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 28golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
62golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 29golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
63golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 30golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
64golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 31golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
65golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 32golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
66golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 33golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
67golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 34golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
68golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 35golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
69golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 36golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
70golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 37golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
71golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
72golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
73golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
74golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
75golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= 39golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
76golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
77golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
78golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
79golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
80golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
81golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
82golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
83golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
84golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
85golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
86golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
87golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
88golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
89golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
90golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
91golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 47golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
92golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
49golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
50golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
93golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 51golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
94golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 52golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
95golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 53golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
96golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
97golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 54golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
98golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 55golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
99golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
100golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 56golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
57golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
101golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 58golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
102golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
103golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 59golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
104golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 60golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
61golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
105golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 62golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
106golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 63golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
107golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 64golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
108golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 65golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
109google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
110google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
111google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
112google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
113google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
114google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
115gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
116gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
117gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 66gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
118gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 67gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
119gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
120gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
121gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
122gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
123gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go
index 8cbf9ce..6681eae 100644
--- a/src/gnunet/message/factory.go
+++ b/src/gnunet/message/factory.go
@@ -56,12 +56,16 @@ func NewEmptyMessage(msgType uint16) (Message, error) {
56 //------------------------------------------------------------------ 56 //------------------------------------------------------------------
57 // DHT 57 // DHT
58 //------------------------------------------------------------------ 58 //------------------------------------------------------------------
59 case DHT_CLIENT_PUT:
60 return NewDHTClientPutMsg(nil, 0, nil), nil
59 case DHT_CLIENT_GET: 61 case DHT_CLIENT_GET:
60 return NewDHTClientGetMsg(nil), nil 62 return NewDHTClientGetMsg(nil), nil
61 case DHT_CLIENT_GET_STOP: 63 case DHT_CLIENT_GET_STOP:
62 return NewDHTClientGetStopMsg(nil), nil 64 return NewDHTClientGetStopMsg(nil), nil
63 case DHT_CLIENT_RESULT: 65 case DHT_CLIENT_RESULT:
64 return NewDHTClientResultMsg(nil), nil 66 return NewDHTClientResultMsg(nil), nil
67 case DHT_CLIENT_GET_RESULTS_KNOWN:
68 return NewDHTClientGetResultsKnownMsg(nil), nil
65 69
66 //------------------------------------------------------------------ 70 //------------------------------------------------------------------
67 // GNS 71 // GNS
diff --git a/src/gnunet/message/message.go b/src/gnunet/message/message.go
index 1b826d7..60e36c9 100644
--- a/src/gnunet/message/message.go
+++ b/src/gnunet/message/message.go
@@ -29,11 +29,19 @@ var (
29 ErrMsgHeaderTooSmall = errors.New("Message header too small") 29 ErrMsgHeaderTooSmall = errors.New("Message header too small")
30) 30)
31 31
32//----------------------------------------------------------------------
33
32// Message is an interface for all GNUnet-specific messages. 34// Message is an interface for all GNUnet-specific messages.
33type Message interface { 35type Message interface {
36 // Header of message
34 Header() *Header 37 Header() *Header
38
39 // String returns a human-readable message
40 String() string
35} 41}
36 42
43//----------------------------------------------------------------------
44
37// Header encapsulates the common part of all GNUnet messages (at the 45// Header encapsulates the common part of all GNUnet messages (at the
38// beginning of the data). 46// beginning of the data).
39type Header struct { 47type Header struct {
diff --git a/src/gnunet/message/msg_dht.go b/src/gnunet/message/msg_dht.go
index 9b73557..bb8fc1a 100644
--- a/src/gnunet/message/msg_dht.go
+++ b/src/gnunet/message/msg_dht.go
@@ -28,6 +28,54 @@ import (
28) 28)
29 29
30//---------------------------------------------------------------------- 30//----------------------------------------------------------------------
31// DHT_CLIENT_PUT
32//----------------------------------------------------------------------
33
34// DHTClientPutMsg is the message for putting values into the DHT
35type DHTClientPutMsg struct {
36 MsgSize uint16 `order:"big"` // total size of message
37 MsgType uint16 `order:"big"` // DHT_CLIENT_PUT (142)
38 Type uint32 `order:"big"` // The type of the data (BLOCK_TYPE_???)
39 Options uint32 `order:"big"` // Message options (DHT_RO_???)
40 ReplLevel uint32 `order:"big"` // Replication level for this message
41 Expire util.AbsoluteTime // Expiration time
42 Key *crypto.HashCode // The key to be used
43 Data []byte `size:"*"` // Block data
44}
45
46// NewDHTClientPutMsg creates a new default DHTClientPutMsg object.
47func NewDHTClientPutMsg(key *crypto.HashCode, btype int, data []byte) *DHTClientPutMsg {
48 if key == nil {
49 key = new(crypto.HashCode)
50 }
51 var size uint16 = 88
52 if data != nil {
53 size += uint16(len(data))
54 }
55 return &DHTClientPutMsg{
56 MsgSize: size,
57 MsgType: DHT_CLIENT_PUT,
58 Type: uint32(btype),
59 Options: uint32(enums.DHT_RO_NONE),
60 ReplLevel: 1,
61 Expire: util.AbsoluteTimeNever(),
62 Key: key,
63 Data: data,
64 }
65}
66
67// String returns a human-readable representation of the message.
68func (m *DHTClientPutMsg) String() string {
69 return fmt.Sprintf("DHTClientPutMsg{Type=%d,Expire=%s,Options=%d,Repl=%d,Key=%s}",
70 m.Type, m.Expire, m.Options, m.ReplLevel, hex.EncodeToString(m.Key.Bits))
71}
72
73// Header returns the message header in a separate instance.
74func (m *DHTClientPutMsg) Header() *Header {
75 return &Header{m.MsgSize, m.MsgType}
76}
77
78//----------------------------------------------------------------------
31// DHT_CLIENT_GET 79// DHT_CLIENT_GET
32//---------------------------------------------------------------------- 80//----------------------------------------------------------------------
33 81
@@ -102,7 +150,7 @@ type DHTClientResultMsg struct {
102// NewDHTClientResultMsg creates a new default DHTClientResultMsg object. 150// NewDHTClientResultMsg creates a new default DHTClientResultMsg object.
103func NewDHTClientResultMsg(key *crypto.HashCode) *DHTClientResultMsg { 151func NewDHTClientResultMsg(key *crypto.HashCode) *DHTClientResultMsg {
104 if key == nil { 152 if key == nil {
105 key = crypto.NewHashCode() 153 key = crypto.NewHashCode(nil)
106 } 154 }
107 return &DHTClientResultMsg{ 155 return &DHTClientResultMsg{
108 MsgSize: 64, // empty message size (no data) 156 MsgSize: 64, // empty message size (no data)
@@ -163,3 +211,48 @@ func (m *DHTClientGetStopMsg) String() string {
163func (m *DHTClientGetStopMsg) Header() *Header { 211func (m *DHTClientGetStopMsg) Header() *Header {
164 return &Header{m.MsgSize, m.MsgType} 212 return &Header{m.MsgSize, m.MsgType}
165} 213}
214
215//----------------------------------------------------------------------
216// DHT_CLIENT_GET_RESULTS_KNOWN
217//----------------------------------------------------------------------
218
219// DHTClientGetResultsKnownMsg is the message for putting values into the DHT
220type DHTClientGetResultsKnownMsg struct {
221 MsgSize uint16 `order:"big"` // total size of message
222 MsgType uint16 `order:"big"` // DHT_CLIENT_GET_RESULTS_KNOWN (156)
223 Reserved uint32 `order:"big"` // Reserved for further use
224 Key *crypto.HashCode // The key to search for
225 ID uint64 `order:"big"` // Unique ID identifying this request
226 Known []*crypto.HashCode `size:"*"` // list of known results
227}
228
229// NewDHTClientPutMsg creates a new default DHTClientPutMsg object.
230func NewDHTClientGetResultsKnownMsg(key *crypto.HashCode) *DHTClientGetResultsKnownMsg {
231 if key == nil {
232 key = new(crypto.HashCode)
233 }
234 return &DHTClientGetResultsKnownMsg{
235 MsgSize: 80,
236 MsgType: DHT_CLIENT_GET_RESULTS_KNOWN,
237 Key: key,
238 ID: 0,
239 Known: make([]*crypto.HashCode, 0),
240 }
241}
242
243// AddKnown adds a known result to the list
244func (m *DHTClientGetResultsKnownMsg) AddKnown(hc *crypto.HashCode) {
245 m.Known = append(m.Known, hc)
246 m.MsgSize += 64
247}
248
249// String returns a human-readable representation of the message.
250func (m *DHTClientGetResultsKnownMsg) String() string {
251 return fmt.Sprintf("DHTClientGetResultsKnownMsg{Id:%d,Key=%s,Num=%d}",
252 m.ID, hex.EncodeToString(m.Key.Bits), len(m.Known))
253}
254
255// Header returns the message header in a separate instance.
256func (m *DHTClientGetResultsKnownMsg) Header() *Header {
257 return &Header{m.MsgSize, m.MsgType}
258}
diff --git a/src/gnunet/message/msg_gns.go b/src/gnunet/message/msg_gns.go
index 35630d6..9b85e40 100644
--- a/src/gnunet/message/msg_gns.go
+++ b/src/gnunet/message/msg_gns.go
@@ -25,15 +25,9 @@ import (
25 "gnunet/enums" 25 "gnunet/enums"
26 "gnunet/util" 26 "gnunet/util"
27 27
28 "github.com/bfix/gospel/data"
29 "github.com/bfix/gospel/logger" 28 "github.com/bfix/gospel/logger"
30) 29)
31 30
32// Error messages
33var (
34 ErrBlockNotDecrypted = fmt.Errorf("GNS block not decrypted")
35)
36
37//---------------------------------------------------------------------- 31//----------------------------------------------------------------------
38// GNS_LOOKUP 32// GNS_LOOKUP
39//---------------------------------------------------------------------- 33//----------------------------------------------------------------------
@@ -147,94 +141,6 @@ func (rs *RecordSet) Expires() util.AbsoluteTime {
147 return expires 141 return expires
148} 142}
149 143
150// SignedBlockData represents the signed and encrypted list of resource
151// records stored in a GNSRecordSet
152type SignedBlockData struct {
153 Purpose *crypto.SignaturePurpose `` // Size and purpose of signature (8 bytes)
154 Expire util.AbsoluteTime `` // Expiration time of the block.
155 EncData []byte `size:"*"` // encrypted GNSRecordSet
156
157 // transient data (not serialized)
158 data []byte // decrypted GNSRecord set
159}
160
161// Block is the result of GNS lookups for a given label in a zone.
162// An encrypted and signed container for GNS resource records that represents
163// the "atomic" data structure associated with a GNS label in a given zone.
164type Block struct {
165 DerivedKeySig *crypto.ZoneSignature // Derived key used for signing
166 Block *SignedBlockData
167
168 // transient data (not serialized)
169 checked bool // block integrity checked
170 verified bool // block signature verified (internal)
171 decrypted bool // block data decrypted (internal)
172}
173
174// String returns the human-readable representation of a GNSBlock
175func (b *Block) String() string {
176 return fmt.Sprintf("GNSBlock{Verified=%v,Decrypted=%v,data=[%d]}",
177 b.verified, b.decrypted, len(b.Block.EncData))
178}
179
180// Records returns the list of resource records in a block.
181func (b *Block) Records() ([]*ResourceRecord, error) {
182 // check if block is decrypted
183 if !b.decrypted {
184 return nil, ErrBlockNotDecrypted
185 }
186 // parse block data into record set
187 rs := NewRecordSet()
188 if err := data.Unmarshal(rs, b.Block.data); err != nil {
189 return nil, err
190 }
191 return rs.Records, nil
192}
193
194// Verify the integrity of the block data from a signature.
195func (b *Block) Verify(zkey *crypto.ZoneKey, label string) (err error) {
196 // Integrity check performed
197 b.checked = true
198
199 // verify derived key
200 dkey := b.DerivedKeySig.ZoneKey
201 dkey2, _ := zkey.Derive(label, "gns")
202 if !dkey.Equal(dkey2) {
203 return fmt.Errorf("invalid signature key for GNS Block")
204 }
205 // verify signature
206 var buf []byte
207 if buf, err = data.Marshal(b.Block); err != nil {
208 return
209 }
210 b.verified, err = b.DerivedKeySig.Verify(buf)
211 return
212}
213
214// Decrypt block data with a key derived from zone key and label.
215func (b *Block) Decrypt(zkey *crypto.ZoneKey, label string) (err error) {
216 // decrypt payload
217 b.Block.data, err = zkey.Decrypt(b.Block.EncData, label, b.Block.Expire)
218 b.decrypted = true
219 return
220}
221
222// NewBlock instantiates an empty GNS block
223func NewBlock() *Block {
224 return &Block{
225 DerivedKeySig: nil,
226 Block: &SignedBlockData{
227 Purpose: new(crypto.SignaturePurpose),
228 Expire: *new(util.AbsoluteTime),
229 EncData: nil,
230 data: nil,
231 },
232 checked: false,
233 verified: false,
234 decrypted: false,
235 }
236}
237
238// ResourceRecord is the GNUnet-specific representation of resource 144// ResourceRecord is the GNUnet-specific representation of resource
239// records (not to be confused with DNS resource records). 145// records (not to be confused with DNS resource records).
240type ResourceRecord struct { 146type ResourceRecord struct {
diff --git a/src/gnunet/message/msg_hello.go b/src/gnunet/message/msg_hello.go
new file mode 100644
index 0000000..18fe9d5
--- /dev/null
+++ b/src/gnunet/message/msg_hello.go
@@ -0,0 +1,103 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package message
20
21import (
22 "fmt"
23 "gnunet/util"
24)
25
26//----------------------------------------------------------------------
27// HELLO
28//
29// A HELLO message is used to exchange information about transports with
30// other peers. This struct is always followed by the actual network
31// addresses which have the format:
32//
33// 1) transport-name (0-terminated)
34// 2) address-length (uint16_t, network byte order)
35// 3) address expiration
36// 4) address (address-length bytes)
37//----------------------------------------------------------------------
38
39// HelloAddress represents a (generic) peer address with expiration date
40type HelloAddress struct {
41 Transport string // Name of transport
42 AddrSize uint16 `order:"big"` // Size of address entry
43 ExpireOn util.AbsoluteTime // Expiry date
44 Address []byte `size:"AddrSize"` // Address specification
45}
46
47// NewHelloAddress create a new HELLO address from the given address
48func NewHelloAddress(a *util.Address) *HelloAddress {
49 addr := &HelloAddress{
50 Transport: a.Netw,
51 AddrSize: uint16(len(a.Address)),
52 ExpireOn: a.Expires,
53 Address: make([]byte, len(a.Address)),
54 }
55 copy(addr.Address, a.Address)
56 return addr
57}
58
59// String returns a human-readable representation of the message.
60func (a *HelloAddress) String() string {
61 return fmt.Sprintf("Address{%s,expire=%s}",
62 util.AddressString(a.Transport, a.Address), a.ExpireOn)
63}
64
65// HelloMsg is a message send by peers to announce their presence
66type HelloMsg struct {
67 MsgSize uint16 `order:"big"` // total size of message
68 MsgType uint16 `order:"big"` // HELLO (17)
69 FriendOnly uint32 `order:"big"` // =1: do not gossip this HELLO
70 PeerID *util.PeerID // EdDSA public key (long-term)
71 Addresses []*HelloAddress `size:"*"` // List of end-point addressess
72}
73
74// NewHelloMsg creates a new HELLO msg for a given peer.
75func NewHelloMsg(peerid *util.PeerID) *HelloMsg {
76 if peerid == nil {
77 peerid = util.NewPeerID(nil)
78 }
79 return &HelloMsg{
80 MsgSize: 40,
81 MsgType: HELLO,
82 FriendOnly: 0,
83 PeerID: peerid,
84 Addresses: make([]*HelloAddress, 0),
85 }
86}
87
88// String returns a human-readable representation of the message.
89func (m *HelloMsg) String() string {
90 return fmt.Sprintf("HelloMsg{peer=%s,friendsonly=%d,addr=%v}",
91 m.PeerID, m.FriendOnly, m.Addresses)
92}
93
94// AddAddress adds a new address to the HELLO message.
95func (m *HelloMsg) AddAddress(a *HelloAddress) {
96 m.Addresses = append(m.Addresses, a)
97 m.MsgSize += uint16(len(a.Transport)) + a.AddrSize + 11
98}
99
100// Header returns the message header in a separate instance.
101func (m *HelloMsg) Header() *Header {
102 return &Header{m.MsgSize, m.MsgType}
103}
diff --git a/src/gnunet/message/msg_namecache.go b/src/gnunet/message/msg_namecache.go
index c3a0ac7..5acb807 100644
--- a/src/gnunet/message/msg_namecache.go
+++ b/src/gnunet/message/msg_namecache.go
@@ -21,8 +21,8 @@ package message
21import ( 21import (
22 "encoding/hex" 22 "encoding/hex"
23 "fmt" 23 "fmt"
24
25 "gnunet/crypto" 24 "gnunet/crypto"
25 "gnunet/service/dht/blocks"
26 "gnunet/util" 26 "gnunet/util"
27) 27)
28 28
@@ -41,7 +41,7 @@ type NamecacheLookupMsg struct {
41// NewNamecacheLookupMsg creates a new default message. 41// NewNamecacheLookupMsg creates a new default message.
42func NewNamecacheLookupMsg(query *crypto.HashCode) *NamecacheLookupMsg { 42func NewNamecacheLookupMsg(query *crypto.HashCode) *NamecacheLookupMsg {
43 if query == nil { 43 if query == nil {
44 query = crypto.NewHashCode() 44 query = crypto.NewHashCode(nil)
45 } 45 }
46 return &NamecacheLookupMsg{ 46 return &NamecacheLookupMsg{
47 MsgSize: 72, 47 MsgSize: 72,
@@ -114,7 +114,7 @@ type NamecacheCacheMsg struct {
114} 114}
115 115
116// NewNamecacheCacheMsg creates a new default message. 116// NewNamecacheCacheMsg creates a new default message.
117func NewNamecacheCacheMsg(block *Block) *NamecacheCacheMsg { 117func NewNamecacheCacheMsg(block *blocks.GNSBlock) *NamecacheCacheMsg {
118 msg := &NamecacheCacheMsg{ 118 msg := &NamecacheCacheMsg{
119 MsgSize: 108, 119 MsgSize: 108,
120 MsgType: NAMECACHE_BLOCK_CACHE, 120 MsgType: NAMECACHE_BLOCK_CACHE,
@@ -125,10 +125,10 @@ func NewNamecacheCacheMsg(block *Block) *NamecacheCacheMsg {
125 } 125 }
126 if block != nil { 126 if block != nil {
127 msg.DerivedKeySig = block.DerivedKeySig 127 msg.DerivedKeySig = block.DerivedKeySig
128 msg.Expire = block.Block.Expire 128 msg.Expire = block.Body.Expire
129 size := len(block.Block.EncData) 129 size := len(block.Body.Data)
130 msg.EncData = make([]byte, size) 130 msg.EncData = make([]byte, size)
131 copy(msg.EncData, block.Block.EncData) 131 copy(msg.EncData, block.Body.Data)
132 msg.MsgSize += uint16(size) 132 msg.MsgSize += uint16(size)
133 } 133 }
134 return msg 134 return msg
diff --git a/src/gnunet/message/msg_transport.go b/src/gnunet/message/msg_transport.go
index 6e64c4d..d0e927b 100644
--- a/src/gnunet/message/msg_transport.go
+++ b/src/gnunet/message/msg_transport.go
@@ -1,5 +1,5 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang. 1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y< 2// Copyright (C) 2019-2022 Bernd Fix >Y<
3// 3//
4// gnunet-go is free software: you can redistribute it and/or modify it 4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published 5// under the terms of the GNU Affero General Public License as published
@@ -225,86 +225,6 @@ func (m *TransportPongMsg) Verify(pub *ed25519.PublicKey) (bool, error) {
225} 225}
226 226
227//---------------------------------------------------------------------- 227//----------------------------------------------------------------------
228// HELLO
229//
230// A HELLO message is used to exchange information about
231// transports with other peers. This struct is always
232// followed by the actual network addresses which have
233// the format:
234//
235// 1) transport-name (0-terminated)
236// 2) address-length (uint16_t, network byte order)
237// 3) address expiration
238// 4) address (address-length bytes)
239//----------------------------------------------------------------------
240
241// HelloAddress represents a (generic) peer address with expiration date
242type HelloAddress struct {
243 Transport string // Name of transport
244 AddrSize uint16 `order:"big"` // Size of address entry
245 ExpireOn util.AbsoluteTime // Expiry date
246 Address []byte `size:"AddrSize"` // Address specification
247}
248
249// NewHelloAddress create a new HELLO address from the given address
250func NewHelloAddress(a *util.Address) *HelloAddress {
251 addr := &HelloAddress{
252 Transport: a.Transport,
253 AddrSize: uint16(len(a.Address)),
254 ExpireOn: util.AbsoluteTimeNow().Add(12 * time.Hour),
255 Address: make([]byte, len(a.Address)),
256 }
257 copy(addr.Address, a.Address)
258 return addr
259}
260
261// String returns a human-readable representation of the message.
262func (a *HelloAddress) String() string {
263 return fmt.Sprintf("Address{%s,expire=%s}",
264 util.AddressString(a.Transport, a.Address), a.ExpireOn)
265}
266
267// HelloMsg is a message send by peers to announce their presence
268type HelloMsg struct {
269 MsgSize uint16 `order:"big"` // total size of message
270 MsgType uint16 `order:"big"` // HELLO (17)
271 FriendOnly uint32 `order:"big"` // =1: do not gossip this HELLO
272 PeerID *util.PeerID // EdDSA public key (long-term)
273 Addresses []*HelloAddress `size:"*"` // List of end-point addressess
274}
275
276// NewHelloMsg creates a new HELLO msg for a given peer.
277func NewHelloMsg(peerid *util.PeerID) *HelloMsg {
278 if peerid == nil {
279 peerid = util.NewPeerID(nil)
280 }
281 return &HelloMsg{
282 MsgSize: 40,
283 MsgType: HELLO,
284 FriendOnly: 0,
285 PeerID: peerid,
286 Addresses: make([]*HelloAddress, 0),
287 }
288}
289
290// String returns a human-readable representation of the message.
291func (m *HelloMsg) String() string {
292 return fmt.Sprintf("HelloMsg{peer=%s,friendsonly=%d,addr=%v}",
293 m.PeerID, m.FriendOnly, m.Addresses)
294}
295
296// AddAddress adds a new address to the HELLO message.
297func (m *HelloMsg) AddAddress(a *HelloAddress) {
298 m.Addresses = append(m.Addresses, a)
299 m.MsgSize += uint16(len(a.Transport)) + a.AddrSize + 11
300}
301
302// Header returns the message header in a separate instance.
303func (m *HelloMsg) Header() *Header {
304 return &Header{m.MsgSize, m.MsgType}
305}
306
307//----------------------------------------------------------------------
308// TRANSPORT_SESSION_ACK 228// TRANSPORT_SESSION_ACK
309//---------------------------------------------------------------------- 229//----------------------------------------------------------------------
310 230
diff --git a/src/gnunet/modules.go b/src/gnunet/modules.go
index f4fdb1b..e47699f 100644
--- a/src/gnunet/modules.go
+++ b/src/gnunet/modules.go
@@ -52,29 +52,33 @@ func (inst Instances) Register() {
52 rpc.Register(inst.Revocation) 52 rpc.Register(inst.Revocation)
53} 53}
54 54
55// Local reference to instance list 55// Local reference to instance list. The list is initialized
56// by core.
56var ( 57var (
57 Modules Instances 58 Modules Instances
58) 59)
59 60
61/* TODO: implement
60// Initialize instance list and link module functions as required. 62// Initialize instance list and link module functions as required.
61func init() { 63// This function is called by core on start-up.
64func Init(ctx context.Context) {
62 65
63 // Namecache (no calls to other modules) 66 // Namecache (no calls to other modules)
64 Modules.Namecache = new(namecache.NamecacheModule) 67 Modules.Namecache = namecache.NewModule(ctx, c)
65 68
66 // DHT (no calls to other modules) 69 // DHT (no calls to other modules)
67 Modules.DHT = new(dht.Module) 70 Modules.DHT = dht.NewModule(ctx, c)
68 71
69 // Revocation (no calls to other modules) 72 // Revocation (no calls to other modules)
70 Modules.Revocation = revocation.NewModule() 73 Modules.Revocation = revocation.NewModule(ctx, c)
71 74
72 // GNS (calls Namecache, DHT and Identity) 75 // GNS (calls Namecache, DHT and Identity)
73 Modules.GNS = &gns.Module{ 76 gns := gns.NewModule(ctx, c)
74 LookupLocal: Modules.Namecache.Get, 77 Modules.GNS = gns
75 StoreLocal: Modules.Namecache.Put, 78 gns.LookupLocal = Modules.Namecache.Get
76 LookupRemote: Modules.DHT.Get, 79 gns.StoreLocal = Modules.Namecache.Put
77 RevocationQuery: Modules.Revocation.Query, 80 gns.LookupRemote = Modules.DHT.Get
78 RevocationRevoke: Modules.Revocation.Revoke, 81 gns.RevocationQuery = Modules.Revocation.Query
79 } 82 gns.RevocationRevoke = Modules.Revocation.Revoke
80} 83}
84*/
diff --git a/src/gnunet/service/client.go b/src/gnunet/service/client.go
index a196c09..19dc4c4 100644
--- a/src/gnunet/service/client.go
+++ b/src/gnunet/service/client.go
@@ -19,39 +19,39 @@
19package service 19package service
20 20
21import ( 21import (
22 "context"
22 "gnunet/message" 23 "gnunet/message"
23 "gnunet/transport"
24 24
25 "github.com/bfix/gospel/logger" 25 "github.com/bfix/gospel/logger"
26) 26)
27 27
28// Client type: Use to perform client-side interactions with GNUnet services. 28// Client type: Use to perform client-side interactions with GNUnet services.
29type Client struct { 29type Client struct {
30 ch *transport.MsgChannel // channel for message exchange 30 ch *Connection // channel for message exchange
31} 31}
32 32
33// NewClient creates a new client instance for the given channel endpoint. 33// NewClient connects to a socket with given path
34func NewClient(endp string) (*Client, error) { 34func NewClient(ctx context.Context, path string) (*Client, error) {
35 // create a new channel to endpoint. 35 // create a connection
36 ch, err := transport.NewChannel(endp) 36 ch, err := NewConnection(ctx, path)
37 if err != nil { 37 if err != nil {
38 return nil, err 38 return nil, err
39 } 39 }
40 // wrap into a message channel for the client. 40 // wrap into a message channel for the client.
41 return &Client{ 41 return &Client{
42 ch: transport.NewMsgChannel(ch), 42 ch: ch,
43 }, nil 43 }, nil
44} 44}
45 45
46// SendRequest sends a give message to the service. 46// SendRequest sends a message to the service.
47func (c *Client) SendRequest(ctx *SessionContext, req message.Message) error { 47func (c *Client) SendRequest(ctx context.Context, req message.Message) error {
48 return c.ch.Send(req, ctx.Signaller()) 48 return c.ch.Send(ctx, req)
49} 49}
50 50
51// ReceiveResponse waits for a response from the service; it can be interrupted 51// ReceiveResponse waits for a response from the service; it can be interrupted
52// by sending "false" to the cmd channel. 52// by sending "false" to the cmd channel.
53func (c *Client) ReceiveResponse(ctx *SessionContext) (message.Message, error) { 53func (c *Client) ReceiveResponse(ctx context.Context) (message.Message, error) {
54 return c.ch.Receive(ctx.Signaller()) 54 return c.ch.Receive(ctx)
55} 55}
56 56
57// Close a client; no further message exchange is possible. 57// Close a client; no further message exchange is possible.
@@ -62,15 +62,15 @@ func (c *Client) Close() error {
62// RequestResponse is a helper method for a one request - one response 62// RequestResponse is a helper method for a one request - one response
63// secenarios of client/serice interactions. 63// secenarios of client/serice interactions.
64func RequestResponse( 64func RequestResponse(
65 ctx *SessionContext, 65 ctx context.Context,
66 caller string, 66 caller string,
67 callee string, 67 callee string,
68 endp string, 68 path string,
69 req message.Message) (message.Message, error) { 69 req message.Message) (message.Message, error) {
70 70
71 // client-connect to the service 71 // client-connect to the service
72 logger.Printf(logger.DBG, "[%s] Connecting to %s service...\n", caller, callee) 72 logger.Printf(logger.DBG, "[%s] Connecting to %s service...\n", caller, callee)
73 cl, err := NewClient(endp) 73 cl, err := NewClient(ctx, path)
74 if err != nil { 74 if err != nil {
75 return nil, err 75 return nil, err
76 } 76 }
diff --git a/src/gnunet/service/connection.go b/src/gnunet/service/connection.go
new file mode 100644
index 0000000..1c690c5
--- /dev/null
+++ b/src/gnunet/service/connection.go
@@ -0,0 +1,280 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package service
20
21import (
22 "context"
23 "errors"
24 "fmt"
25 "gnunet/message"
26 "net"
27 "os"
28 "strconv"
29
30 "github.com/bfix/gospel/data"
31 "github.com/bfix/gospel/logger"
32)
33
34// Error codes
35var (
36 ErrConnectionNotOpened = errors.New("channel not opened")
37 ErrConnectionInterrupted = errors.New("channel interrupted")
38)
39
40//======================================================================
41
42// Connection is a channel for GNUnet message exchange (send/receive)
43// based on Unix domain sockets. It is used locally by services and
44// clients in the standard GNUnet environment.
45type Connection struct {
46 path string // file name of Unix socket
47 conn net.Conn // associated connection
48 buf []byte // read/write buffer
49}
50
51// NewConnection creates a new connection to a socket with given path.
52// This is used by clients to connect to a service.
53func NewConnection(ctx context.Context, path string) (s *Connection, err error) {
54 var d net.Dialer
55 s = new(Connection)
56 s.path = path
57 s.buf = make([]byte, 65536)
58 s.conn, err = d.DialContext(ctx, "unix", path)
59 return
60}
61
62// Close a socket connection
63func (s *Connection) Close() error {
64 if s.conn != nil {
65 rc := s.conn.Close()
66 s.conn = nil
67 return rc
68 }
69 return ErrConnectionNotOpened
70}
71
72// Send a GNUnet message over a socket.
73func (s *Connection) Send(ctx context.Context, msg message.Message) error {
74 // convert message to binary data
75 data, err := data.Marshal(msg)
76 if err != nil {
77 return err
78 }
79 // check message header size and packet size
80 mh, err := message.GetMsgHeader(data)
81 if err != nil {
82 return err
83 }
84 if len(data) != int(mh.MsgSize) {
85 return errors.New("send: message size mismatch")
86 }
87
88 // send packet
89 n, err := s.write(ctx, data)
90 if err != nil {
91 return err
92 }
93 if n != len(data) {
94 return errors.New("incomplete send")
95 }
96 return nil
97}
98
99// Receive GNUnet messages from socket.
100func (s *Connection) Receive(ctx context.Context) (message.Message, error) {
101 // get bytes from socket
102 get := func(pos, count int) error {
103 n, err := s.read(ctx, s.buf[pos:pos+count])
104 if err != nil {
105 return err
106 }
107 if n != count {
108 return errors.New("not enough bytes on network")
109 }
110 return nil
111 }
112 // read header first
113 if err := get(0, 4); err != nil {
114 return nil, err
115 }
116 mh, err := message.GetMsgHeader(s.buf[:4])
117 if err != nil {
118 return nil, err
119 }
120 // get rest of message
121 if err := get(4, int(mh.MsgSize)-4); err != nil {
122 return nil, err
123 }
124 msg, err := message.NewEmptyMessage(mh.MsgType)
125 if err != nil {
126 return nil, err
127 }
128 if msg == nil {
129 return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
130 }
131 if err = data.Unmarshal(msg, s.buf[:mh.MsgSize]); err != nil {
132 return nil, err
133 }
134 return msg, nil
135}
136
137//----------------------------------------------------------------------
138// internal methods
139//----------------------------------------------------------------------
140
141// result of read/write operations on sockets.
142type result struct {
143 n int // number of bytes read/written
144 err error // error (or nil)
145}
146
147// Read bytes from a socket into buffer: Returns the number of read
148// bytes and an error code. Only works on open channels ;)
149func (s *Connection) read(ctx context.Context, buf []byte) (int, error) {
150 // check if the channel is open
151 if s.conn == nil {
152 return 0, ErrConnectionNotOpened
153 }
154 // perform read operation
155 ch := make(chan *result)
156 go func() {
157 n, err := s.conn.Read(buf)
158 ch <- &result{n, err}
159 }()
160 for {
161 select {
162 // terminate on request
163 case <-ctx.Done():
164 return 0, ErrConnectionInterrupted
165
166 // handle result of read operation
167 case res := <-ch:
168 return res.n, res.err
169 }
170 }
171}
172
173// Write buffer to socket and returns the number of bytes written and an
174// optional error code.
175func (s *Connection) write(ctx context.Context, buf []byte) (int, error) {
176 // check if we have an open socket to write to.
177 if s.conn == nil {
178 return 0, ErrConnectionNotOpened
179 }
180 // perform write operation
181 ch := make(chan *result)
182 go func() {
183 n, err := s.conn.Write(buf)
184 ch <- &result{n, err}
185 }()
186 for {
187 select {
188 // handle terminate command
189 case <-ctx.Done():
190 return 0, ErrConnectionInterrupted
191
192 // handle result of write operation
193 case res := <-ch:
194 return res.n, res.err
195 }
196 }
197}
198
199//======================================================================
200
201// ConnectionManager to handle client connections on a socket.
202type ConnectionManager struct {
203 listener net.Listener // reference to listener object
204 running bool // server running?
205}
206
207// NewConnectionManager creates a new socket connection manager. Incoming
208// connections from clients are dispatched to a handler channel.
209func NewConnectionManager(
210 ctx context.Context, // execution context
211 path string, // socket file name
212 params map[string]string, // connection parameters
213 hdlr chan *Connection, // handler for incoming connections
214) (cs *ConnectionManager, err error) {
215
216 // instantiate channel server
217 cs = &ConnectionManager{
218 listener: nil,
219 running: false,
220 }
221 // create listener
222 var lc net.ListenConfig
223 if cs.listener, err = lc.Listen(ctx, "unix", path); err != nil {
224 return
225 }
226 // handle additional parameters
227 if params != nil {
228 for key, value := range params {
229 switch key {
230 case "perm": // set permissions on 'unix'
231 if perm, err := strconv.ParseInt(value, 8, 32); err == nil {
232 if err := os.Chmod(path, os.FileMode(perm)); err != nil {
233 logger.Printf(
234 logger.ERROR,
235 "MsgChannelServer: Failed to set permissions %s on %s: %s\n",
236 path, value, err.Error())
237
238 }
239 } else {
240 logger.Printf(
241 logger.ERROR,
242 "MsgChannelServer: Invalid permissions '%s'\n",
243 value)
244 }
245 }
246 }
247 }
248 // run go routine to handle channel requests from clients
249 cs.running = true
250 go func() {
251 for cs.running {
252 conn, err := cs.listener.Accept()
253 if err != nil {
254 break
255 }
256 // handle connection
257 c := &Connection{
258 conn: conn,
259 path: path,
260 buf: make([]byte, 65536),
261 }
262 hdlr <- c
263 }
264 if cs.listener != nil {
265 cs.listener.Close()
266 }
267 }()
268 return cs, nil
269}
270
271// Close a network channel server (= stop the server)
272func (s *ConnectionManager) Close() error {
273 s.running = false
274 if s.listener != nil {
275 err := s.listener.Close()
276 s.listener = nil
277 return err
278 }
279 return nil
280}
diff --git a/src/gnunet/service/context.go b/src/gnunet/service/context.go
deleted file mode 100644
index 4ae786d..0000000
--- a/src/gnunet/service/context.go
+++ /dev/null
@@ -1,87 +0,0 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package service
20
21import (
22 "sync"
23
24 "gnunet/util"
25
26 "github.com/bfix/gospel/concurrent"
27)
28
29// SessionContext is used to set a context for each client connection handled
30// by a service; the session is handled by the 'ServeClient' method of a
31// service implementation.
32type SessionContext struct {
33 ID int // session identifier
34 wg *sync.WaitGroup // wait group for the session
35 sig *concurrent.Signaller // signaller for the session
36 pending int // number of pending go-routines
37 active bool // is the context active (un-cancelled)?
38 onCancel *sync.Mutex // only run one Cancel() at a time
39}
40
41// NewSessionContext instantiates a new session context.
42func NewSessionContext() *SessionContext {
43 return &SessionContext{
44 ID: util.NextID(),
45 wg: new(sync.WaitGroup),
46 sig: concurrent.NewSignaller(),
47 pending: 0,
48 active: true,
49 onCancel: new(sync.Mutex),
50 }
51}
52
53// Cancel all go-routines associated with this context.
54func (ctx *SessionContext) Cancel() {
55 ctx.onCancel.Lock()
56 if ctx.active {
57 // we are going out-of-business
58 ctx.active = false
59 // send signal to terminate...
60 ctx.sig.Send(true)
61 // wait for session go-routines to finish
62 ctx.wg.Wait()
63 }
64 ctx.onCancel.Unlock()
65}
66
67// Add a go-routine to the wait group.
68func (ctx *SessionContext) Add() {
69 ctx.wg.Add(1)
70 ctx.pending++
71}
72
73// Remove a go-routine from the wait group.
74func (ctx *SessionContext) Remove() {
75 ctx.wg.Done()
76 ctx.pending--
77}
78
79// Waiting returns the number of waiting go-routines.
80func (ctx *SessionContext) Waiting() int {
81 return ctx.pending
82}
83
84// Signaller returns the working instance for the context.
85func (ctx *SessionContext) Signaller() *concurrent.Signaller {
86 return ctx.sig
87}
diff --git a/src/gnunet/service/dht/blocks/generic.go b/src/gnunet/service/dht/blocks/generic.go
new file mode 100644
index 0000000..6301e3b
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/generic.go
@@ -0,0 +1,196 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package blocks
20
21import (
22 "bytes"
23 "encoding/gob"
24 "encoding/hex"
25 "fmt"
26 "gnunet/crypto"
27 "gnunet/util"
28
29 "github.com/bfix/gospel/data"
30)
31
32//----------------------------------------------------------------------
33// Query/Block interfaces for generic DHT handling
34//----------------------------------------------------------------------
35
36// DHT Query interface
37type Query interface {
38
39 // Key returns the DHT key for a block
40 Key() *crypto.HashCode
41
42 // Get retrieves the value of a named query parameter. The value is
43 // unchanged if the key is not in the map or if the value in the map
44 // has an incompatible type.
45 Get(key string, value any) bool
46
47 // Set stores the value of a named query parameter
48 Set(key string, value any)
49
50 // Verify the integrity of a retrieved block (optional). Override in
51 // custom query types to implement block-specific integrity checks
52 // (see GNSQuery for example).
53 Verify(blk Block) error
54
55 // Decrypt block content (optional). Override in custom query types to
56 // implement block-specific encryption (see GNSQuery for example).
57 Decrypt(blk Block) error
58
59 // String returns the human-readable representation of a query
60 String() string
61}
62
63// DHT Block interface
64type Block interface {
65
66 // Data returns the DHT block data (unstructured without type and
67 // expiration information.
68 Data() []byte
69
70 // Return the block type
71 Type() uint16
72
73 // Expire returns the block expiration
74 Expire() util.AbsoluteTime
75
76 // Verify the integrity of a block (optional). Override in custom query
77 // types to implement block-specific integrity checks (see GNSBlock for
78 // example). This verification is usually weaker than the verification
79 // method from a Query (see GNSBlock.Verify for explanation).
80 Verify() error
81
82 // String returns the human-readable representation of a block
83 String() string
84}
85
86// Unwrap (raw) block to a specific block type
87func Unwrap(blk Block, obj interface{}) error {
88 return data.Unmarshal(obj, blk.Data())
89}
90
91//----------------------------------------------------------------------
92// Generic interface implementations without persistent attributes
93//----------------------------------------------------------------------
94
95// GenericQuery is the binary representation of a DHT key
96type GenericQuery struct {
97 // Key for repository queries (local/remote)
98 key *crypto.HashCode
99
100 // query parameters (binary value representation)
101 params map[string][]byte
102}
103
104// Key interface method implementation
105func (q *GenericQuery) Key() *crypto.HashCode {
106 return q.key
107}
108
109// Get retrieves the value of a named query parameter
110func (q *GenericQuery) Get(key string, value any) bool {
111 data, ok := q.params[key]
112 if !ok {
113 return false
114 }
115 dec := gob.NewDecoder(bytes.NewReader(data))
116 return dec.Decode(value) != nil
117}
118
119// Set stores the value of a named query parameter
120func (q *GenericQuery) Set(key string, value any) {
121 wrt := new(bytes.Buffer)
122 enc := gob.NewEncoder(wrt)
123 if enc.Encode(value) == nil {
124 q.params[key] = wrt.Bytes()
125 }
126}
127
128// Verify interface method implementation
129func (q *GenericQuery) Verify(b Block) error {
130 // no verification, no errors ;)
131 return nil
132}
133
134// Decrypt interface method implementation
135func (q *GenericQuery) Decrypt(b Block) error {
136 // no decryption, no errors ;)
137 return nil
138}
139
140// String returns the human-readable representation of a block
141func (q *GenericQuery) String() string {
142 return fmt.Sprintf("GenericQuery{key=%s}", hex.EncodeToString(q.Key().Bits))
143}
144
145// NewGenericQuery creates a simple Query from hash code.
146func NewGenericQuery(buf []byte) *GenericQuery {
147 return &GenericQuery{
148 key: crypto.NewHashCode(buf),
149 params: make(map[string][]byte),
150 }
151}
152
153//----------------------------------------------------------------------
154
155// GenericBlock is the block in simple binary representation
156type GenericBlock struct {
157 block []byte // block data
158 btype uint16 // block type
159 expire util.AbsoluteTime // expiration date
160}
161
162// Data interface method implementation
163func (b *GenericBlock) Data() []byte {
164 return b.block
165}
166
167// Type returns the block type
168func (b *GenericBlock) Type() uint16 {
169 return b.btype
170}
171
172// Expire returns the block expiration
173func (b *GenericBlock) Expire() util.AbsoluteTime {
174 return b.expire
175}
176
177// String returns the human-readable representation of a block
178func (b *GenericBlock) String() string {
179 return fmt.Sprintf("GenericBlock{type=%d,expires=%s,data=[%d]}",
180 b.btype, b.expire.String(), len(b.block))
181}
182
183// Verify interface method implementation
184func (b *GenericBlock) Verify() error {
185 // no verification, no errors ;)
186 return nil
187}
188
189// NewGenericBlock creates a Block from binary data.
190func NewGenericBlock(buf []byte) *GenericBlock {
191 return &GenericBlock{
192 block: util.Clone(buf),
193 btype: DHT_BLOCK_ANY, // unknown block type
194 expire: util.AbsoluteTimeNever(), // never expires
195 }
196}
diff --git a/src/gnunet/service/dht/blocks/generic_test.go b/src/gnunet/service/dht/blocks/generic_test.go
new file mode 100644
index 0000000..51ee5a1
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/generic_test.go
@@ -0,0 +1,67 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package blocks
20
21import (
22 "bytes"
23 "testing"
24)
25
26// Test parameter handling for queries
27func TestQueryParams(t *testing.T) {
28 q := NewGenericQuery(nil)
29
30 // set parameters
31 var (
32 btype uint16 = DHT_BLOCK_ANY
33 flags uint32 = 0
34 name string = "Test"
35 data = make([]byte, 8)
36 )
37 q.Set("btype", btype)
38 q.Set("flags", flags)
39 q.Set("name", name)
40 q.Set("data", data)
41
42 // get parameters
43 var (
44 t_btype uint16
45 t_flags uint32
46 t_name string
47 t_data []byte
48 )
49 q.Get("btype", &t_btype)
50 q.Get("flags", &t_flags)
51 q.Get("name", &t_name)
52 q.Get("data", &t_data)
53
54 // check for unchanged data
55 if btype != t_btype {
56 t.Fatal("btype mismatch")
57 }
58 if flags != t_flags {
59 t.Fatal("flags mismatch")
60 }
61 if name != t_name {
62 t.Fatal("name mismatch")
63 }
64 if !bytes.Equal(data, t_data) {
65 t.Fatal("data mismatch")
66 }
67}
diff --git a/src/gnunet/service/dht/blocks/gns.go b/src/gnunet/service/dht/blocks/gns.go
new file mode 100644
index 0000000..2085677
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/gns.go
@@ -0,0 +1,172 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package blocks
20
21import (
22 "errors"
23 "fmt"
24 "gnunet/crypto"
25 "gnunet/util"
26
27 "github.com/bfix/gospel/data"
28)
29
30// Error messages
31var (
32 ErrBlockNotDecrypted = fmt.Errorf("GNS block not decrypted")
33)
34
35//----------------------------------------------------------------------
36// Query key for GNS lookups
37//----------------------------------------------------------------------
38
39// GNSQuery specifies the context for a basic GNS name lookup of an (atomic)
40// label in a given zone identified by its public key.
41type GNSQuery struct {
42 GenericQuery
43 Zone *crypto.ZoneKey // Public zone key
44 Label string // Atomic label
45 derived *crypto.ZoneKey // Derived zone key from (pkey,label)
46}
47
48// Verify the integrity of the block data from a signature.
49func (q *GNSQuery) Verify(b Block) (err error) {
50 switch blk := b.(type) {
51 case *GNSBlock:
52 // Integrity check performed
53 blk.checked = true
54
55 // verify derived key
56 dkey := blk.DerivedKeySig.ZoneKey
57 dkey2, _ := q.Zone.Derive(q.Label, "gns")
58 if !dkey.Equal(dkey2) {
59 return fmt.Errorf("invalid signature key for GNS Block")
60 }
61 // verify signature
62 var buf []byte
63 if buf, err = data.Marshal(blk.Body); err != nil {
64 return
65 }
66 blk.verified, err = blk.DerivedKeySig.Verify(buf)
67
68 default:
69 err = errors.New("can't verify block type")
70 }
71 return
72}
73
74// Decrypt block data with a key derived from zone key and label.
75func (q *GNSQuery) Decrypt(b Block) (err error) {
76 switch blk := b.(type) {
77 case *GNSBlock:
78 // decrypt GNS payload
79 blk.data, err = q.Zone.Decrypt(blk.Body.Data, q.Label, blk.Body.Expire)
80 blk.decrypted = true
81 return
82
83 default:
84 err = errors.New("can't decrypt block type")
85 }
86 return
87}
88
89// NewGNSQuery assembles a new Query object for the given zone and label.
90func NewGNSQuery(zkey *crypto.ZoneKey, label string) *GNSQuery {
91 // derive a public key from (pkey,label) and set the repository
92 // key as the SHA512 hash of the binary key representation.
93 // (key blinding)
94 pd, _ := zkey.Derive(label, "gns")
95 gq := crypto.Hash(pd.Bytes()).Bits
96 return &GNSQuery{
97 GenericQuery: *NewGenericQuery(gq),
98 Zone: zkey,
99 Label: label,
100 derived: pd,
101 }
102}
103
104//----------------------------------------------------------------------
105// GNS blocks
106//----------------------------------------------------------------------
107
108// SignedGNSBlockData represents the signed content of a GNS block
109type SignedGNSBlockData struct {
110 Purpose *crypto.SignaturePurpose `` // Size and purpose of signature (8 bytes)
111 Expire util.AbsoluteTime `` // Expiration time of the block.
112 Data []byte `size:"*"` // Block data content
113}
114
115// GNSBlock is the result of GNS lookups for a given label in a zone.
116// An encrypted and signed container for GNS resource records that represents
117// the "atomic" data structure associated with a GNS label in a given zone.
118type GNSBlock struct {
119 GenericBlock
120
121 // persistent
122 DerivedKeySig *crypto.ZoneSignature // Derived key used for signing
123 Body *SignedGNSBlockData
124
125 // transient data (not serialized)
126 checked bool // block integrity checked
127 verified bool // block signature verified (internal)
128 decrypted bool // block decrypted (internal)
129 data []byte // decrypted data
130}
131
132// Data block interface implementation
133func (b *GNSBlock) Data() []byte {
134 buf, _ := data.Marshal(b)
135 return buf
136}
137
138// String returns the human-readable representation of a GNSBlock
139func (b *GNSBlock) String() string {
140 return fmt.Sprintf("GNSBlock{Verified=%v,Decrypted=%v,data=[%d]}",
141 b.verified, b.decrypted, len(b.Body.Data))
142}
143
144// NewBlock instantiates an empty GNS block
145func NewBlock() *GNSBlock {
146 return &GNSBlock{
147 DerivedKeySig: nil,
148 Body: &SignedGNSBlockData{
149 Purpose: new(crypto.SignaturePurpose),
150 Expire: *new(util.AbsoluteTime),
151 Data: nil,
152 },
153 checked: false,
154 verified: false,
155 decrypted: false,
156 data: nil,
157 }
158}
159
160// Verify the integrity of the block data from a signature.
161// Only the cryptographic signature is verified; the formal correctness of
162// the association between the block and a GNS label in a GNS zone can't
163// be verified. This is only possible in Query.Verify().
164func (b *GNSBlock) Verify() (err error) {
165 // verify signature
166 var buf []byte
167 if buf, err = data.Marshal(b.Body); err != nil {
168 return
169 }
170 _, err = b.DerivedKeySig.Verify(buf)
171 return
172}
diff --git a/src/gnunet/service/dht/blocks/hello.go b/src/gnunet/service/dht/blocks/hello.go
new file mode 100644
index 0000000..77fc2ae
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/hello.go
@@ -0,0 +1,226 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package blocks
20
21import (
22 "bytes"
23 "encoding/binary"
24 "fmt"
25 "gnunet/util"
26 "net/url"
27 "strconv"
28 "strings"
29
30 "github.com/bfix/gospel/crypto/ed25519"
31 "github.com/bfix/gospel/data"
32)
33
34//----------------------------------------------------------------------
35// HELLO URLs are used for bootstrapping a node and for adding nodes
36// outside of GNUnet message exchange (e.g. command-line tools)
37//----------------------------------------------------------------------
38
39const helloPrefix = "gnunet://hello/"
40
41// HelloBlock is the DHT-managed block type for HELLO information.
42// It is used to create and parse HELLO URLs.
43// All addresses expire at the same time /this different from HELLO
44// messages (see message.HeeloMsg).
45type HelloBlock struct {
46 PeerID *util.PeerID `` // peer identifier
47 Signature *ed25519.EdSignature `` // signature
48 Expire util.AbsoluteTime `` // Expiration date
49 AddrBin []byte `size:"*"` // raw address data
50
51 // transient attributes
52 addrs []*util.Address // cooked address data
53}
54
55// SetAddresses adds a bulk of addresses for this HELLO block.
56func (h *HelloBlock) SetAddresses(a []*util.Address) {
57 h.addrs = util.Clone(a)
58 h.finalize()
59}
60
61// Addresses returns the list of addresses
62func (h *HelloBlock) Addresses() []*util.Address {
63 return util.Clone(h.addrs)
64}
65
66// ParseHelloURL parses a HELLO URL of the following form:
67// gnunet://hello/<PeerID>/<signature>/<expire>?<addrs>
68// The addresses are encoded.
69func ParseHelloURL(u string) (h *HelloBlock, err error) {
70 // check and trim prefix
71 if !strings.HasPrefix(u, helloPrefix) {
72 err = fmt.Errorf("invalid HELLO-URL prefix: '%s'", u)
73 return
74 }
75 u = u[len(helloPrefix):]
76
77 // split remainder into parts
78 p := strings.Split(u, "/")
79 if len(p) != 3 {
80 err = fmt.Errorf("invalid HELLO-URL: '%s'", u)
81 return
82 }
83
84 // assemble HELLO data
85 h = new(HelloBlock)
86
87 // (1) parse peer public key (peer ID)
88 var buf []byte
89 if buf, err = util.DecodeStringToBinary(p[0], 32); err != nil {
90 return
91 }
92 h.PeerID = util.NewPeerID(buf)
93
94 // (2) parse signature
95 if buf, err = util.DecodeStringToBinary(p[1], 64); err != nil {
96 return
97 }
98 if h.Signature, err = ed25519.NewEdSignatureFromBytes(buf); err != nil {
99 return
100 }
101
102 // (3) split last element into parts
103 q := strings.SplitN(p[2], "?", 2)
104
105 // (4) parse expiration date
106 var exp uint64
107 if exp, err = strconv.ParseUint(q[0], 10, 64); err != nil {
108 return
109 }
110 h.Expire = util.NewAbsoluteTimeEpoch(exp)
111
112 // (5) process addresses.
113 h.addrs = make([]*util.Address, 0)
114 var ua string
115 for _, a := range strings.Split(q[1], "&") {
116 // unescape URL query
117 if ua, err = url.QueryUnescape(a); err != nil {
118 return
119 }
120 // parse address and append it to list
121 var addr *util.Address
122 if addr, err = util.ParseAddress(ua); err != nil {
123 return
124 }
125 h.addrs = append(h.addrs, addr)
126 }
127
128 // (6) generate raw address data so block is complete
129 h.finalize()
130 return
131}
132
133// ParseHelloFromBytes converts a byte array into a HelloBlock instance.
134func ParseHelloFromBytes(buf []byte) (h *HelloBlock, err error) {
135 h = new(HelloBlock)
136 if err = data.Unmarshal(h, buf); err == nil {
137 err = h.finalize()
138 }
139 return
140}
141
142// finalize block data (generate dependent fields)
143func (h *HelloBlock) finalize() (err error) {
144 if h.addrs == nil {
145 err = data.Unmarshal(h.addrs, h.AddrBin)
146 } else if h.AddrBin == nil {
147 wrt := new(bytes.Buffer)
148 for _, a := range h.addrs {
149 wrt.WriteString(a.String())
150 wrt.WriteByte(0)
151 }
152 h.AddrBin = wrt.Bytes()
153 }
154 return
155}
156
157/*
158// Message returns the corresponding HELLO message to be sent to peers.
159func (h *HelloBlock) Message() *message.HelloMsg {
160 msg := message.NewHelloMsg(h.PeerID)
161 for _, a := range h.addrs {
162 msg.AddAddress(message.NewHelloAddress(a, h.Expire))
163 }
164 return msg
165}
166*/
167
168// URL returns the HELLO URL for the data.
169func (h *HelloBlock) URL() string {
170 u := fmt.Sprintf("%s%s/%s/%d?",
171 helloPrefix,
172 h.PeerID.String(),
173 util.EncodeBinaryToString(h.Signature.Bytes()),
174 h.Expire.Epoch(),
175 )
176 for i, a := range h.addrs {
177 if i > 0 {
178 u += "&"
179 }
180 u += url.QueryEscape(a.String())
181 }
182 return u
183}
184
185// Equals returns true if two HELLOs are the same. The expiration
186// timestamp is ignored in the comparision.
187func (h *HelloBlock) Equals(g *HelloBlock) bool {
188 if !h.PeerID.Equals(g.PeerID) ||
189 !util.Equals(h.Signature.Bytes(), g.Signature.Bytes()) ||
190 len(h.addrs) != len(g.addrs) {
191 return false
192 }
193 for i, a := range h.addrs {
194 if !a.Equals(g.addrs[i]) {
195 return false
196 }
197 }
198 return true
199}
200
201// Verify the integrity of the HELLO data
202func (h *HelloBlock) Verify() (bool, error) {
203 // assemble signed data and public key
204 sd := h.signedData()
205 pub := ed25519.NewPublicKeyFromBytes(h.PeerID.Key)
206 return pub.EdVerify(sd, h.Signature)
207}
208
209// Sign the HELLO data with private key
210func (h *HelloBlock) Sign(prv *ed25519.PrivateKey) (err error) {
211 // assemble signed data
212 sd := h.signedData()
213 h.Signature, err = prv.EdSign(sd)
214 return
215}
216
217// signedData assembles a data block for sign and verify operations.
218func (h *HelloBlock) signedData() []byte {
219 buf := new(bytes.Buffer)
220 buf.Write(h.PeerID.Key)
221 binary.Write(buf, binary.BigEndian, h.Expire)
222 for _, a := range h.addrs {
223 buf.Write(a.Address)
224 }
225 return buf.Bytes()
226}
diff --git a/src/gnunet/service/dht/blocks/hello_test.go b/src/gnunet/service/dht/blocks/hello_test.go
new file mode 100644
index 0000000..089259a
--- /dev/null
+++ b/src/gnunet/service/dht/blocks/hello_test.go
@@ -0,0 +1,44 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package blocks
20
21import "testing"
22
23const (
24 helloURL = "gnunet://hello" +
25 "/7KTBJ90340HF1Q2GB0A57E2XJER4FDHX8HP5GHEB9125VPWPD27G" +
26
27 "/BNMDFN6HJCPWSPNBSEC06MC1K8QN1Z2DHRQSRXDTFR7FTBD4JHN" +
28 "BJ2RJAAEZ31FWG1Q3PMN3PXGZQ3Q7NTNEKQZFA7TE2Y46FM8E20R" +
29 "/1653499308" +
30 "?r5n%2Bip%2Budp%3A1.2.3.4%3A6789" +
31 "&gnunet%2Btcp%3A12.3.4.5"
32)
33
34func TestHelloURL(t *testing.T) {
35
36 hd, err := ParseHelloURL(helloURL)
37 if err != nil {
38 t.Fatal(err)
39 }
40 u := hd.URL()
41 if u != helloURL {
42 t.Fatal("urls don't match")
43 }
44}
diff --git a/src/gnunet/transport/session.go b/src/gnunet/service/dht/blocks/types.go
index f5a0787..04edb6e 100644
--- a/src/gnunet/transport/session.go
+++ b/src/gnunet/service/dht/blocks/types.go
@@ -1,5 +1,5 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang. 1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y< 2// Copyright (C) 2019-2022 Bernd Fix >Y<
3// 3//
4// gnunet-go is free software: you can redistribute it and/or modify it 4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published 5// under the terms of the GNU Affero General Public License as published
@@ -16,14 +16,11 @@
16// 16//
17// SPDX-License-Identifier: AGPL3.0-or-later 17// SPDX-License-Identifier: AGPL3.0-or-later
18 18
19package transport 19package blocks
20 20
21// Session states 21// DHT Block types
22const ( 22const (
23 KxStateDown = iota // No handshake yet. 23 DHT_BLOCK_ANY = 0
24 KxStateKeySent // We've sent our session key. 24 DHT_BLOCK_HELLO = 7 // Type of a block that contains a HELLO for a peer
25 KxStateKeyReceived // We've received the other peers session key. 25 DHT_BLOCK_GNS = 11 // Block for storing record data
26 KxStateUp // Key exchange is done.
27 KxStateRekeySent // We're rekeying (or had a timeout).
28 KxPeerDisconnect // Last state of a KX (when it is being terminated).
29) 26)
diff --git a/src/gnunet/service/dht/bloomfilter.go b/src/gnunet/service/dht/bloomfilter.go
new file mode 100644
index 0000000..dcfd935
--- /dev/null
+++ b/src/gnunet/service/dht/bloomfilter.go
@@ -0,0 +1,123 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package dht
20
21import (
22 "bytes"
23 "crypto/sha512"
24 "encoding/binary"
25)
26
27//======================================================================
28// Generic BloomFilter
29//======================================================================
30
31// BloomFilter parameter
32var (
33 bfNumBits = 128
34 bfHash = sha512.New
35)
36
37// BloomFilter is a space-efficient probabilistic datastructure to test if
38// an element is part of a set of elementsis defined as a string of bits
39// always initially empty.
40type BloomFilter struct {
41 data []byte // filter bits
42 salt []byte // salt for hashing
43}
44
45// NewBloomFilter cretes a new filter using the specified salt. An unused
46// salt is set to nil.
47func NewBloomFilter(salt []byte) *BloomFilter {
48 return &BloomFilter{
49 data: make([]byte, (bfNumBits+7)/8),
50 salt: salt,
51 }
52}
53
54// Add entry (binary representation):
55// When adding an element to the Bloom filter bf using BF-SET(bf,e), each
56// integer n of the mapping M(e) is interpreted as a bit offset n mod L
57// within bf and set to 1.
58func (bf *BloomFilter) Add(e []byte) {
59 for _, idx := range bf.indices(e) {
60 bf.data[idx/8] |= (1 << (idx % 7))
61 }
62}
63
64// Contains returns true if the entry is most likely to be included:
65// When testing if an element may be in the Bloom filter bf using
66// BF-TEST(bf,e), each bit offset n mod L within bf MUST have been set to 1.
67// Otherwise, the element is not considered to be in the Bloom filter.
68func (bf *BloomFilter) Contains(e []byte) bool {
69 for _, idx := range bf.indices(e) {
70 if bf.data[idx/8]&(1<<(idx%7)) == 0 {
71 return false
72 }
73 }
74 return true
75}
76
77// indices returns the list of bit indices for antry e:
78// The element e is prepended with a salt (pütional) and hashed using SHA-512.
79// The resulting byte string is interpreted as a list of 16 32-bit integers
80// in network byte order.
81func (bf *BloomFilter) indices(e []byte) []int {
82 // hash the entry (with optional salt prepended)
83 hsh := bfHash()
84 if bf.salt != nil {
85 hsh.Write(bf.salt)
86 }
87 hsh.Write(e)
88 h := hsh.Sum(nil)
89
90 // compute the indices for the entry
91 idx := make([]int, len(h)/2)
92 buf := bytes.NewReader(h)
93 for i := range idx {
94 binary.Read(buf, binary.BigEndian, &idx[i])
95 }
96 return idx
97}
98
99//======================================================================
100// BloomFilter for peer addresses
101//======================================================================
102
103// PeerBloomFilter implements specific Add/Contains functions.
104type PeerBloomFilter struct {
105 BloomFilter
106}
107
108// NewPeerBloomFilter creates a new filter for peer addresses.
109func NewPeerBloomFilter() *PeerBloomFilter {
110 return &PeerBloomFilter{
111 BloomFilter: *NewBloomFilter(nil),
112 }
113}
114
115// Add peer address to the filter.
116func (bf *PeerBloomFilter) Add(p *PeerAddress) {
117 bf.BloomFilter.Add(p.addr[:])
118}
119
120// Contains returns true if the peer address is most likely to be included.
121func (bf *PeerBloomFilter) Contains(p *PeerAddress) bool {
122 return bf.BloomFilter.Contains(p.addr[:])
123}
diff --git a/src/gnunet/service/dht/dhtstore_test.go b/src/gnunet/service/dht/dhtstore_test.go
new file mode 100644
index 0000000..3cb8080
--- /dev/null
+++ b/src/gnunet/service/dht/dhtstore_test.go
@@ -0,0 +1,89 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package dht
20
21import (
22 "encoding/hex"
23 "gnunet/crypto"
24 "gnunet/service"
25 blocks "gnunet/service/dht/blocks"
26 "math/rand"
27 "testing"
28)
29
30// test constants
31const (
32 fsNumBlocks = 5
33)
34
35// TestDHTFileStore generates 'fsNumBlocks' fully-random blocks
36// and stores them under their SHA512 key. It than retrieves
37// each block from storage and checks for matching hash.
38func TestDHTFilesStore(t *testing.T) {
39
40 // create file store
41 fs, err := service.NewFileCache("/var/lib/gnunet/dht/cache", "100")
42 if err != nil {
43 t.Fatal(err)
44 }
45 // allocate keys
46 keys := make([]blocks.Query, 0, fsNumBlocks)
47
48 // First round: save blocks
49 for i := 0; i < fsNumBlocks; i++ {
50 // generate random block
51 size := 20 // 1024 + rand.Intn(62000)
52 buf := make([]byte, size)
53 rand.Read(buf)
54 val := blocks.NewGenericBlock(buf)
55 // generate associated key
56 k := crypto.Hash(buf).Bits
57 key := blocks.NewGenericQuery(k)
58 t.Logf("> %d: %s -- %s", i, hex.EncodeToString(k), hex.EncodeToString(buf))
59
60 // store block
61 if err := fs.Put(key, val); err != nil {
62 t.Fatal(err)
63 }
64
65 // remember key
66 keys = append(keys, key)
67 }
68
69 // Second round: retrieve blocks and check
70 for i, key := range keys {
71 // get block
72 val, err := fs.Get(key)
73 if err != nil {
74 t.Fatal(err)
75 }
76 buf := val.Data()
77 t.Logf("< %d: %s -- %s", i, hex.EncodeToString(key.Key().Bits), hex.EncodeToString(buf))
78
79 // re-create key
80 k := crypto.Hash(buf)
81
82 // do the keys match?
83 if !k.Equals(key.Key()) {
84 t.Log(hex.EncodeToString(k.Bits))
85 t.Log(hex.EncodeToString(key.Key().Bits))
86 t.Fatal("key/value mismatch")
87 }
88 }
89}
diff --git a/src/gnunet/service/dht/module.go b/src/gnunet/service/dht/module.go
index 1588a06..d369f3f 100644
--- a/src/gnunet/service/dht/module.go
+++ b/src/gnunet/service/dht/module.go
@@ -19,9 +19,13 @@
19package dht 19package dht
20 20
21import ( 21import (
22 "context"
23 "gnunet/config"
24 "gnunet/core"
22 "gnunet/message" 25 "gnunet/message"
23 "gnunet/service" 26 "gnunet/service"
24 "gnunet/service/gns" 27 "gnunet/service/dht/blocks"
28 "net/http"
25) 29)
26 30
27//====================================================================== 31//======================================================================
@@ -32,16 +36,99 @@ import (
32// Put and get blocks into/from a DHT. 36// Put and get blocks into/from a DHT.
33//---------------------------------------------------------------------- 37//----------------------------------------------------------------------
34 38
35// Module handles the permanent storage of blocks under the query key. 39// Module handles the permanent storage of blocks under a query key.
36type Module struct { 40type Module struct {
41 service.ModuleImpl
42
43 store service.DHTStore // reference to the block storage mechanism
44 cache service.DHTStore // transient block cache
45 core *core.Core // reference to core services
46
47 rtable *RoutingTable // routing table
48}
49
50// NewModule returns a new module instance. It initializes the storage
51// mechanism for persistence.
52func NewModule(ctx context.Context, c *core.Core) (m *Module) {
53 // create permanent storage handler
54 store, err := service.NewDHTStore(config.Cfg.DHT.Storage)
55 if err != nil {
56 return nil
57 }
58 // create cache handler
59 cache, err := service.NewDHTStore(config.Cfg.DHT.Cache)
60 if err != nil {
61 return nil
62 }
63 // create routing table
64 rt := NewRoutingTable(NewPeerAddress(c.PeerID()))
65
66 // return module instance
67 m = &Module{
68 ModuleImpl: *service.NewModuleImpl(),
69 store: store,
70 cache: cache,
71 core: c,
72 rtable: rt,
73 }
74 // register as listener for core events
75 listener := m.Run(ctx, m.event, m.Filter())
76 c.Register("dht", listener)
77
78 return
37} 79}
38 80
39// Get a GNS block from the DHT 81//----------------------------------------------------------------------
40func (nc *Module) Get(ctx *service.SessionContext, query *gns.Query) (*message.Block, error) { 82
83// Get a block from the DHT
84func (nc *Module) Get(ctx context.Context, query blocks.Query) (block blocks.Block, err error) {
85
86 // check if we have the requested block in cache or permanent storage.
87 block, err = nc.cache.Get(query)
88 if err == nil {
89 // yes: we are done
90 return
91 }
92 block, err = nc.store.Get(query)
93 if err == nil {
94 // yes: we are done
95 return
96 }
97 // retrieve the block from the DHT
98
41 return nil, nil 99 return nil, nil
42} 100}
43 101
44// Put a GNS block into the DHT 102// Put a block into the DHT
45func (nc *Module) Put(ctx *service.SessionContext, block *message.Block) error { 103func (nc *Module) Put(ctx context.Context, key blocks.Query, block blocks.Block) error {
46 return nil 104 return nil
47} 105}
106
107//----------------------------------------------------------------------
108
109// Filter returns the event filter for the module
110func (m *Module) Filter() *core.EventFilter {
111 f := core.NewEventFilter()
112 f.AddEvent(core.EV_CONNECT)
113 f.AddEvent(core.EV_DISCONNECT)
114 f.AddMsgType(message.DHT_CLIENT_GET)
115 f.AddMsgType(message.DHT_CLIENT_GET_RESULTS_KNOWN)
116 f.AddMsgType(message.DHT_CLIENT_GET_STOP)
117 f.AddMsgType(message.DHT_CLIENT_PUT)
118 f.AddMsgType(message.DHT_CLIENT_RESULT)
119 return f
120}
121
122// Event handler
123func (nc *Module) event(ctx context.Context, ev *core.Event) {
124
125}
126
127//----------------------------------------------------------------------
128
129// RPC returns the route and handler function for a JSON-RPC request
130func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
131 return "/gns/", func(wrt http.ResponseWriter, req *http.Request) {
132 wrt.Write([]byte(`{"msg": "This is DHT" }`))
133 }
134}
diff --git a/src/gnunet/service/dht/routingtable.go b/src/gnunet/service/dht/routingtable.go
new file mode 100644
index 0000000..895a1b2
--- /dev/null
+++ b/src/gnunet/service/dht/routingtable.go
@@ -0,0 +1,305 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package dht
20
21import (
22 "bytes"
23 "crypto/sha512"
24 "encoding/hex"
25 "gnunet/util"
26 "math/rand"
27 "sync"
28
29 "github.com/bfix/gospel/math"
30)
31
32var (
33 // routing table hash function: defines number of
34 // buckets and size of peer addresses
35 rtHash = sha512.New
36)
37
38// Routing table contants (adjust with changing hash function)
39const (
40 numBuckets = 512 // number of bits of hash function result
41 numK = 20 // number of entries per k-bucket
42 sizeAddr = 64 // size of peer address in bytes
43)
44
45//======================================================================
46//======================================================================
47
48// PeerAddress is the identifier for a peer in the DHT network.
49// It is the SHA-512 hash of the PeerID (public Ed25519 key).
50type PeerAddress struct {
51 addr [sizeAddr]byte
52}
53
54// NewPeerAddress returns the DHT address of a peer.
55func NewPeerAddress(peer *util.PeerID) *PeerAddress {
56 r := new(PeerAddress)
57 h := rtHash()
58 h.Write(peer.Key)
59 copy(r.addr[:], h.Sum(nil))
60 return r
61}
62
63// String returns a human-readble representation of an address.
64func (addr *PeerAddress) String() string {
65 return hex.EncodeToString(addr.addr[:])
66}
67
68// Equals returns true if two peer addresses are the same.
69func (addr *PeerAddress) Equals(p *PeerAddress) bool {
70 return bytes.Equal(addr.addr[:], p.addr[:])
71}
72
73// Distance between two addresses: returns a distance value and a
74// bucket index (smaller index = less distant).
75func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) {
76 var d PeerAddress
77 for i := range d.addr {
78 d.addr[i] = addr.addr[i] ^ p.addr[i]
79 }
80 r := math.NewIntFromBytes(d.addr[:])
81 return r, numBuckets - r.BitLen()
82}
83
84//======================================================================
85// Routing table implementation
86//======================================================================
87
88// RoutingTable holds the (local) routing table for a node.
89// The index of of an address is the number of bits in the
90// distance to the reference address, so smaller index means
91// "nearer" to the reference address.
92type RoutingTable struct {
93 ref *PeerAddress // reference address for distance
94 buckets []*Bucket // list of buckets
95 list map[*PeerAddress]bool // keep list of peers
96 rwlock sync.RWMutex // lock for write operations
97 l2nse float64 // log2 of estimated network size
98}
99
100// NewRoutingTable creates a new routing table for the reference address.
101func NewRoutingTable(ref *PeerAddress) *RoutingTable {
102 rt := new(RoutingTable)
103 rt.ref = ref
104 rt.list = make(map[*PeerAddress]bool)
105 rt.buckets = make([]*Bucket, numBuckets)
106 for i := range rt.buckets {
107 rt.buckets[i] = NewBucket(numK)
108 }
109 return rt
110}
111
112// Add new peer address to routing table.
113// Returns true if the entry was added, false otherwise.
114func (rt *RoutingTable) Add(p *PeerAddress, connected bool) bool {
115 // ensure one write and no readers
116 rt.rwlock.Lock()
117 defer rt.rwlock.Unlock()
118
119 // compute distance (bucket index) and insert address.
120 _, idx := p.Distance(rt.ref)
121 if rt.buckets[idx].Add(p, connected) {
122 rt.list[p] = true
123 return true
124 }
125 // Full bucket: we did not add the address to the routing table.
126 return false
127}
128
129// Remove peer address from routing table.
130// Returns true if the entry was removed, false otherwise.
131func (rt *RoutingTable) Remove(p *PeerAddress) bool {
132 // ensure one write and no readers
133 rt.rwlock.Lock()
134 defer rt.rwlock.Unlock()
135
136 // compute distance (bucket index) and remove entry from bucket
137 _, idx := p.Distance(rt.ref)
138 if rt.buckets[idx].Remove(p) {
139 delete(rt.list, p)
140 return true
141 }
142 return false
143}
144
145//----------------------------------------------------------------------
146// routing functions
147//----------------------------------------------------------------------
148
149// SelectClosestPeer for a given peer address and bloomfilter.
150func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n *PeerAddress) {
151 // no writer allowed
152 rt.rwlock.RLock()
153 defer rt.rwlock.RUnlock()
154
155 // find closest address
156 var dist *math.Int
157 for _, b := range rt.buckets {
158 if k, d := b.SelectClosestPeer(p, bf); n == nil || (d != nil && d.Cmp(dist) < 0) {
159 dist = d
160 n = k
161 }
162 }
163 return
164}
165
166// SelectRandomPeer returns a random address from table (that is not
167// included in the bloomfilter)
168func (rt *RoutingTable) SelectRandomPeer(bf *PeerBloomFilter) *PeerAddress {
169 // no writer allowed
170 rt.rwlock.RLock()
171 defer rt.rwlock.RUnlock()
172
173 // select random entry from list
174 if size := len(rt.list); size > 0 {
175 idx := rand.Intn(size)
176 for k := range rt.list {
177 if idx == 0 {
178 return k
179 }
180 idx--
181 }
182 }
183 return nil
184}
185
186// SelectPeer selects a neighbor depending on the number of hops parameter.
187// If hops < NSE this function MUST return SelectRandomPeer() and
188// SelectClosestpeer() otherwise.
189func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf *PeerBloomFilter) *PeerAddress {
190 if float64(hops) < rt.l2nse {
191 return rt.SelectRandomPeer(bf)
192 }
193 return rt.SelectClosestPeer(p, bf)
194}
195
196// IsClosestPeer returns true if p is the closest peer for k. Peers with a
197// positive test in the Bloom filter are not considered.
198func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf *PeerBloomFilter) bool {
199 n := rt.SelectClosestPeer(k, bf)
200 return n.Equals(p)
201}
202
203// ComputeOutDegree computes the number of neighbors that a message should be forwarded to.
204// The arguments are the desired replication level, the hop count of the message so far,
205// and the base-2 logarithm of the current network size estimate (L2NSE) as provided by the
206// underlay. The result is the non-negative number of next hops to select.
207func (rt *RoutingTable) ComputeOutDegree(repl, hop int) int {
208 hf := float64(hop)
209 if hf > 4*rt.l2nse {
210 return 0
211 }
212 if hf > 2*rt.l2nse {
213 return 1
214 }
215 if repl == 0 {
216 repl = 1
217 } else if repl > 16 {
218 repl = 16
219 }
220 rm1 := float64(repl - 1)
221 return 1 + int(rm1/(rt.l2nse+rm1*hf))
222}
223
224//======================================================================
225// Routing table buckets
226//======================================================================
227
228// PeerEntry in a k-Bucket: use routing specific attributes
229// for book-keeping
230type PeerEntry struct {
231 addr *PeerAddress // peer address
232 connected bool // is peer connected?
233}
234
235// Bucket holds peer entries with approx. same distance from node
236type Bucket struct {
237 list []*PeerEntry
238 rwlock sync.RWMutex
239}
240
241// NewBucket creates a new entry list of given size
242func NewBucket(n int) *Bucket {
243 return &Bucket{
244 list: make([]*PeerEntry, 0, n),
245 }
246}
247
248// Add peer address to the bucket if there is free space.
249// Returns true if entry is added, false otherwise.
250func (b *Bucket) Add(p *PeerAddress, connected bool) bool {
251 // only one writer and no readers
252 b.rwlock.Lock()
253 defer b.rwlock.Unlock()
254
255 // check for free space in bucket
256 if len(b.list) < numK {
257 // append entry at the end
258 pe := &PeerEntry{
259 addr: p,
260 connected: connected,
261 }
262 b.list = append(b.list, pe)
263 return true
264 }
265 return false
266}
267
268// Remove peer address from the bucket.
269// Returns true if entry is removed (found), false otherwise.
270func (b *Bucket) Remove(p *PeerAddress) bool {
271 // only one writer and no readers
272 b.rwlock.Lock()
273 defer b.rwlock.Unlock()
274
275 for i, pe := range b.list {
276 if pe.addr.Equals(p) {
277 // found entry: remove it
278 b.list = append(b.list[:i], b.list[i+1:]...)
279 return true
280 }
281 }
282 return false
283}
284
285// SelectClosestPeer returns the entry with minimal distance to the given
286// peer address; entries included in the bloom flter are ignored.
287func (b *Bucket) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n *PeerAddress, dist *math.Int) {
288 // no writer allowed
289 b.rwlock.RLock()
290 defer b.rwlock.RUnlock()
291
292 for _, pe := range b.list {
293 // skip addresses in bloomfilter
294 if bf.Contains(pe.addr) {
295 continue
296 }
297 // check for shorter distance
298 if d, _ := p.Distance(pe.addr); n == nil || d.Cmp(dist) < 0 {
299 // remember best match
300 dist = d
301 n = pe.addr
302 }
303 }
304 return
305}
diff --git a/src/gnunet/service/dht/routingtable_test.go b/src/gnunet/service/dht/routingtable_test.go
new file mode 100644
index 0000000..2579356
--- /dev/null
+++ b/src/gnunet/service/dht/routingtable_test.go
@@ -0,0 +1,140 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package dht
20
21import (
22 "gnunet/config"
23 "gnunet/core"
24 "gnunet/util"
25 "math/rand"
26 "testing"
27)
28
29const (
30 NUMP = 1000 // Total number of peers
31 EPOCHS = 10000 // number of epochs to run
32)
33
34type Entry struct {
35 addr *PeerAddress // address of peer
36 ttl int64 // time to live (in epochs)
37 born int64 // epoch of birth
38 last int64 // last action
39 drop int64 // drop (in epochs)
40 revive int64 // revive dropped (in epochs)
41 online bool // peer connected?
42}
43
44// test data
45var (
46 cfg = &config.NodeConfig{
47 PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
48 Endpoints: []string{
49 "r5n+ip+udp://127.0.0.1:6666",
50 },
51 }
52)
53
54// TestRT connects and disconnects random peers to test the base
55// functionality of the routing table algorithms.
56func TestRT(t *testing.T) {
57 // start deterministic randomizer
58 rand.Seed(19031962)
59
60 // helper functions
61 genRemotePeer := func() *PeerAddress {
62 d := make([]byte, 32)
63 if _, err := rand.Read(d); err != nil {
64 panic(err)
65 }
66 return NewPeerAddress(util.NewPeerID(d))
67 }
68
69 // create routing table and start command handler
70 local, err := core.NewLocalPeer(cfg)
71 if err != nil {
72 t.Fatal(err)
73 }
74 rt := NewRoutingTable(NewPeerAddress(local.GetID()))
75
76 // create a task list
77 tasks := make([]*Entry, NUMP)
78 for i := range tasks {
79 tasks[i] = new(Entry)
80 tasks[i].addr = genRemotePeer()
81 tasks[i].born = rand.Int63n(EPOCHS)
82 tasks[i].ttl = 1000 + rand.Int63n(7000)
83 tasks[i].drop = 2000 + rand.Int63n(3000)
84 tasks[i].revive = rand.Int63n(2000)
85 tasks[i].online = false
86 }
87
88 // actions:
89 connected := func(task *Entry, e int64, msg string) {
90 rt.Add(task.addr, true)
91 task.online = true
92 task.last = e
93 t.Logf("[%6d] %s %s\n", e, task.addr, msg)
94 }
95 disconnected := func(task *Entry, e int64, msg string) {
96 rt.Remove(task.addr)
97 task.online = false
98 task.last = e
99 t.Logf("[%6d] %s %s\n", e, task.addr, msg)
100 }
101
102 // run epochs
103 var e int64
104 for e = 0; e < EPOCHS; e++ {
105 for _, task := range tasks {
106 // birth
107 if task.born == e {
108 connected(task, e, "connected")
109 continue
110 }
111 // death
112 if task.born+task.ttl == e {
113 disconnected(task, e, "disconnected")
114 continue
115 }
116 if task.online {
117 // drop out
118 if task.last+task.drop == e {
119 disconnected(task, e, "dropped out")
120 continue
121 }
122 } else {
123 // drop in
124 if task.last+task.drop == e {
125 connected(task, e, "dropped in")
126 continue
127 }
128 }
129 }
130 }
131
132 // execute some routing functions on remaining table
133 k := genRemotePeer()
134 bf := NewPeerBloomFilter()
135 n := rt.SelectClosestPeer(k, bf)
136 t.Logf("Closest: %s -> %s\n", k, n)
137
138 n = rt.SelectRandomPeer(bf)
139 t.Logf("Random: %s\n", n)
140}
diff --git a/src/gnunet/service/dht/service.go b/src/gnunet/service/dht/service.go
new file mode 100644
index 0000000..2a189bb
--- /dev/null
+++ b/src/gnunet/service/dht/service.go
@@ -0,0 +1,134 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package dht
20
21import (
22 "context"
23 "fmt"
24 "io"
25
26 "gnunet/core"
27 "gnunet/message"
28 "gnunet/service"
29
30 "github.com/bfix/gospel/logger"
31)
32
33// Error codes
34var (
35 ErrInvalidID = fmt.Errorf("invalid/unassociated ID")
36 ErrBlockExpired = fmt.Errorf("block expired")
37 ErrInvalidResponseType = fmt.Errorf("invald response type")
38)
39
40//----------------------------------------------------------------------
41// "GNUnet R5N DHT" service implementation
42//----------------------------------------------------------------------
43
44// Service implements a DHT service
45type Service struct {
46 Module
47}
48
49// NewService creates a new DHT service instance
50func NewService(ctx context.Context, c *core.Core) service.Service {
51 return &Service{
52 Module: *NewModule(ctx, c),
53 }
54}
55
56// ServeClient processes a client channel.
57func (s *Service) ServeClient(ctx context.Context, id int, mc *service.Connection) {
58 reqID := 0
59 var cancel context.CancelFunc
60 ctx, cancel = context.WithCancel(ctx)
61
62loop:
63 for {
64 // receive next message from client
65 reqID++
66 logger.Printf(logger.DBG, "[dht:%d:%d] Waiting for client request...\n", id, reqID)
67 msg, err := mc.Receive(ctx)
68 if err != nil {
69 if err == io.EOF {
70 logger.Printf(logger.INFO, "[dht:%d:%d] Client channel closed.\n", id, reqID)
71 } else if err == service.ErrConnectionInterrupted {
72 logger.Printf(logger.INFO, "[dht:%d:%d] Service operation interrupted.\n", id, reqID)
73 } else {
74 logger.Printf(logger.ERROR, "[dht:%d:%d] Message-receive failed: %s\n", id, reqID, err.Error())
75 }
76 break loop
77 }
78 logger.Printf(logger.INFO, "[dht:%d:%d] Received request: %v\n", id, reqID, msg)
79
80 // handle message
81 s.HandleMessage(context.WithValue(ctx, "label", fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
82 }
83 // close client connection
84 mc.Close()
85
86 // cancel all tasks running for this session/connection
87 logger.Printf(logger.INFO, "[dht:%d] Start closing session...\n", id)
88 cancel()
89}
90
91// HandleMessage handles a DHT request/response message. If the transport channel
92// is nil, responses are send directly via the transport layer.
93func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back service.Responder) bool {
94 // assemble log label
95 label := ""
96 if v := ctx.Value("label"); v != nil {
97 label = v.(string)
98 }
99 // process message
100 switch msg.(type) {
101 case *message.DHTClientPutMsg:
102 //----------------------------------------------------------
103 // DHT PUT
104 //----------------------------------------------------------
105
106 case *message.DHTClientGetMsg:
107 //----------------------------------------------------------
108 // DHT GET
109 //----------------------------------------------------------
110
111 case *message.DHTClientGetResultsKnownMsg:
112 //----------------------------------------------------------
113 // DHT GET-RESULTS-KNOWN
114 //----------------------------------------------------------
115
116 case *message.DHTClientGetStopMsg:
117 //----------------------------------------------------------
118 // DHT GET-STOP
119 //----------------------------------------------------------
120
121 case *message.DHTClientResultMsg:
122 //----------------------------------------------------------
123 // DHT RESULT
124 //----------------------------------------------------------
125
126 default:
127 //----------------------------------------------------------
128 // UNKNOWN message type received
129 //----------------------------------------------------------
130 logger.Printf(logger.ERROR, "[dht%s] Unhandled message of type (%d)\n", label, msg.Header().MsgType)
131 return false
132 }
133 return true
134}
diff --git a/src/gnunet/service/gns/block_handler.go b/src/gnunet/service/gns/block_handler.go
index b05c59e..c93fca1 100644
--- a/src/gnunet/service/gns/block_handler.go
+++ b/src/gnunet/service/gns/block_handler.go
@@ -76,7 +76,7 @@ type BlockHandler interface {
76 // resource records in the same block. 'cm' maps the resource type 76 // resource records in the same block. 'cm' maps the resource type
77 // to an integer count (how many records of a type are present in the 77 // to an integer count (how many records of a type are present in the
78 // GNS block). 78 // GNS block).
79 Coexist(cm util.CounterMap) bool 79 Coexist(cm util.Counter[int]) bool
80 80
81 // Records returns a list of RR of the given types associated with 81 // Records returns a list of RR of the given types associated with
82 // the custom handler 82 // the custom handler
@@ -103,7 +103,7 @@ type BlockHandler interface {
103// BlockHandlerList is a list of block handlers instantiated. 103// BlockHandlerList is a list of block handlers instantiated.
104type BlockHandlerList struct { 104type BlockHandlerList struct {
105 list map[int]BlockHandler // list of handler instances 105 list map[int]BlockHandler // list of handler instances
106 counts util.CounterMap // count number of RRs by type 106 counts util.Counter[int] // count number of RRs by type
107} 107}
108 108
109// NewBlockHandlerList instantiates an a list of active block handlers 109// NewBlockHandlerList instantiates an a list of active block handlers
@@ -112,7 +112,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord, labels []string) (*B
112 // initialize block handler list 112 // initialize block handler list
113 hl := &BlockHandlerList{ 113 hl := &BlockHandlerList{
114 list: make(map[int]BlockHandler), 114 list: make(map[int]BlockHandler),
115 counts: make(util.CounterMap), 115 counts: make(util.Counter[int]),
116 } 116 }
117 117
118 // first pass: build list of shadow records in this block 118 // first pass: build list of shadow records in this block
@@ -260,7 +260,7 @@ func (h *ZoneKeyHandler) AddRecord(rec *message.ResourceRecord, labels []string)
260 260
261// Coexist return a flag indicating how a resource record of a given type 261// Coexist return a flag indicating how a resource record of a given type
262// is to be treated (see BlockHandler interface) 262// is to be treated (see BlockHandler interface)
263func (h *ZoneKeyHandler) Coexist(cm util.CounterMap) bool { 263func (h *ZoneKeyHandler) Coexist(cm util.Counter[int]) bool {
264 // only one type (GNS_TYPE_PKEY) is present 264 // only one type (GNS_TYPE_PKEY) is present
265 return len(cm) == 1 && cm.Num(enums.GNS_TYPE_PKEY) == 1 265 return len(cm) == 1 && cm.Num(enums.GNS_TYPE_PKEY) == 1
266} 266}
@@ -335,7 +335,7 @@ func (h *Gns2DnsHandler) AddRecord(rec *message.ResourceRecord, labels []string)
335 335
336// Coexist return a flag indicating how a resource record of a given type 336// Coexist return a flag indicating how a resource record of a given type
337// is to be treated (see BlockHandler interface) 337// is to be treated (see BlockHandler interface)
338func (h *Gns2DnsHandler) Coexist(cm util.CounterMap) bool { 338func (h *Gns2DnsHandler) Coexist(cm util.Counter[int]) bool {
339 // only one type (GNS_TYPE_GNS2DNS) is present 339 // only one type (GNS_TYPE_GNS2DNS) is present
340 return len(cm) == 1 && cm.Num(enums.GNS_TYPE_GNS2DNS) > 0 340 return len(cm) == 1 && cm.Num(enums.GNS_TYPE_GNS2DNS) > 0
341} 341}
@@ -405,7 +405,7 @@ func (h *BoxHandler) AddRecord(rec *message.ResourceRecord, labels []string) err
405 405
406// Coexist return a flag indicating how a resource record of a given type 406// Coexist return a flag indicating how a resource record of a given type
407// is to be treated (see BlockHandler interface) 407// is to be treated (see BlockHandler interface)
408func (h *BoxHandler) Coexist(cm util.CounterMap) bool { 408func (h *BoxHandler) Coexist(cm util.Counter[int]) bool {
409 // anything goes... 409 // anything goes...
410 return true 410 return true
411} 411}
@@ -469,7 +469,7 @@ func (h *LehoHandler) AddRecord(rec *message.ResourceRecord, labels []string) er
469 469
470// Coexist return a flag indicating how a resource record of a given type 470// Coexist return a flag indicating how a resource record of a given type
471// is to be treated (see BlockHandler interface) 471// is to be treated (see BlockHandler interface)
472func (h *LehoHandler) Coexist(cm util.CounterMap) bool { 472func (h *LehoHandler) Coexist(cm util.Counter[int]) bool {
473 // requires exactly one LEHO and any number of other records. 473 // requires exactly one LEHO and any number of other records.
474 return cm.Num(enums.GNS_TYPE_LEHO) == 1 474 return cm.Num(enums.GNS_TYPE_LEHO) == 1
475} 475}
@@ -527,7 +527,7 @@ func (h *CnameHandler) AddRecord(rec *message.ResourceRecord, labels []string) e
527 527
528// Coexist return a flag indicating how a resource record of a given type 528// Coexist return a flag indicating how a resource record of a given type
529// is to be treated (see BlockHandler interface) 529// is to be treated (see BlockHandler interface)
530func (h *CnameHandler) Coexist(cm util.CounterMap) bool { 530func (h *CnameHandler) Coexist(cm util.Counter[int]) bool {
531 // only a single CNAME allowed 531 // only a single CNAME allowed
532 return len(cm) == 1 && cm.Num(enums.GNS_TYPE_DNS_CNAME) == 1 532 return len(cm) == 1 && cm.Num(enums.GNS_TYPE_DNS_CNAME) == 1
533} 533}
@@ -581,7 +581,7 @@ func (h *VpnHandler) AddRecord(rec *message.ResourceRecord, labels []string) err
581 581
582// Coexist return a flag indicating how a resource record of a given type 582// Coexist return a flag indicating how a resource record of a given type
583// is to be treated (see BlockHandler interface) 583// is to be treated (see BlockHandler interface)
584func (h *VpnHandler) Coexist(cm util.CounterMap) bool { 584func (h *VpnHandler) Coexist(cm util.Counter[int]) bool {
585 // anything goes 585 // anything goes
586 return true 586 return true
587} 587}
diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go
index 3bcc35e..8818ca6 100644
--- a/src/gnunet/service/gns/dns.go
+++ b/src/gnunet/service/gns/dns.go
@@ -19,6 +19,7 @@
19package gns 19package gns
20 20
21import ( 21import (
22 "context"
22 "fmt" 23 "fmt"
23 "net" 24 "net"
24 "strings" 25 "strings"
@@ -27,7 +28,6 @@ import (
27 "gnunet/crypto" 28 "gnunet/crypto"
28 "gnunet/enums" 29 "gnunet/enums"
29 "gnunet/message" 30 "gnunet/message"
30 "gnunet/service"
31 "gnunet/util" 31 "gnunet/util"
32 32
33 "github.com/bfix/gospel/logger" 33 "github.com/bfix/gospel/logger"
@@ -205,7 +205,7 @@ func QueryDNS(id int, name string, server net.IP, kind RRTypeList) *message.Reco
205// parallel; the first result delivered by any of the servers is returned 205// parallel; the first result delivered by any of the servers is returned
206// as the result list of matching resource records. 206// as the result list of matching resource records.
207func (gns *Module) ResolveDNS( 207func (gns *Module) ResolveDNS(
208 ctx *service.SessionContext, 208 ctx context.Context,
209 name string, 209 name string,
210 servers []string, 210 servers []string,
211 kind RRTypeList, 211 kind RRTypeList,
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
index 5ff81f2..31ad6c7 100644
--- a/src/gnunet/service/gns/module.go
+++ b/src/gnunet/service/gns/module.go
@@ -19,18 +19,22 @@
19package gns 19package gns
20 20
21import ( 21import (
22 "context"
22 "fmt" 23 "fmt"
23 "net/http" 24 "net/http"
24 "strings" 25 "strings"
25 26
26 "gnunet/config" 27 "gnunet/config"
28 "gnunet/core"
27 "gnunet/crypto" 29 "gnunet/crypto"
28 "gnunet/enums" 30 "gnunet/enums"
29 "gnunet/message" 31 "gnunet/message"
30 "gnunet/service" 32 "gnunet/service"
33 "gnunet/service/dht/blocks"
31 "gnunet/service/revocation" 34 "gnunet/service/revocation"
32 "gnunet/util" 35 "gnunet/util"
33 36
37 "github.com/bfix/gospel/data"
34 "github.com/bfix/gospel/logger" 38 "github.com/bfix/gospel/logger"
35) 39)
36 40
@@ -45,34 +49,6 @@ var (
45) 49)
46 50
47//---------------------------------------------------------------------- 51//----------------------------------------------------------------------
48// Query for simple GNS lookups
49//----------------------------------------------------------------------
50
51// Query specifies the context for a basic GNS name lookup of an (atomic)
52// label in a given zone identified by its public key.
53type Query struct {
54 Zone *crypto.ZoneKey // Public zone key
55 Label string // Atomic label
56 Derived *crypto.ZoneKey // Derived key from (pkey,label)
57 Key *crypto.HashCode // Key for repository queries (local/remote)
58}
59
60// NewQuery assembles a new Query object for the given zone and label.
61func NewQuery(zkey *crypto.ZoneKey, label string) *Query {
62 // derive a public key from (pkey,label) and set the repository
63 // key as the SHA512 hash of the binary key representation.
64 // (key blinding)
65 pd, _ := zkey.Derive(label, "gns")
66 key := crypto.Hash(pd.Bytes())
67 return &Query{
68 Zone: zkey,
69 Label: label,
70 Derived: pd,
71 Key: key,
72 }
73}
74
75//----------------------------------------------------------------------
76// The GNS module (recursively) resolves GNS names: 52// The GNS module (recursively) resolves GNS names:
77// Resolves DNS-like names (e.g. "minecraft.servers.bob.games"; a name is 53// Resolves DNS-like names (e.g. "minecraft.servers.bob.games"; a name is
78// a list of labels with '.' as separator) to the requested resource 54// a list of labels with '.' as separator) to the requested resource
@@ -112,25 +88,50 @@ func NewQuery(zkey *crypto.ZoneKey, label string) *Query {
112 88
113// Module handles the resolution of GNS names to RRs bundled in a block. 89// Module handles the resolution of GNS names to RRs bundled in a block.
114type Module struct { 90type Module struct {
91 service.ModuleImpl
92
115 // Use function references for calls to methods in other modules: 93 // Use function references for calls to methods in other modules:
116 LookupLocal func(ctx *service.SessionContext, query *Query) (*message.Block, error) 94 LookupLocal func(ctx context.Context, query *blocks.GNSQuery) (*blocks.GNSBlock, error)
117 StoreLocal func(ctx *service.SessionContext, block *message.Block) error 95 StoreLocal func(ctx context.Context, query *blocks.GNSQuery, block *blocks.GNSBlock) error
118 LookupRemote func(ctx *service.SessionContext, query *Query) (*message.Block, error) 96 LookupRemote func(ctx context.Context, query blocks.Query) (blocks.Block, error)
119 RevocationQuery func(ctx *service.SessionContext, zkey *crypto.ZoneKey) (valid bool, err error) 97 RevocationQuery func(ctx context.Context, zkey *crypto.ZoneKey) (valid bool, err error)
120 RevocationRevoke func(ctx *service.SessionContext, rd *revocation.RevData) (success bool, err error) 98 RevocationRevoke func(ctx context.Context, rd *revocation.RevData) (success bool, err error)
121} 99}
122 100
123// RPC returns the route and handler function for a JSON-RPC request 101func NewModule(ctx context.Context, c *core.Core) (m *Module) {
124func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) { 102 m = &Module{
125 return "/gns/", func(wrt http.ResponseWriter, req *http.Request) { 103 ModuleImpl: *service.NewModuleImpl(),
126 wrt.Write([]byte(`{"msg": "This is GNS" }`))
127 } 104 }
105 // register as listener for core events
106 listener := m.Run(ctx, m.event, m.Filter())
107 c.Register("gns", listener)
108
109 return
110}
111
112//----------------------------------------------------------------------
113
114// Filter returns the event filter for the service
115func (m *Module) Filter() *core.EventFilter {
116 f := core.NewEventFilter()
117 f.AddMsgType(message.GNS_LOOKUP)
118 f.AddMsgType(message.GNS_LOOKUP_RESULT)
119 f.AddMsgType(message.GNS_REVERSE_LOOKUP)
120 f.AddMsgType(message.GNS_REVERSE_LOOKUP_RESULT)
121 return f
128} 122}
129 123
124// Event handler
125func (m *Module) event(ctx context.Context, ev *core.Event) {
126
127}
128
129//----------------------------------------------------------------------
130
130// Resolve a GNS name with multiple labels. If pkey is not nil, the name 131// Resolve a GNS name with multiple labels. If pkey is not nil, the name
131// is interpreted as "relative to current zone". 132// is interpreted as "relative to current zone".
132func (m *Module) Resolve( 133func (m *Module) Resolve(
133 ctx *service.SessionContext, 134 ctx context.Context,
134 path string, 135 path string,
135 zkey *crypto.ZoneKey, 136 zkey *crypto.ZoneKey,
136 kind RRTypeList, 137 kind RRTypeList,
@@ -142,7 +143,7 @@ func (m *Module) Resolve(
142 return nil, ErrGNSRecursionExceeded 143 return nil, ErrGNSRecursionExceeded
143 } 144 }
144 // get the labels in reverse order 145 // get the labels in reverse order
145 names := util.ReverseStringList(strings.Split(path, ".")) 146 names := util.Reverse(strings.Split(path, "."))
146 logger.Printf(logger.DBG, "[gns] Resolver called for %v\n", names) 147 logger.Printf(logger.DBG, "[gns] Resolver called for %v\n", names)
147 148
148 // check for relative path 149 // check for relative path
@@ -157,7 +158,7 @@ func (m *Module) Resolve(
157// ResolveAbsolute resolves a fully qualified GNS absolute name 158// ResolveAbsolute resolves a fully qualified GNS absolute name
158// (with multiple labels). 159// (with multiple labels).
159func (m *Module) ResolveAbsolute( 160func (m *Module) ResolveAbsolute(
160 ctx *service.SessionContext, 161 ctx context.Context,
161 labels []string, 162 labels []string,
162 kind RRTypeList, 163 kind RRTypeList,
163 mode int, 164 mode int,
@@ -184,7 +185,7 @@ func (m *Module) ResolveAbsolute(
184// processing simple (PKEY,Label) lookups in sequence and handle intermediate 185// processing simple (PKEY,Label) lookups in sequence and handle intermediate
185// GNS record types 186// GNS record types
186func (m *Module) ResolveRelative( 187func (m *Module) ResolveRelative(
187 ctx *service.SessionContext, 188 ctx context.Context,
188 labels []string, 189 labels []string,
189 zkey *crypto.ZoneKey, 190 zkey *crypto.ZoneKey,
190 kind RRTypeList, 191 kind RRTypeList,
@@ -200,7 +201,7 @@ func (m *Module) ResolveRelative(
200 logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in '%s'\n", labels[0], util.EncodeBinaryToString(zkey.Bytes())) 201 logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in '%s'\n", labels[0], util.EncodeBinaryToString(zkey.Bytes()))
201 202
202 // resolve next level 203 // resolve next level
203 var block *message.Block 204 var block *blocks.GNSBlock
204 if block, err = m.Lookup(ctx, zkey, labels[0], mode); err != nil { 205 if block, err = m.Lookup(ctx, zkey, labels[0], mode); err != nil {
205 // failed to resolve name 206 // failed to resolve name
206 return 207 return
@@ -221,7 +222,7 @@ func (m *Module) ResolveRelative(
221 } 222 }
222 // post-process block by inspecting contained resource records for 223 // post-process block by inspecting contained resource records for
223 // special GNS types 224 // special GNS types
224 if records, err = block.Records(); err != nil { 225 if records, err = m.records(block.Body.Data); err != nil {
225 return 226 return
226 } 227 }
227 // assemble a list of block handlers for this block: if multiple 228 // assemble a list of block handlers for this block: if multiple
@@ -238,10 +239,7 @@ func (m *Module) ResolveRelative(
238 // handle special block cases in priority order: 239 // handle special block cases in priority order:
239 //-------------------------------------------------------------- 240 //--------------------------------------------------------------
240 241
241 if hdlr := hdlrs.GetHandler( 242 if hdlr := hdlrs.GetHandler(crypto.ZoneTypes...); hdlr != nil {
242 enums.GNS_TYPE_PKEY,
243 enums.GNS_TYPE_EDKEY,
244 ); hdlr != nil {
245 // (1) zone key record: 243 // (1) zone key record:
246 inst := hdlr.(*ZoneKeyHandler) 244 inst := hdlr.(*ZoneKeyHandler)
247 // if labels are pending, set new zone and continue resolution; 245 // if labels are pending, set new zone and continue resolution;
@@ -267,7 +265,7 @@ func (m *Module) ResolveRelative(
267 } 265 }
268 // ... otherwise we need to handle delegation to DNS: returns a 266 // ... otherwise we need to handle delegation to DNS: returns a
269 // list of found resource records in DNS (filter by 'kind') 267 // list of found resource records in DNS (filter by 'kind')
270 lbls := strings.Join(util.ReverseStringList(labels[1:]), ".") 268 lbls := strings.Join(util.Reverse(labels[1:]), ".")
271 if len(lbls) > 0 { 269 if len(lbls) > 0 {
272 lbls += "." 270 lbls += "."
273 } 271 }
@@ -361,7 +359,7 @@ func (m *Module) ResolveRelative(
361// a PKEY TLD), it is also resolved with GNS. All other names are resolved 359// a PKEY TLD), it is also resolved with GNS. All other names are resolved
362// via DNS queries. 360// via DNS queries.
363func (m *Module) ResolveUnknown( 361func (m *Module) ResolveUnknown(
364 ctx *service.SessionContext, 362 ctx context.Context,
365 name string, 363 name string,
366 labels []string, 364 labels []string,
367 zkey *crypto.ZoneKey, 365 zkey *crypto.ZoneKey,
@@ -372,7 +370,7 @@ func (m *Module) ResolveUnknown(
372 if strings.HasSuffix(name, ".+") { 370 if strings.HasSuffix(name, ".+") {
373 // resolve server name relative to current zone 371 // resolve server name relative to current zone
374 name = strings.TrimSuffix(name, ".+") 372 name = strings.TrimSuffix(name, ".+")
375 for _, label := range util.ReverseStringList(labels) { 373 for _, label := range util.Reverse(labels) {
376 name += "." + label 374 name += "." + label
377 } 375 }
378 if set, err = m.Resolve(ctx, name, zkey, kind, enums.GNS_LO_DEFAULT, depth+1); err != nil { 376 if set, err = m.Resolve(ctx, name, zkey, kind, enums.GNS_LO_DEFAULT, depth+1); err != nil {
@@ -397,7 +395,7 @@ func (m *Module) ResolveUnknown(
397 395
398// GetZoneKey returns the zone key (or nil) from an absolute GNS path. 396// GetZoneKey returns the zone key (or nil) from an absolute GNS path.
399func (m *Module) GetZoneKey(path string) *crypto.ZoneKey { 397func (m *Module) GetZoneKey(path string) *crypto.ZoneKey {
400 labels := util.ReverseStringList(strings.Split(path, ".")) 398 labels := util.Reverse(strings.Split(path, "."))
401 if len(labels[0]) == 52 { 399 if len(labels[0]) == 52 {
402 if data, err := util.DecodeStringToBinary(labels[0], 32); err == nil { 400 if data, err := util.DecodeStringToBinary(labels[0], 32); err == nil {
403 if zkey, err := crypto.NewZoneKey(data); err == nil { 401 if zkey, err := crypto.NewZoneKey(data); err == nil {
@@ -410,13 +408,13 @@ func (m *Module) GetZoneKey(path string) *crypto.ZoneKey {
410 408
411// Lookup name in GNS. 409// Lookup name in GNS.
412func (m *Module) Lookup( 410func (m *Module) Lookup(
413 ctx *service.SessionContext, 411 ctx context.Context,
414 zkey *crypto.ZoneKey, 412 zkey *crypto.ZoneKey,
415 label string, 413 label string,
416 mode int) (block *message.Block, err error) { 414 mode int) (block *blocks.GNSBlock, err error) {
417 415
418 // create query (lookup key) 416 // create query (lookup key)
419 query := NewQuery(zkey, label) 417 query := blocks.NewGNSQuery(zkey, label)
420 418
421 // try local lookup first 419 // try local lookup first
422 if block, err = m.LookupLocal(ctx, query); err != nil { 420 if block, err = m.LookupLocal(ctx, query); err != nil {
@@ -427,7 +425,8 @@ func (m *Module) Lookup(
427 if block == nil { 425 if block == nil {
428 if mode == enums.GNS_LO_DEFAULT { 426 if mode == enums.GNS_LO_DEFAULT {
429 // get the block from a remote lookup 427 // get the block from a remote lookup
430 if block, err = m.LookupRemote(ctx, query); err != nil || block == nil { 428 var blk blocks.Block
429 if blk, err = m.LookupRemote(ctx, query); err != nil || blk == nil {
431 if err != nil { 430 if err != nil {
432 logger.Printf(logger.ERROR, "[gns] remote Lookup failed: %s\n", err.Error()) 431 logger.Printf(logger.ERROR, "[gns] remote Lookup failed: %s\n", err.Error())
433 block = nil 432 block = nil
@@ -437,8 +436,13 @@ func (m *Module) Lookup(
437 // lookup fails completely -- no result 436 // lookup fails completely -- no result
438 return 437 return
439 } 438 }
439 // convert to GNSBlock
440 if err = blocks.Unwrap(blk, &block); err != nil {
441 logger.Println(logger.DBG, "[gns] remote Lookup: GNS unwrap failed")
442 return
443 }
440 // store RRs from remote locally. 444 // store RRs from remote locally.
441 m.StoreLocal(ctx, block) 445 m.StoreLocal(ctx, query, block)
442 } 446 }
443 } 447 }
444 return 448 return
@@ -456,3 +460,22 @@ func (m *Module) newLEHORecord(name string, expires util.AbsoluteTime) *message.
456 rr.Data[len(name)] = 0 460 rr.Data[len(name)] = 0
457 return rr 461 return rr
458} 462}
463
464// Records returns the list of resource records from binary data.
465func (m *Module) records(buf []byte) ([]*message.ResourceRecord, error) {
466 // parse data into record set
467 rs := message.NewRecordSet()
468 if err := data.Unmarshal(rs, buf); err != nil {
469 return nil, err
470 }
471 return rs.Records, nil
472}
473
474//----------------------------------------------------------------------
475
476// RPC returns the route and handler function for a JSON-RPC request
477func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
478 return "/gns/", func(wrt http.ResponseWriter, req *http.Request) {
479 wrt.Write([]byte(`{"msg": "This is GNS" }`))
480 }
481}
diff --git a/src/gnunet/service/gns/service.go b/src/gnunet/service/gns/service.go
index 6fa1eb2..326d831 100644
--- a/src/gnunet/service/gns/service.go
+++ b/src/gnunet/service/gns/service.go
@@ -19,6 +19,7 @@
19package gns 19package gns
20 20
21import ( 21import (
22 "context"
22 "encoding/hex" 23 "encoding/hex"
23 "fmt" 24 "fmt"
24 "io" 25 "io"
@@ -28,8 +29,8 @@ import (
28 "gnunet/enums" 29 "gnunet/enums"
29 "gnunet/message" 30 "gnunet/message"
30 "gnunet/service" 31 "gnunet/service"
32 "gnunet/service/dht/blocks"
31 "gnunet/service/revocation" 33 "gnunet/service/revocation"
32 "gnunet/transport"
33 "gnunet/util" 34 "gnunet/util"
34 35
35 "github.com/bfix/gospel/data" 36 "github.com/bfix/gospel/data"
@@ -64,115 +65,117 @@ func NewService() service.Service {
64 return inst 65 return inst
65} 66}
66 67
67// Start the GNS service
68func (s *Service) Start(spec string) error {
69 return nil
70}
71
72// Stop the GNS service
73func (s *Service) Stop() error {
74 return nil
75}
76
77// ServeClient processes a client channel. 68// ServeClient processes a client channel.
78func (s *Service) ServeClient(ctx *service.SessionContext, mc *transport.MsgChannel) { 69func (s *Service) ServeClient(ctx context.Context, id int, mc *service.Connection) {
79 reqID := 0 70 reqID := 0
80loop: 71 var cancel context.CancelFunc
72 ctx, cancel = context.WithCancel(ctx)
73
81 for { 74 for {
82 // receive next message from client 75 // receive next message from client
83 reqID++ 76 reqID++
84 logger.Printf(logger.DBG, "[gns:%d:%d] Waiting for client request...\n", ctx.ID, reqID) 77 logger.Printf(logger.DBG, "[gns:%d:%d] Waiting for client request...\n", id, reqID)
85 msg, err := mc.Receive(ctx.Signaller()) 78 msg, err := mc.Receive(ctx)
86 if err != nil { 79 if err != nil {
87 if err == io.EOF { 80 if err == io.EOF {
88 logger.Printf(logger.INFO, "[gns:%d:%d] Client channel closed.\n", ctx.ID, reqID) 81 logger.Printf(logger.INFO, "[gns:%d:%d] Client channel closed.\n", id, reqID)
89 } else if err == transport.ErrChannelInterrupted { 82 } else if err == service.ErrConnectionInterrupted {
90 logger.Printf(logger.INFO, "[gns:%d:%d] Service operation interrupted.\n", ctx.ID, reqID) 83 logger.Printf(logger.INFO, "[gns:%d:%d] Service operation interrupted.\n", id, reqID)
91 } else { 84 } else {
92 logger.Printf(logger.ERROR, "[gns:%d:%d] Message-receive failed: %s\n", ctx.ID, reqID, err.Error()) 85 logger.Printf(logger.ERROR, "[gns:%d:%d] Message-receive failed: %s\n", id, reqID, err.Error())
93 } 86 }
94 break loop 87 break
95 } 88 }
96 logger.Printf(logger.INFO, "[gns:%d:%d] Received request: %v\n", ctx.ID, reqID, msg) 89 logger.Printf(logger.INFO, "[gns:%d:%d] Received request: %v\n", id, reqID, msg)
97 90
98 // perform lookup 91 // handle message
99 switch m := msg.(type) { 92 s.HandleMessage(context.WithValue(ctx, "label", fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
100 case *message.LookupMsg: 93 }
101 //---------------------------------------------------------- 94 // close client connection
102 // GNS_LOOKUP 95 mc.Close()
103 //---------------------------------------------------------- 96
104 97 // cancel all tasks running for this session/connection
105 // perform lookup on block (locally and remote) 98 logger.Printf(logger.INFO, "[gns:%d] Start closing session...\n", id)
106 go func(id int, m *message.LookupMsg) { 99 cancel()
107 logger.Printf(logger.INFO, "[gns:%d:%d] Lookup request received.\n", ctx.ID, id) 100}
108 resp := message.NewGNSLookupResultMsg(m.ID) 101
109 ctx.Add() 102// Handle a single incoming message
110 defer func() { 103func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back service.Responder) bool {
111 // send response 104 // assemble log label
112 if resp != nil { 105 label := ""
113 if err := mc.Send(resp, ctx.Signaller()); err != nil { 106 if v := ctx.Value("label"); v != nil {
114 logger.Printf(logger.ERROR, "[gns:%d:%d] Failed to send response: %s\n", ctx.ID, id, err.Error()) 107 label = v.(string)
115 } 108 }
116 } 109 // perform lookup
117 // go-routine finished 110 switch m := msg.(type) {
118 logger.Printf(logger.DBG, "[gns:%d:%d] Lookup request finished.\n", ctx.ID, id) 111 case *message.LookupMsg:
119 ctx.Remove() 112 //----------------------------------------------------------
120 }() 113 // GNS_LOOKUP
121 114 //----------------------------------------------------------
122 label := m.GetName() 115
123 kind := NewRRTypeList(int(m.Type)) 116 // perform lookup on block (locally and remote)
124 recset, err := s.Resolve(ctx, label, m.Zone, kind, int(m.Options), 0) 117 go func(m *message.LookupMsg) {
125 if err != nil { 118 logger.Printf(logger.INFO, "[gns%s] Lookup request received.\n", label)
126 logger.Printf(logger.ERROR, "[gns:%d:%d] Failed to lookup block: %s\n", ctx.ID, id, err.Error()) 119 resp := message.NewGNSLookupResultMsg(m.ID)
127 if err == transport.ErrChannelInterrupted { 120 defer func() {
128 resp = nil 121 // send response
122 if resp != nil {
123 if err := back.Send(ctx, resp); err != nil {
124 logger.Printf(logger.ERROR, "[gns%s] Failed to send response: %s\n", label, err.Error())
129 } 125 }
126 }
127 // go-routine finished
128 logger.Printf(logger.DBG, "[gns%s] Lookup request finished.\n", label)
129 }()
130
131 label := m.GetName()
132 kind := NewRRTypeList(int(m.Type))
133 recset, err := s.Resolve(ctx, label, m.Zone, kind, int(m.Options), 0)
134 if err != nil {
135 logger.Printf(logger.ERROR, "[gns%s] Failed to lookup block: %s\n", label, err.Error())
136 if err == service.ErrConnectionInterrupted {
137 resp = nil
138 }
139 return
140 }
141 // handle records
142 if recset != nil {
143 logger.Printf(logger.DBG, "[gns%s] Received record set with %d entries\n", label, recset.Count)
144
145 // get records from block
146 if recset.Count == 0 {
147 logger.Printf(logger.WARN, "[gns%s] No records in block\n", label)
130 return 148 return
131 } 149 }
132 // handle records 150 // process records
133 if recset != nil { 151 for i, rec := range recset.Records {
134 logger.Printf(logger.DBG, "[gns:%d:%d] Received record set with %d entries\n", ctx.ID, id, recset.Count) 152 logger.Printf(logger.DBG, "[gns%s] Record #%d: %v\n", label, i, rec)
135 153
136 // get records from block 154 // is this the record type we are looking for?
137 if recset.Count == 0 { 155 if rec.Type == m.Type || int(m.Type) == enums.GNS_TYPE_ANY {
138 logger.Printf(logger.WARN, "[gns:%d:%d] No records in block\n", ctx.ID, id) 156 // add it to the response message
139 return 157 resp.AddRecord(rec)
140 }
141 // process records
142 for i, rec := range recset.Records {
143 logger.Printf(logger.DBG, "[gns:%d:%d] Record #%d: %v\n", ctx.ID, id, i, rec)
144
145 // is this the record type we are looking for?
146 if rec.Type == m.Type || int(m.Type) == enums.GNS_TYPE_ANY {
147 // add it to the response message
148 resp.AddRecord(rec)
149 }
150 } 158 }
151 } 159 }
152 }(reqID, m) 160 }
153 161 }(m)
154 default:
155 //----------------------------------------------------------
156 // UNKNOWN message type received
157 //----------------------------------------------------------
158 logger.Printf(logger.ERROR, "[gns:%d:%d] Unhandled message of type (%d)\n", ctx.ID, reqID, msg.Header().MsgType)
159 break loop
160 }
161 }
162 // close client connection
163 mc.Close()
164 162
165 // cancel all tasks running for this session/connection 163 default:
166 logger.Printf(logger.INFO, "[gns:%d] Start closing session... [%d]\n", ctx.ID, ctx.Waiting()) 164 //----------------------------------------------------------
167 ctx.Cancel() 165 // UNKNOWN message type received
166 //----------------------------------------------------------
167 logger.Printf(logger.ERROR, "[gns%s] Unhandled message of type (%d)\n", label, msg.Header().MsgType)
168 return false
169 }
170 return true
168} 171}
169 172
170//====================================================================== 173//======================================================================
171// Revocationrelated methods 174// Revocation-related methods
172//====================================================================== 175//======================================================================
173 176
174// QueryKeyRevocation checks if a key has been revoked 177// QueryKeyRevocation checks if a key has been revoked
175func (s *Service) QueryKeyRevocation(ctx *service.SessionContext, zkey *crypto.ZoneKey) (valid bool, err error) { 178func (s *Service) QueryKeyRevocation(ctx context.Context, zkey *crypto.ZoneKey) (valid bool, err error) {
176 logger.Printf(logger.DBG, "[gns] QueryKeyRev(%s)...\n", util.EncodeBinaryToString(zkey.Bytes())) 179 logger.Printf(logger.DBG, "[gns] QueryKeyRev(%s)...\n", util.EncodeBinaryToString(zkey.Bytes()))
177 180
178 // assemble request 181 // assemble request
@@ -180,7 +183,7 @@ func (s *Service) QueryKeyRevocation(ctx *service.SessionContext, zkey *crypto.Z
180 183
181 // get response from Revocation service 184 // get response from Revocation service
182 var resp message.Message 185 var resp message.Message
183 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Endpoint, req); err != nil { 186 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Service.Socket, req); err != nil {
184 return 187 return
185 } 188 }
186 189
@@ -195,7 +198,7 @@ func (s *Service) QueryKeyRevocation(ctx *service.SessionContext, zkey *crypto.Z
195} 198}
196 199
197// RevokeKey revokes a key with given revocation data 200// RevokeKey revokes a key with given revocation data
198func (s *Service) RevokeKey(ctx *service.SessionContext, rd *revocation.RevData) (success bool, err error) { 201func (s *Service) RevokeKey(ctx context.Context, rd *revocation.RevData) (success bool, err error) {
199 logger.Printf(logger.DBG, "[gns] RevokeKey(%s)...\n", rd.ZoneKeySig.ID()) 202 logger.Printf(logger.DBG, "[gns] RevokeKey(%s)...\n", rd.ZoneKeySig.ID())
200 203
201 // assemble request 204 // assemble request
@@ -206,7 +209,7 @@ func (s *Service) RevokeKey(ctx *service.SessionContext, rd *revocation.RevData)
206 209
207 // get response from Revocation service 210 // get response from Revocation service
208 var resp message.Message 211 var resp message.Message
209 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Endpoint, req); err != nil { 212 if resp, err = service.RequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Service.Socket, req); err != nil {
210 return 213 return
211 } 214 }
212 215
@@ -225,17 +228,17 @@ func (s *Service) RevokeKey(ctx *service.SessionContext, rd *revocation.RevData)
225//====================================================================== 228//======================================================================
226 229
227// LookupNamecache returns a cached lookup (if available) 230// LookupNamecache returns a cached lookup (if available)
228func (s *Service) LookupNamecache(ctx *service.SessionContext, query *Query) (block *message.Block, err error) { 231func (s *Service) LookupNamecache(ctx context.Context, query *blocks.GNSQuery) (block *blocks.GNSBlock, err error) {
229 logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n", hex.EncodeToString(query.Key.Bits)) 232 logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n", hex.EncodeToString(query.Key().Bits))
230 233
231 // assemble Namecache request 234 // assemble Namecache request
232 req := message.NewNamecacheLookupMsg(query.Key) 235 req := message.NewNamecacheLookupMsg(query.Key())
233 req.ID = uint32(util.NextID()) 236 req.ID = uint32(util.NextID())
234 block = nil 237 block = nil
235 238
236 // get response from Namecache service 239 // get response from Namecache service
237 var resp message.Message 240 var resp message.Message
238 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Endpoint, req); err != nil { 241 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Service.Socket, req); err != nil {
239 return 242 return
240 } 243 }
241 244
@@ -250,7 +253,7 @@ func (s *Service) LookupNamecache(ctx *service.SessionContext, query *Query) (bl
250 break 253 break
251 } 254 }
252 // check if block was found 255 // check if block was found
253 if len(m.EncData) == 0 || util.IsNull(m.EncData) { 256 if len(m.EncData) == 0 || util.IsAll(m.EncData, 0) {
254 logger.Println(logger.DBG, "[gns] block not found in namecache") 257 logger.Println(logger.DBG, "[gns] block not found in namecache")
255 break 258 break
256 } 259 }
@@ -262,21 +265,21 @@ func (s *Service) LookupNamecache(ctx *service.SessionContext, query *Query) (bl
262 } 265 }
263 266
264 // assemble GNSBlock from message 267 // assemble GNSBlock from message
265 block = new(message.Block) 268 block = new(blocks.GNSBlock)
266 block.DerivedKeySig = m.DerivedKeySig 269 block.DerivedKeySig = m.DerivedKeySig
267 sb := new(message.SignedBlockData) 270 sb := new(blocks.SignedGNSBlockData)
268 sb.Purpose = new(crypto.SignaturePurpose) 271 sb.Purpose = new(crypto.SignaturePurpose)
269 sb.Purpose.Purpose = enums.SIG_GNS_RECORD_SIGN 272 sb.Purpose.Purpose = enums.SIG_GNS_RECORD_SIGN
270 sb.Purpose.Size = uint32(16 + len(m.EncData)) 273 sb.Purpose.Size = uint32(16 + len(m.EncData))
271 sb.Expire = m.Expire 274 sb.Expire = m.Expire
272 sb.EncData = m.EncData 275 sb.Data = m.EncData
273 block.Block = sb 276 block.Body = sb
274 277
275 // verify and decrypt block 278 // verify and decrypt block
276 if err = block.Verify(query.Zone, query.Label); err != nil { 279 if err = query.Verify(block); err != nil {
277 break 280 break
278 } 281 }
279 if err = block.Decrypt(query.Zone, query.Label); err != nil { 282 if err = query.Decrypt(block); err != nil {
280 break 283 break
281 } 284 }
282 default: 285 default:
@@ -287,7 +290,7 @@ func (s *Service) LookupNamecache(ctx *service.SessionContext, query *Query) (bl
287} 290}
288 291
289// StoreNamecache stores a lookup in the local namecache. 292// StoreNamecache stores a lookup in the local namecache.
290func (s *Service) StoreNamecache(ctx *service.SessionContext, block *message.Block) (err error) { 293func (s *Service) StoreNamecache(ctx context.Context, query *blocks.GNSQuery, block *blocks.GNSBlock) (err error) {
291 logger.Println(logger.DBG, "[gns] StoreNamecache()...") 294 logger.Println(logger.DBG, "[gns] StoreNamecache()...")
292 295
293 // assemble Namecache request 296 // assemble Namecache request
@@ -296,7 +299,7 @@ func (s *Service) StoreNamecache(ctx *service.SessionContext, block *message.Blo
296 299
297 // get response from Namecache service 300 // get response from Namecache service
298 var resp message.Message 301 var resp message.Message
299 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Endpoint, req); err != nil { 302 if resp, err = service.RequestResponse(ctx, "gns", "Namecache", config.Cfg.Namecache.Service.Socket, req); err != nil {
300 return 303 return
301 } 304 }
302 305
@@ -327,13 +330,13 @@ func (s *Service) StoreNamecache(ctx *service.SessionContext, block *message.Blo
327//====================================================================== 330//======================================================================
328 331
329// LookupDHT gets a GNS block from the DHT for the given query key. 332// LookupDHT gets a GNS block from the DHT for the given query key.
330func (s *Service) LookupDHT(ctx *service.SessionContext, query *Query) (block *message.Block, err error) { 333func (s *Service) LookupDHT(ctx context.Context, query blocks.Query) (block blocks.Block, err error) {
331 logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n", hex.EncodeToString(query.Key.Bits)) 334 logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n", hex.EncodeToString(query.Key().Bits))
332 block = nil 335 block = nil
333 336
334 // client-connect to the DHT service 337 // client-connect to the DHT service
335 logger.Println(logger.DBG, "[gns] Connecting to DHT service...") 338 logger.Println(logger.DBG, "[gns] Connecting to DHT service...")
336 cl, err := service.NewClient(config.Cfg.DHT.Endpoint) 339 cl, err := service.NewClient(ctx, config.Cfg.DHT.Service.Socket)
337 if err != nil { 340 if err != nil {
338 return nil, err 341 return nil, err
339 } 342 }
@@ -360,7 +363,7 @@ func (s *Service) LookupDHT(ctx *service.SessionContext, query *Query) (block *m
360 ) 363 )
361 364
362 // send DHT GET request and wait for response 365 // send DHT GET request and wait for response
363 reqGet := message.NewDHTClientGetMsg(query.Key) 366 reqGet := message.NewDHTClientGetMsg(query.Key())
364 reqGet.ID = uint64(util.NextID()) 367 reqGet.ID = uint64(util.NextID())
365 reqGet.ReplLevel = uint32(enums.DHT_GNS_REPLICATION_LEVEL) 368 reqGet.ReplLevel = uint32(enums.DHT_GNS_REPLICATION_LEVEL)
366 reqGet.Type = uint32(enums.BLOCK_TYPE_GNS_NAMERECORD) 369 reqGet.Type = uint32(enums.BLOCK_TYPE_GNS_NAMERECORD)
@@ -368,16 +371,16 @@ func (s *Service) LookupDHT(ctx *service.SessionContext, query *Query) (block *m
368 371
369 if err = interact(reqGet, true); err != nil { 372 if err = interact(reqGet, true); err != nil {
370 // check for aborted remote lookup: we need to cancel the query 373 // check for aborted remote lookup: we need to cancel the query
371 if err == transport.ErrChannelInterrupted { 374 if err == service.ErrConnectionInterrupted {
372 logger.Println(logger.WARN, "[gns] remote Lookup aborted -- cleaning up.") 375 logger.Println(logger.WARN, "[gns] remote Lookup aborted -- cleaning up.")
373 376
374 // send DHT GET_STOP request and terminate 377 // send DHT GET_STOP request and terminate
375 reqStop := message.NewDHTClientGetStopMsg(query.Key) 378 reqStop := message.NewDHTClientGetStopMsg(query.Key())
376 reqStop.ID = reqGet.ID 379 reqStop.ID = reqGet.ID
377 if err = interact(reqStop, false); err != nil { 380 if err = interact(reqStop, false); err != nil {
378 logger.Printf(logger.ERROR, "[gns] remote Lookup abort failed: %s\n", err.Error()) 381 logger.Printf(logger.ERROR, "[gns] remote Lookup abort failed: %s\n", err.Error())
379 } 382 }
380 return nil, transport.ErrChannelInterrupted 383 return nil, service.ErrConnectionInterrupted
381 } 384 }
382 } 385 }
383 386
@@ -407,22 +410,24 @@ func (s *Service) LookupDHT(ctx *service.SessionContext, query *Query) (block *m
407 } 410 }
408 411
409 // get GNSBlock from message 412 // get GNSBlock from message
410 block = message.NewBlock() 413 qGNS := query.(*blocks.GNSQuery)
414 block = new(blocks.GNSBlock)
411 if err = data.Unmarshal(block, m.Data); err != nil { 415 if err = data.Unmarshal(block, m.Data); err != nil {
412 logger.Printf(logger.ERROR, "[gns] can't read GNS block: %s\n", err.Error()) 416 logger.Printf(logger.ERROR, "[gns] can't read GNS block: %s\n", err.Error())
413 break 417 break
414 } 418 }
419
415 // verify and decrypt block 420 // verify and decrypt block
416 if err = block.Verify(query.Zone, query.Label); err != nil { 421 if err = qGNS.Verify(block); err != nil {
417 break 422 break
418 } 423 }
419 if err = block.Decrypt(query.Zone, query.Label); err != nil { 424 if err = qGNS.Decrypt(block); err != nil {
420 break 425 break
421 } 426 }
422 427
423 // we got a result from DHT that was not in the namecache, 428 // we got a result from DHT that was not in the namecache,
424 // so store it there now. 429 // so store it there now.
425 if err = s.StoreNamecache(ctx, block); err != nil { 430 if err = s.StoreNamecache(ctx, qGNS, block.(*blocks.GNSBlock)); err != nil {
426 logger.Printf(logger.ERROR, "[gns] can't store block in Namecache: %s\n", err.Error()) 431 logger.Printf(logger.ERROR, "[gns] can't store block in Namecache: %s\n", err.Error())
427 } 432 }
428 } 433 }
diff --git a/src/gnunet/service/module.go b/src/gnunet/service/module.go
new file mode 100644
index 0000000..98307d6
--- /dev/null
+++ b/src/gnunet/service/module.go
@@ -0,0 +1,70 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package service
20
21import (
22 "context"
23 "gnunet/core"
24 "net/http"
25)
26
27// Module is an interface for GNUnet service modules (workers).
28type Module interface {
29 // RPC returns the route and handler for JSON-RPC requests
30 RPC() (string, func(http.ResponseWriter, *http.Request))
31
32 // Filter returns the event filter for the module
33 Filter() *core.EventFilter
34}
35
36// EventHandler is a function prototype for event handling
37type EventHandler func(context.Context, *core.Event)
38
39// ModuleImpl is an event-handling type used by Module implementations.
40type ModuleImpl struct {
41 ch chan *core.Event // channel for core events.
42}
43
44// NewModuleImplementation returns a new base module and starts
45func NewModuleImpl() (m *ModuleImpl) {
46 return &ModuleImpl{
47 ch: make(chan *core.Event),
48 }
49}
50
51// Run event handling loop
52func (m *ModuleImpl) Run(ctx context.Context, hdlr EventHandler, filter *core.EventFilter) (listener *core.Listener) {
53 // listener for registration
54 listener = core.NewListener(m.ch, filter)
55 // run event loop
56 go func() {
57 for {
58 select {
59 // Handle events
60 case event := <-m.ch:
61 hdlr(ctx, event)
62
63 // wait for terminate signal
64 case <-ctx.Done():
65 return
66 }
67 }
68 }()
69 return
70}
diff --git a/src/gnunet/service/namecache/module.go b/src/gnunet/service/namecache/module.go
index d5aa014..9d5bca1 100644
--- a/src/gnunet/service/namecache/module.go
+++ b/src/gnunet/service/namecache/module.go
@@ -19,9 +19,11 @@
19package namecache 19package namecache
20 20
21import ( 21import (
22 "gnunet/message" 22 "context"
23 "gnunet/config"
24 "gnunet/core"
23 "gnunet/service" 25 "gnunet/service"
24 "gnunet/service/gns" 26 "gnunet/service/dht/blocks"
25) 27)
26 28
27//====================================================================== 29//======================================================================
@@ -34,12 +36,29 @@ import (
34 36
35// Namecache handles the transient storage of GNS blocks under the query key. 37// Namecache handles the transient storage of GNS blocks under the query key.
36type NamecacheModule struct { 38type NamecacheModule struct {
39 service.ModuleImpl
40
41 cache service.DHTStore // transient block cache
42}
43
44// NewModule creates a new module instance.
45func NewModule(ctx context.Context, c *core.Core) (m *NamecacheModule) {
46 m = &NamecacheModule{
47 ModuleImpl: *service.NewModuleImpl(),
48 }
49 m.cache, _ = service.NewDHTStore(config.Cfg.Namecache.Storage)
50 return
37} 51}
38 52
39func (nc *NamecacheModule) Get(ctx *service.SessionContext, query *gns.Query) (*message.Block, error) { 53// Get an entry from the cache if available.
40 return nil, nil 54func (m *NamecacheModule) Get(ctx context.Context, query *blocks.GNSQuery) (block *blocks.GNSBlock, err error) {
55 var b blocks.Block
56 b, err = m.cache.Get(query)
57 err = blocks.Unwrap(b, block)
58 return
41} 59}
42 60
43func (nc *NamecacheModule) Put(ctx *service.SessionContext, block *message.Block) error { 61// Put entry into the cache.
44 return nil 62func (m *NamecacheModule) Put(ctx context.Context, query *blocks.GNSQuery, block *blocks.GNSBlock) error {
63 return m.cache.Put(query, block)
45} 64}
diff --git a/src/gnunet/service/revocation/module.go b/src/gnunet/service/revocation/module.go
index d13c069..37b57ab 100644
--- a/src/gnunet/service/revocation/module.go
+++ b/src/gnunet/service/revocation/module.go
@@ -19,8 +19,11 @@
19package revocation 19package revocation
20 20
21import ( 21import (
22 "context"
22 "gnunet/config" 23 "gnunet/config"
24 "gnunet/core"
23 "gnunet/crypto" 25 "gnunet/crypto"
26 "gnunet/message"
24 "gnunet/service" 27 "gnunet/service"
25 "gnunet/util" 28 "gnunet/util"
26 "net/http" 29 "net/http"
@@ -33,55 +36,71 @@ import (
33// "GNUnet Revocation" implementation 36// "GNUnet Revocation" implementation
34//====================================================================== 37//======================================================================
35 38
39// The minimum average difficulty acceptable for a set of revocation PoWs
40const MinAvgDifficulty = 23
41
36// Module handles the revocation-related calls to other modules. 42// Module handles the revocation-related calls to other modules.
37type Module struct { 43type Module struct {
38 bloomf *data.BloomFilter // bloomfilter for fast revocation check 44 service.ModuleImpl
39 kvs util.KeyValueStore // storage for known revocations 45
46 bloomf *data.BloomFilter // bloomfilter for fast revocation check
47 kvs service.KVStore // storage for known revocations
40} 48}
41 49
42// Init a revocation module 50// NewModule returns an initialized revocation module
43func (m *Module) Init() error { 51func NewModule(ctx context.Context, c *core.Core) (m *Module) {
44 // Initialize access to revocation data storage 52 // create and init instance
45 var err error 53 m = &Module{
46 if m.kvs, err = util.OpenKVStore(config.Cfg.Revocation.Storage); err != nil { 54 ModuleImpl: *service.NewModuleImpl(),
47 return err
48 }
49 // traverse the storage and build bloomfilter for all keys
50 m.bloomf = data.NewBloomFilter(1000000, 1e-8)
51 keys, err := m.kvs.List()
52 if err != nil {
53 return err
54 } 55 }
55 for _, key := range keys { 56 init := func() (err error) {
56 buf, err := util.DecodeStringToBinary(key, 32) 57 // Initialize access to revocation data storage
57 if err != nil { 58 if m.kvs, err = service.NewKVStore(config.Cfg.Revocation.Storage); err != nil {
58 return err 59 return
59 } 60 }
60 m.bloomf.Add(buf) 61 // traverse the storage and build bloomfilter for all keys
62 m.bloomf = data.NewBloomFilter(1000000, 1e-8)
63 var keys []string
64 if keys, err = m.kvs.List(); err != nil {
65 return
66 }
67 for _, key := range keys {
68 m.bloomf.Add([]byte(key))
69 }
70 return
61 } 71 }
62 return nil 72 if err := init(); err != nil {
63}
64
65// NewModule returns an initialized revocation module
66func NewModule() *Module {
67 m := new(Module)
68 if err := m.Init(); err != nil {
69 logger.Printf(logger.ERROR, "[revocation] Failed to initialize module: %s\n", err.Error()) 73 logger.Printf(logger.ERROR, "[revocation] Failed to initialize module: %s\n", err.Error())
70 return nil 74 return nil
71 } 75 }
76 // register as listener for core events
77 listener := m.Run(ctx, m.event, m.Filter())
78 c.Register("gns", listener)
72 return m 79 return m
73} 80}
74 81
75// RPC returns the route and handler function for a JSON-RPC request 82//----------------------------------------------------------------------
76func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) { 83
77 return "/revocation/", func(wrt http.ResponseWriter, req *http.Request) { 84// Filter returns the event filter for the service
78 wrt.Write([]byte(`{"msg": "This is REVOCATION" }`)) 85func (m *Module) Filter() *core.EventFilter {
79 } 86 f := core.NewEventFilter()
87 f.AddMsgType(message.REVOCATION_QUERY)
88 f.AddMsgType(message.REVOCATION_QUERY_RESPONSE)
89 f.AddMsgType(message.REVOCATION_REVOKE)
90 f.AddMsgType(message.REVOCATION_REVOKE_RESPONSE)
91 return f
80} 92}
81 93
94// Event handler
95func (m *Module) event(ctx context.Context, ev *core.Event) {
96
97}
98
99//----------------------------------------------------------------------
100
82// Query return true if the pkey is valid (not revoked) and false 101// Query return true if the pkey is valid (not revoked) and false
83// if the pkey has been revoked. 102// if the pkey has been revoked.
84func (m *Module) Query(ctx *service.SessionContext, zkey *crypto.ZoneKey) (valid bool, err error) { 103func (m *Module) Query(ctx context.Context, zkey *crypto.ZoneKey) (valid bool, err error) {
85 // fast check first: is the key in the bloomfilter? 104 // fast check first: is the key in the bloomfilter?
86 data := zkey.Bytes() 105 data := zkey.Bytes()
87 if !m.bloomf.Contains(data) { 106 if !m.bloomf.Contains(data) {
@@ -100,9 +119,9 @@ func (m *Module) Query(ctx *service.SessionContext, zkey *crypto.ZoneKey) (valid
100} 119}
101 120
102// Revoke a key with given revocation data 121// Revoke a key with given revocation data
103func (m *Module) Revoke(ctx *service.SessionContext, rd *RevData) (success bool, err error) { 122func (m *Module) Revoke(ctx context.Context, rd *RevData) (success bool, err error) {
104 // verify the revocation data 123 // verify the revocation data
105 rc := rd.Verify(true) 124 diff, rc := rd.Verify(true)
106 switch { 125 switch {
107 case rc == -1: 126 case rc == -1:
108 logger.Println(logger.WARN, "[revocation] Revoke: Missing/invalid signature") 127 logger.Println(logger.WARN, "[revocation] Revoke: Missing/invalid signature")
@@ -113,10 +132,12 @@ func (m *Module) Revoke(ctx *service.SessionContext, rd *RevData) (success bool,
113 case rc == -3: 132 case rc == -3:
114 logger.Println(logger.WARN, "[revocation] Revoke: Wrong PoW sequence order") 133 logger.Println(logger.WARN, "[revocation] Revoke: Wrong PoW sequence order")
115 return false, nil 134 return false, nil
116 case rc < 25: 135 }
136 if diff < float64(MinAvgDifficulty) {
117 logger.Println(logger.WARN, "[revocation] Revoke: Difficulty to small") 137 logger.Println(logger.WARN, "[revocation] Revoke: Difficulty to small")
118 return false, nil 138 return false, nil
119 } 139 }
140
120 // store the revocation data 141 // store the revocation data
121 // (1) add it to the bloomfilter 142 // (1) add it to the bloomfilter
122 m.bloomf.Add(rd.ZoneKeySig.KeyData) 143 m.bloomf.Add(rd.ZoneKeySig.KeyData)
@@ -129,3 +150,12 @@ func (m *Module) Revoke(ctx *service.SessionContext, rd *RevData) (success bool,
129 err = m.kvs.Put(rd.ZoneKeySig.ID(), value) 150 err = m.kvs.Put(rd.ZoneKeySig.ID(), value)
130 return true, err 151 return true, err
131} 152}
153
154//----------------------------------------------------------------------
155
156// RPC returns the route and handler function for a JSON-RPC request
157func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
158 return "/revocation/", func(wrt http.ResponseWriter, req *http.Request) {
159 wrt.Write([]byte(`{"msg": "This is REVOCATION" }`))
160 }
161}
diff --git a/src/gnunet/service/revocation/pow.go b/src/gnunet/service/revocation/pow.go
index 04bbb13..57ddad7 100644
--- a/src/gnunet/service/revocation/pow.go
+++ b/src/gnunet/service/revocation/pow.go
@@ -40,6 +40,11 @@ import (
40// Proof-of-Work data 40// Proof-of-Work data
41//---------------------------------------------------------------------- 41//----------------------------------------------------------------------
42 42
43const (
44 // MinDifficulty for revocations -> expires in ~1year
45 MinDifficulty = 23
46)
47
43// PoWData is the proof-of-work data 48// PoWData is the proof-of-work data
44type PoWData struct { 49type PoWData struct {
45 PoW uint64 `order:"big"` // start with this PoW value 50 PoW uint64 `order:"big"` // start with this PoW value
@@ -143,6 +148,11 @@ func NewRevDataFromMsg(m *message.RevocationRevokeMsg) *RevData {
143 return rd 148 return rd
144} 149}
145 150
151// Size of a serialized RevData object.
152func (rd *RevData) Size() int {
153 return 16 + 8*len(rd.PoWs) + int(rd.ZoneKeySig.SigSize())
154}
155
146// Sign the revocation data 156// Sign the revocation data
147func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err error) { 157func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err error) {
148 sigBlock := &SignedRevData{ 158 sigBlock := &SignedRevData{
@@ -160,12 +170,10 @@ func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err error) {
160 return 170 return
161} 171}
162 172
163// Verify a revocation object: returns the (smallest) number of leading 173// Verify a revocation object and return the average difficulty of the PoWs
164// zero-bits in the PoWs of this revocation; a number > 0, but smaller 174// in this revocation and a verification status (-1=failed signature, -2=
165// than the minimum (25) indicates invalid PoWs; a value of -1 indicates 175// expired revocation, -3="out-of-order" PoW sequence).
166// a failed signature; -2 indicates an expired revocation and -3 for a 176func (rd *RevData) Verify(withSig bool) (zbits float64, rc int) {
167// "out-of-order" PoW sequence.
168func (rd *RevData) Verify(withSig bool) int {
169 177
170 // (1) check signature 178 // (1) check signature
171 if withSig { 179 if withSig {
@@ -179,39 +187,36 @@ func (rd *RevData) Verify(withSig bool) int {
179 } 187 }
180 sigData, err := data.Marshal(sigBlock) 188 sigData, err := data.Marshal(sigBlock)
181 if err != nil { 189 if err != nil {
182 return -1 190 return 0., -1
183 } 191 }
184 valid, err := rd.ZoneKeySig.Verify(sigData) 192 valid, err := rd.ZoneKeySig.Verify(sigData)
185 if err != nil || !valid { 193 if err != nil || !valid {
186 return -1 194 return 0., -1
187 } 195 }
188 } 196 }
189 197
190 // (2) check PoWs 198 // (2) check PoWs
191 var ( 199 var last uint64 = 0
192 zbits float64 = 0
193 last uint64 = 0
194 )
195 for _, pow := range rd.PoWs { 200 for _, pow := range rd.PoWs {
196 // check sequence order 201 // check sequence order
197 if pow <= last { 202 if pow <= last {
198 return -3 203 return 0., -3
199 } 204 }
200 last = pow 205 last = pow
201 // compute number of leading zero-bits 206 // compute number of leading zero-bits
202 work := NewPoWData(pow, rd.Timestamp, &rd.ZoneKeySig.ZoneKey) 207 work := NewPoWData(pow, rd.Timestamp, &rd.ZoneKeySig.ZoneKey)
203 zbits += float64(512 - work.Compute().BitLen()) 208 zbits += float64(512 - work.Compute().BitLen())
204 } 209 }
205 zbits /= 32.0 210 zbits /= float64(len(rd.PoWs))
206 211
207 // (3) check expiration 212 // (3) check expiration
208 if zbits > 24.0 { 213 if zbits >= 23.0 {
209 ttl := time.Duration(int((zbits-24)*365*24)) * time.Hour 214 ttl := time.Duration(int((zbits-22)*365*24*1.1)) * time.Hour
210 if util.AbsoluteTimeNow().Add(ttl).Expired() { 215 if util.AbsoluteTimeNow().Add(ttl).Expired() {
211 return -2 216 return zbits, -2
212 } 217 }
213 } 218 }
214 return int(zbits) 219 return zbits, 0
215} 220}
216 221
217//---------------------------------------------------------------------- 222//----------------------------------------------------------------------
@@ -240,6 +245,11 @@ func NewRevDataCalc(zkey *crypto.ZoneKey) *RevDataCalc {
240 return rd 245 return rd
241} 246}
242 247
248// Size of a serialized RevData object.
249func (rdc *RevDataCalc) Size() int {
250 return rdc.RevData.Size() + 2*len(rdc.Bits) + 1
251}
252
243// Average number of leading zero-bits in current list 253// Average number of leading zero-bits in current list
244func (rdc *RevDataCalc) Average() float64 { 254func (rdc *RevDataCalc) Average() float64 {
245 var sum uint16 = 0 255 var sum uint16 = 0
diff --git a/src/gnunet/service/revocation/pow_test.go b/src/gnunet/service/revocation/pow_test.go
index 8280402..a59f92b 100644
--- a/src/gnunet/service/revocation/pow_test.go
+++ b/src/gnunet/service/revocation/pow_test.go
@@ -16,7 +16,6 @@ func TestRevocationRFC(t *testing.T) {
16 var ( 16 var (
17 D = "6fea32c05af58bfa979553d188605fd57d8bf9cc263b78d5f7478c07b998ed70" 17 D = "6fea32c05af58bfa979553d188605fd57d8bf9cc263b78d5f7478c07b998ed70"
18 ZKEY = "000100002ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa" 18 ZKEY = "000100002ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa"
19 DIFF = 7
20 PROOF = "" + 19 PROOF = "" +
21 "0005d66da3598127" + 20 "0005d66da3598127" +
22 "0000395d1827c000" + 21 "0000395d1827c000" +
@@ -142,8 +141,11 @@ func TestRevocationRFC(t *testing.T) {
142 } 141 }
143 142
144 // verify revocation data object 143 // verify revocation data object
145 rc := revData.Verify(true) 144 diff, rc := revData.Verify(true)
146 if rc != DIFF { 145 if testing.Verbose() {
146 t.Logf("Average difficulty of PoWs = %f\n", diff)
147 }
148 if rc != 0 {
147 t.Fatalf("REV_Verify (pkey): %d\n", rc) 149 t.Fatalf("REV_Verify (pkey): %d\n", rc)
148 } 150 }
149} 151}
diff --git a/src/gnunet/service/revocation/service.go b/src/gnunet/service/revocation/service.go
index a82e582..4d48d40 100644
--- a/src/gnunet/service/revocation/service.go
+++ b/src/gnunet/service/revocation/service.go
@@ -19,11 +19,12 @@
19package revocation 19package revocation
20 20
21import ( 21import (
22 "context"
23 "fmt"
22 "io" 24 "io"
23 25
24 "gnunet/message" 26 "gnunet/message"
25 "gnunet/service" 27 "gnunet/service"
26 "gnunet/transport"
27 28
28 "github.com/bfix/gospel/logger" 29 "github.com/bfix/gospel/logger"
29) 30)
@@ -44,114 +45,113 @@ func NewService() service.Service {
44 return inst 45 return inst
45} 46}
46 47
47// Start the Revocation service
48func (s *Service) Start(spec string) error {
49 return nil
50}
51
52// Stop the Revocation service
53func (s *Service) Stop() error {
54 return nil
55}
56
57// ServeClient processes a client channel. 48// ServeClient processes a client channel.
58func (s *Service) ServeClient(ctx *service.SessionContext, mc *transport.MsgChannel) { 49func (s *Service) ServeClient(ctx context.Context, id int, mc *service.Connection) {
59 reqID := 0 50 reqID := 0
60loop: 51 var cancel context.CancelFunc
52 ctx, cancel = context.WithCancel(ctx)
53
61 for { 54 for {
62 // receive next message from client 55 // receive next message from client
63 reqID++ 56 reqID++
64 logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for client request...\n", ctx.ID, reqID) 57 logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for client request...\n", id, reqID)
65 msg, err := mc.Receive(ctx.Signaller()) 58 msg, err := mc.Receive(ctx)
66 if err != nil { 59 if err != nil {
67 if err == io.EOF { 60 if err == io.EOF {
68 logger.Printf(logger.INFO, "[revocation:%d:%d] Client channel closed.\n", ctx.ID, reqID) 61 logger.Printf(logger.INFO, "[revocation:%d:%d] Client channel closed.\n", id, reqID)
69 } else if err == transport.ErrChannelInterrupted { 62 } else if err == service.ErrConnectionInterrupted {
70 logger.Printf(logger.INFO, "[revocation:%d:%d] Service operation interrupted.\n", ctx.ID, reqID) 63 logger.Printf(logger.INFO, "[revocation:%d:%d] Service operation interrupted.\n", id, reqID)
71 } else { 64 } else {
72 logger.Printf(logger.ERROR, "[revocation:%d:%d] Message-receive failed: %s\n", ctx.ID, reqID, err.Error()) 65 logger.Printf(logger.ERROR, "[revocation:%d:%d] Message-receive failed: %s\n", id, reqID, err.Error())
73 } 66 }
74 break loop 67 break
75 }
76 logger.Printf(logger.INFO, "[revocation:%d:%d] Received request: %v\n", ctx.ID, reqID, msg)
77
78 // handle request
79 switch m := msg.(type) {
80 case *message.RevocationQueryMsg:
81 //----------------------------------------------------------
82 // REVOCATION_QUERY
83 //----------------------------------------------------------
84 go func(id int, m *message.RevocationQueryMsg) {
85 logger.Printf(logger.INFO, "[revocation:%d:%d] Query request received.\n", ctx.ID, id)
86 var resp *message.RevocationQueryResponseMsg
87 ctx.Add()
88 defer func() {
89 // send response
90 if resp != nil {
91 if err := mc.Send(resp, ctx.Signaller()); err != nil {
92 logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", ctx.ID, id, err.Error())
93 }
94 }
95 // go-routine finished
96 logger.Printf(logger.DBG, "[revocation:%d:%d] Query request finished.\n", ctx.ID, id)
97 ctx.Remove()
98 }()
99
100 valid, err := s.Query(ctx, m.Zone)
101 if err != nil {
102 logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to query revocation status: %s\n", ctx.ID, id, err.Error())
103 if err == transport.ErrChannelInterrupted {
104 resp = nil
105 }
106 return
107 }
108 resp = message.NewRevocationQueryResponseMsg(valid)
109 }(reqID, m)
110
111 case *message.RevocationRevokeMsg:
112 //----------------------------------------------------------
113 // REVOCATION_REVOKE
114 //----------------------------------------------------------
115 go func(id int, m *message.RevocationRevokeMsg) {
116 logger.Printf(logger.INFO, "[revocation:%d:%d] Revoke request received.\n", ctx.ID, id)
117 var resp *message.RevocationRevokeResponseMsg
118 ctx.Add()
119 defer func() {
120 // send response
121 if resp != nil {
122 if err := mc.Send(resp, ctx.Signaller()); err != nil {
123 logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", ctx.ID, id, err.Error())
124 }
125 }
126 // go-routine finished
127 logger.Printf(logger.DBG, "[revocation:%d:%d] Revoke request finished.\n", ctx.ID, id)
128 ctx.Remove()
129 }()
130
131 rd := NewRevDataFromMsg(m)
132 valid, err := s.Revoke(ctx, rd)
133 if err != nil {
134 logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to revoke key: %s\n", ctx.ID, id, err.Error())
135 if err == transport.ErrChannelInterrupted {
136 resp = nil
137 }
138 return
139 }
140 resp = message.NewRevocationRevokeResponseMsg(valid)
141 }(reqID, m)
142
143 default:
144 //----------------------------------------------------------
145 // UNKNOWN message type received
146 //----------------------------------------------------------
147 logger.Printf(logger.ERROR, "[revocation:%d:%d] Unhandled message of type (%d)\n", ctx.ID, reqID, msg.Header().MsgType)
148 break loop
149 } 68 }
69 logger.Printf(logger.INFO, "[revocation:%d:%d] Received request: %v\n", id, reqID, msg)
70
71 // handle message
72 s.HandleMessage(context.WithValue(ctx, "label", fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
150 } 73 }
151 // close client connection 74 // close client connection
152 mc.Close() 75 mc.Close()
153 76
154 // cancel all tasks running for this session/connection 77 // cancel all tasks running for this session/connection
155 logger.Printf(logger.INFO, "[revocation:%d] Start closing session... [%d]\n", ctx.ID, ctx.Waiting()) 78 logger.Printf(logger.INFO, "[revocation:%d] Start closing session...\n", id)
156 ctx.Cancel() 79 cancel()
80}
81
82// Handle a single incoming message
83func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back service.Responder) bool {
84 // assemble log label
85 label := ""
86 if v := ctx.Value("label"); v != nil {
87 label = v.(string)
88 }
89 switch m := msg.(type) {
90 case *message.RevocationQueryMsg:
91 //----------------------------------------------------------
92 // REVOCATION_QUERY
93 //----------------------------------------------------------
94 go func(m *message.RevocationQueryMsg) {
95 logger.Printf(logger.INFO, "[revocation%s] Query request received.\n", label)
96 var resp *message.RevocationQueryResponseMsg
97 defer func() {
98 // send response
99 if resp != nil {
100 if err := back.Send(ctx, resp); err != nil {
101 logger.Printf(logger.ERROR, "[revocation%s] Failed to send response: %s\n", label, err.Error())
102 }
103 }
104 // go-routine finished
105 logger.Printf(logger.DBG, "[revocation%s] Query request finished.\n", label)
106 }()
107
108 valid, err := s.Query(ctx, m.Zone)
109 if err != nil {
110 logger.Printf(logger.ERROR, "[revocation%s] Failed to query revocation status: %s\n", label, err.Error())
111 if err == service.ErrConnectionInterrupted {
112 resp = nil
113 }
114 return
115 }
116 resp = message.NewRevocationQueryResponseMsg(valid)
117 }(m)
118
119 case *message.RevocationRevokeMsg:
120 //----------------------------------------------------------
121 // REVOCATION_REVOKE
122 //----------------------------------------------------------
123 go func(m *message.RevocationRevokeMsg) {
124 logger.Printf(logger.INFO, "[revocation%s] Revoke request received.\n", label)
125 var resp *message.RevocationRevokeResponseMsg
126 defer func() {
127 // send response
128 if resp != nil {
129 if err := back.Send(ctx, resp); err != nil {
130 logger.Printf(logger.ERROR, "[revocation%s] Failed to send response: %s\n", label, err.Error())
131 }
132 }
133 // go-routine finished
134 logger.Printf(logger.DBG, "[revocation%s] Revoke request finished.\n", label)
135 }()
136
137 rd := NewRevDataFromMsg(m)
138 valid, err := s.Revoke(ctx, rd)
139 if err != nil {
140 logger.Printf(logger.ERROR, "[revocation%s] Failed to revoke key: %s\n", label, err.Error())
141 if err == service.ErrConnectionInterrupted {
142 resp = nil
143 }
144 return
145 }
146 resp = message.NewRevocationRevokeResponseMsg(valid)
147 }(m)
148
149 default:
150 //----------------------------------------------------------
151 // UNKNOWN message type received
152 //----------------------------------------------------------
153 logger.Printf(logger.ERROR, "[revocation%s] Unhandled message of type (%d)\n", label, msg.Header().MsgType)
154 return false
155 }
156 return true
157} 157}
diff --git a/src/gnunet/service/service.go b/src/gnunet/service/service.go
index c996e0c..32ccf67 100644
--- a/src/gnunet/service/service.go
+++ b/src/gnunet/service/service.go
@@ -1,5 +1,5 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang. 1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y< 2// Copyright (C) 2019-2022 Bernd Fix >Y<
3// 3//
4// gnunet-go is free software: you can redistribute it and/or modify it 4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published 5// under the terms of the GNU Affero General Public License as published
@@ -19,145 +19,133 @@
19package service 19package service
20 20
21import ( 21import (
22 "context"
23 "errors"
22 "fmt" 24 "fmt"
23 "net/http" 25 "gnunet/message"
24 "sync" 26 "gnunet/util"
25
26 "gnunet/transport"
27 27
28 "github.com/bfix/gospel/logger" 28 "github.com/bfix/gospel/logger"
29) 29)
30 30
31// Module is an interface for GNUnet service modules (workers). 31//----------------------------------------------------------------------
32type Module interface { 32
33 // RPC returns the route and handler for JSON-RPC requests 33// Responder is a back-channel for messages generated during
34 RPC() (string, func(http.ResponseWriter, *http.Request)) 34// message processing. The Connection type is a responder
35// and used as such in ServeClient().
36type Responder interface {
37 // Handle outgoing message
38 Send(ctx context.Context, msg message.Message) error
39}
40
41// TransportResponder is used as a responder in message handling for
42// messages received from Transport.
43type TransportResponder struct {
44 Peer *util.PeerID
45 SendFcn func(context.Context, *util.PeerID, message.Message) error
35} 46}
36 47
37// Service is an interface for GNUnet services. Every service has one channel 48// Send a message back to caller.
38// end-point it listens to for incoming channel requests (network-based 49func (r *TransportResponder) Send(ctx context.Context, msg message.Message) error {
39// channels established by service clients). The end-point is specified in 50 if r.SendFcn == nil {
40// Channel semantics in the specification string. 51 return errors.New("no send function defined")
52 }
53 return r.SendFcn(ctx, r.Peer, msg)
54}
55
56//----------------------------------------------------------------------
57
58// Service is an interface for GNUnet services
41type Service interface { 59type Service interface {
42 Module 60 Module
43 // Start a service on the given endpoint 61
44 Start(spec string) error 62 // Serve a client session: A service has a socket it listens to for
45 // Serve a client session 63 // incoming connections (sessions) which are used for message exchange
46 ServeClient(ctx *SessionContext, ch *transport.MsgChannel) 64 // with local GNUnet services or clients.
47 // Stop the service 65 ServeClient(ctx context.Context, id int, mc *Connection)
48 Stop() error 66
67 // Handle a single incoming message (either locally from a socket
68 // connection or from Transport). Response messages can be send
69 // via a Responder. Returns true if message was processed.
70 HandleMessage(ctx context.Context, msg message.Message, resp Responder) bool
49} 71}
50 72
51// Impl is an implementation of generic service functionality. 73// SocketHandler handles incoming connections on the local service socket.
52type Impl struct { 74// It delegates calls to ServeClient() and HandleMessage() methods
53 impl Service // Specific service implementation 75// to a custom service 'srv'.
54 hdlr chan transport.Channel // Channel from listener 76type SocketHandler struct {
55 ctrl chan bool // Control channel 77 srv Service // Specific service implementation
56 drop chan int // Channel to drop a session from pending list 78 hdlr chan *Connection // handler for incoming connections
57 srvc transport.ChannelServer // multi-user service 79 cmgr *ConnectionManager // manager for client connections
58 wg *sync.WaitGroup // wait group for go routine synchronization 80 name string // service name
59 name string // service name
60 running bool // service currently running?
61 pending map[int]*SessionContext // list of pending sessions
62} 81}
63 82
64// NewServiceImpl instantiates a new ServiceImpl object. 83// NewSocketHandler instantiates a new socket handler.
65func NewServiceImpl(name string, srv Service) *Impl { 84func NewSocketHandler(name string, srv Service) *SocketHandler {
66 return &Impl{ 85 return &SocketHandler{
67 impl: srv, 86 srv: srv,
68 hdlr: make(chan transport.Channel), 87 hdlr: make(chan *Connection),
69 ctrl: make(chan bool), 88 cmgr: nil,
70 drop: make(chan int), 89 name: name,
71 srvc: nil,
72 wg: new(sync.WaitGroup),
73 name: name,
74 running: false,
75 pending: make(map[int]*SessionContext),
76 } 90 }
77} 91}
78 92
79// Start a service 93// Start the socket handler by listening on a Unix domain socket specified
80func (si *Impl) Start(spec string) (err error) { 94// by its path and additional parameters. Incoming connections from clients
95// are dispatched to 'hdlr'. Stopped socket handlers can be re-started.
96func (h *SocketHandler) Start(ctx context.Context, path string, params map[string]string) (err error) {
81 // check if we are already running 97 // check if we are already running
82 if si.running { 98 if h.cmgr != nil {
83 logger.Printf(logger.ERROR, "Service '%s' already running.\n", si.name) 99 logger.Printf(logger.ERROR, "Service '%s' already running.\n", h.name)
84 return fmt.Errorf("service already running") 100 return fmt.Errorf("service already running")
85 } 101 }
86 102 // start connection manager
87 // start channel server 103 logger.Printf(logger.INFO, "[%s] Service starting.\n", h.name)
88 logger.Printf(logger.INFO, "[%s] Service starting.\n", si.name) 104 if h.cmgr, err = NewConnectionManager(ctx, path, params, h.hdlr); err != nil {
89 if si.srvc, err = transport.NewChannelServer(spec, si.hdlr); err != nil {
90 return 105 return
91 } 106 }
92 si.running = true
93 107
94 // handle clients 108 // handle client connections
95 si.wg.Add(1)
96 go func() { 109 go func() {
97 defer si.wg.Done()
98 loop: 110 loop:
99 for si.running { 111 for {
100 select { 112 select {
101 113
102 // handle incoming connections 114 // handle incoming connection
103 case in := <-si.hdlr: 115 case conn := <-h.hdlr:
104 if in == nil { 116 // run a new session with context
105 logger.Printf(logger.INFO, "[%s] Listener terminated.\n", si.name) 117 id := util.NextID()
106 break loop 118 logger.Printf(logger.INFO, "[%s] Session '%d' started.\n", h.name, id)
107 } 119
108 switch ch := in.(type) { 120 go func() {
109 case transport.Channel: 121 // serve client on the message channel
110 // run a new session with context 122 h.srv.ServeClient(ctx, id, conn)
111 ctx := NewSessionContext() 123 // session is done now.
112 sessID := ctx.ID 124 logger.Printf(logger.INFO, "[%s] Session with client '%d' ended.\n", h.name, id)
113 si.pending[sessID] = ctx 125 }()
114 logger.Printf(logger.INFO, "[%s] Session '%d' started.\n", si.name, sessID) 126
115 127 // handle termination
116 go func() { 128 case <-ctx.Done():
117 // serve client on the message channel 129 logger.Printf(logger.INFO, "[%s] Listener terminated.\n", h.name)
118 si.impl.ServeClient(ctx, transport.NewMsgChannel(ch))
119 // session is done now.
120 logger.Printf(logger.INFO, "[%s] Session with client '%d' ended.\n", si.name, sessID)
121 si.drop <- sessID
122 }()
123 }
124
125 // handle session removal
126 case sessID := <-si.drop:
127 delete(si.pending, sessID)
128
129 // handle cancelation signal on listener.
130 case <-si.ctrl:
131 break loop 130 break loop
132 } 131 }
133 } 132 }
134 133
135 // terminate pending sessions
136 for _, ctx := range si.pending {
137 logger.Printf(logger.DBG, "[%s] Session '%d' closing...\n", si.name, ctx.ID)
138 ctx.Cancel()
139 }
140
141 // close-down service 134 // close-down service
142 logger.Printf(logger.INFO, "[%s] Service closing.\n", si.name) 135 logger.Printf(logger.INFO, "[%s] Service closing.\n", h.name)
143 si.srvc.Close() 136 h.cmgr.Close()
144 si.running = false
145 }() 137 }()
146 138 return nil
147 return si.impl.Start(spec)
148} 139}
149 140
150// Stop a service 141// Stop socket handler.
151func (si *Impl) Stop() error { 142func (h *SocketHandler) Stop() error {
152 if !si.running { 143 if h.cmgr == nil {
153 logger.Printf(logger.WARN, "Service '%s' not running.\n", si.name) 144 logger.Printf(logger.WARN, "Service '%s' not running.\n", h.name)
154 return fmt.Errorf("service not running") 145 return fmt.Errorf("service not running")
155 } 146 }
156 si.running = false 147 logger.Printf(logger.INFO, "[%s] Service terminating.\n", h.name)
157 si.ctrl <- true 148 h.cmgr.Close()
158 logger.Printf(logger.INFO, "[%s] Service terminating.\n", si.name) 149 h.cmgr = nil
159 150 return nil
160 err := si.impl.Stop()
161 si.wg.Wait()
162 return err
163} 151}
diff --git a/src/gnunet/service/store.go b/src/gnunet/service/store.go
new file mode 100644
index 0000000..1e5af8b
--- /dev/null
+++ b/src/gnunet/service/store.go
@@ -0,0 +1,379 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package service
20
21import (
22 "context"
23 "database/sql"
24 "encoding/binary"
25 "encoding/hex"
26 "errors"
27 "fmt"
28 "gnunet/crypto"
29 "gnunet/service/dht/blocks"
30 "gnunet/util"
31 "io/ioutil"
32 "os"
33 "strconv"
34 "strings"
35
36 redis "github.com/go-redis/redis/v8"
37)
38
39// Error messages related to the key/value-store implementations
40var (
41 ErrStoreInvalidSpec = fmt.Errorf("Invalid Store specification")
42 ErrStoreUnknown = fmt.Errorf("Unknown Store type")
43 ErrStoreNotAvailable = fmt.Errorf("Store not available")
44)
45
46//------------------------------------------------------------
47// Generic storage interface. Can be used for persistent or
48// transient (caching) storage of key/value data.
49// One set of methods (Get/Put) work on DHT queries and blocks,
50// the other set (GetS, PutS) work on key/value strings.
51// Each custom implementation can decide which sets to support.
52//------------------------------------------------------------
53
54// Store is a key/value storage where the type of the key is either
55// a SHA512 hash value or a string and the value is either a DHT
56// block or a string.
57type Store[K, V any] interface {
58 // Put block into storage under given key
59 Put(key K, val V) error
60
61 // Get block with given key from storage
62 Get(key K) (V, error)
63
64 // List all store queries
65 List() ([]K, error)
66}
67
68//------------------------------------------------------------
69// Types for custom store requirements
70//------------------------------------------------------------
71
72// DHTStore for DHT queries and blocks
73type DHTStore Store[blocks.Query, blocks.Block]
74
75// KVStore for key/value string pairs
76type KVStore Store[string, string]
77
78//------------------------------------------------------------
79// NewDHTStore creates a new storage handler with given spec
80// for use with DHT queries and blocks
81func NewDHTStore(spec string) (DHTStore, error) {
82 specs := strings.SplitN(spec, ":", 2)
83 if len(specs) < 2 {
84 return nil, ErrStoreInvalidSpec
85 }
86 switch specs[0] {
87 //------------------------------------------------------------------
88 // File-base storage
89 //------------------------------------------------------------------
90 case "file_store":
91 return NewFileStore(specs[1])
92 case "file_cache":
93 if len(specs) < 3 {
94 return nil, ErrStoreInvalidSpec
95 }
96 return NewFileCache(specs[1], specs[2])
97 }
98 return nil, ErrStoreUnknown
99}
100
101//------------------------------------------------------------
102// NewKVStore creates a new storage handler with given spec
103// for use with key/value string pairs.
104func NewKVStore(spec string) (KVStore, error) {
105 specs := strings.SplitN(spec, ":", 2)
106 if len(specs) < 2 {
107 return nil, ErrStoreInvalidSpec
108 }
109 switch specs[0] {
110 //--------------------------------------------------------------
111 // Redis service
112 //--------------------------------------------------------------
113 case "redis":
114 if len(specs) < 4 {
115 return nil, ErrStoreInvalidSpec
116 }
117 return NewRedisStore(specs[1], specs[2], specs[3])
118
119 //--------------------------------------------------------------
120 // SQL database service
121 //--------------------------------------------------------------
122 case "sql":
123 if len(specs) < 4 {
124 return nil, ErrStoreInvalidSpec
125 }
126 return NewSQLStore(specs[1])
127 }
128 return nil, errors.New("unknown storage mechanism")
129}
130
131//------------------------------------------------------------
132// Filesystem-based storage
133//------------------------------------------------------------
134
135// FileStore implements a filesystem-based storage mechanism for
136// DHT queries and blocks.
137type FileStore struct {
138 path string // storage path
139 cached []*crypto.HashCode // list of cached entries (key)
140 wrPos int // write position in cyclic list
141}
142
143// NewFileStore instantiates a new file storage.
144func NewFileStore(path string) (DHTStore, error) {
145 // create file store
146 return &FileStore{
147 path: path,
148 }, nil
149}
150
151// NewFileCache instantiates a new file-based cache.
152func NewFileCache(path, param string) (DHTStore, error) {
153 // remove old cache content
154 os.RemoveAll(path)
155
156 // get number of cache entries
157 num, err := strconv.ParseUint(param, 10, 32)
158 if err != nil {
159 return nil, err
160 }
161 // create file store
162 return &FileStore{
163 path: path,
164 cached: make([]*crypto.HashCode, num),
165 wrPos: 0,
166 }, nil
167}
168
169// Put block into storage under given key
170func (s *FileStore) Put(query blocks.Query, block blocks.Block) (err error) {
171 // get query parameters for entry
172 var (
173 btype uint16 // block type
174 expire util.AbsoluteTime // block expiration
175 )
176 query.Get("blkType", &btype)
177 query.Get("expire", &expire)
178
179 // are we in caching mode?
180 if s.cached != nil {
181 // release previous block if defined
182 if key := s.cached[s.wrPos]; key != nil {
183 // get path and filename from key
184 path, fname := s.expandPath(key)
185 if err = os.Remove(path + "/" + fname); err != nil {
186 return
187 }
188 // free slot
189 s.cached[s.wrPos] = nil
190 }
191 }
192 // get path and filename from key
193 path, fname := s.expandPath(query.Key())
194 // make sure the path exists
195 if err = os.MkdirAll(path, 0755); err != nil {
196 return
197 }
198 // write to file for storage
199 var fp *os.File
200 if fp, err = os.Create(path + "/" + fname); err == nil {
201 defer fp.Close()
202 // write block data
203 if err = binary.Write(fp, binary.BigEndian, btype); err == nil {
204 if err = binary.Write(fp, binary.BigEndian, expire); err == nil {
205 _, err = fp.Write(block.Data())
206 }
207 }
208 }
209 // update cache list
210 if s.cached != nil {
211 s.cached[s.wrPos] = query.Key()
212 s.wrPos = (s.wrPos + 1) % len(s.cached)
213 }
214 return
215}
216
217// Get block with given key from storage
218func (s *FileStore) Get(query blocks.Query) (block blocks.Block, err error) {
219 // get requested block type
220 var (
221 btype uint16 = blocks.DHT_BLOCK_ANY
222 blkt uint16 // actual block type
223 expire util.AbsoluteTime // expiration date
224 data []byte // block data
225 )
226 query.Get("blkType", &btype)
227
228 // get path and filename from key
229 path, fname := s.expandPath(query.Key())
230 // read file content (block data)
231 var file *os.File
232 if file, err = os.Open(path + "/" + fname); err != nil {
233 return
234 }
235 // read block data
236 if err = binary.Read(file, binary.BigEndian, &blkt); err == nil {
237 if btype != blocks.DHT_BLOCK_ANY && btype != blkt {
238 // block types not matching
239 return
240 }
241 if err = binary.Read(file, binary.BigEndian, &expire); err == nil {
242 if data, err = ioutil.ReadAll(file); err == nil {
243 block = blocks.NewGenericBlock(data)
244 }
245 }
246 }
247 return
248}
249
250// Get a list of all stored block keys (generic query).
251func (s *FileStore) List() ([]blocks.Query, error) {
252 return make([]blocks.Query, 0), nil
253}
254
255// expandPath returns the full path to the file for given key.
256func (s *FileStore) expandPath(key *crypto.HashCode) (string, string) {
257 h := hex.EncodeToString(key.Bits)
258 return fmt.Sprintf("%s/%s/%s", s.path, h[:2], h[2:4]), h[4:]
259}
260
261//------------------------------------------------------------
262// Redis: only use for caching purposes on key/value strings
263//------------------------------------------------------------
264
265// RedisStore uses a (local) Redis server for key/value storage
266type RedisStore struct {
267 client *redis.Client // client connection
268 db int // index to database
269}
270
271// NewRedisStore creates a Redis service client instance.
272func NewRedisStore(addr, passwd, db string) (s KVStore, err error) {
273 kvs := new(RedisStore)
274 if kvs.db, err = strconv.Atoi(db); err != nil {
275 err = ErrStoreInvalidSpec
276 return
277 }
278 kvs.client = redis.NewClient(&redis.Options{
279 Addr: addr,
280 Password: passwd,
281 DB: kvs.db,
282 })
283 if kvs.client == nil {
284 err = ErrStoreNotAvailable
285 }
286 s = kvs
287 return
288}
289
290// Put block into storage under given key
291func (s *RedisStore) Put(key string, value string) (err error) {
292 return s.client.Set(context.TODO(), key, value, 0).Err()
293}
294
295// Get block with given key from storage
296func (s *RedisStore) Get(key string) (value string, err error) {
297 return s.client.Get(context.TODO(), key).Result()
298}
299
300// List all keys in store
301func (s *RedisStore) List() (keys []string, err error) {
302 var (
303 crs uint64
304 segm []string
305 ctx = context.TODO()
306 )
307 keys = make([]string, 0)
308 for {
309 segm, crs, err = s.client.Scan(ctx, crs, "*", 10).Result()
310 if err != nil {
311 return
312 }
313 if crs == 0 {
314 break
315 }
316 keys = append(keys, segm...)
317 }
318 return
319}
320
321//------------------------------------------------------------
322// SQL-based key-value-store
323//------------------------------------------------------------
324
325// SQLStore for generic SQL database handling
326type SQLStore struct {
327 db *util.DbConn
328}
329
330// NewSQLStore creates a new SQL-based key/value store.
331func NewSQLStore(spec string) (s KVStore, err error) {
332 kvs := new(SQLStore)
333
334 // connect to SQL database
335 kvs.db, err = util.DbPool.Connect(spec)
336 if err != nil {
337 return nil, err
338 }
339 // get number of key/value pairs (as a check for existing table)
340 row := kvs.db.QueryRow("select count(*) from store")
341 var num int
342 if row.Scan(&num) != nil {
343 return nil, ErrStoreNotAvailable
344 }
345 return kvs, nil
346
347}
348
349// Put a key/value pair into the store
350func (s *SQLStore) Put(key string, value string) error {
351 _, err := s.db.Exec("insert into store(key,value) values(?,?)", key, value)
352 return err
353}
354
355// Get a value for a given key from store
356func (s *SQLStore) Get(key string) (value string, err error) {
357 row := s.db.QueryRow("select value from store where key=?", key)
358 err = row.Scan(&value)
359 return
360}
361
362// List all keys in store
363func (s *SQLStore) List() (keys []string, err error) {
364 var (
365 rows *sql.Rows
366 key string
367 )
368 keys = make([]string, 0)
369 rows, err = s.db.Query("select key from store")
370 if err == nil {
371 for rows.Next() {
372 if err = rows.Scan(&key); err != nil {
373 break
374 }
375 keys = append(keys, key)
376 }
377 }
378 return
379}
diff --git a/src/gnunet/test.sh b/src/gnunet/test.sh
new file mode 100755
index 0000000..78495a8
--- /dev/null
+++ b/src/gnunet/test.sh
@@ -0,0 +1,3 @@
1#!/bin/bash
2
3go test $* -gcflags "-N -l" ./...
diff --git a/src/gnunet/transport/channel.go b/src/gnunet/transport/channel.go
deleted file mode 100644
index 1632aab..0000000
--- a/src/gnunet/transport/channel.go
+++ /dev/null
@@ -1,213 +0,0 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package transport
20
21import (
22 "encoding/hex"
23 "errors"
24 "fmt"
25 "path"
26 "strings"
27
28 "gnunet/message"
29 "gnunet/util"
30
31 "github.com/bfix/gospel/concurrent"
32 "github.com/bfix/gospel/data"
33 "github.com/bfix/gospel/logger"
34)
35
36// Error codes
37var (
38 ErrChannelNotImplemented = fmt.Errorf("protocol not implemented")
39 ErrChannelNotOpened = fmt.Errorf("channel not opened")
40 ErrChannelInterrupted = fmt.Errorf("channel interrupted")
41)
42
43////////////////////////////////////////////////////////////////////////
44// CHANNEL
45
46// Channel is an abstraction for exchanging arbitrary data over various
47// transport protocols and mechanisms. They are created by clients via
48// 'NewChannel()' or by services run via 'NewChannelServer()'.
49// A string specifies the end-point of the channel:
50// "unix+/tmp/test.sock" -- for UDS channels
51// "tcp+1.2.3.4:5" -- for TCP channels
52// "udp+1.2.3.4:5" -- for UDP channels
53type Channel interface {
54 Open(spec string) error // open channel (for read/write)
55 Close() error // close open channel
56 IsOpen() bool // check if channel is open
57 Read([]byte, *concurrent.Signaller) (int, error) // read from channel
58 Write([]byte, *concurrent.Signaller) (int, error) // write to channel
59}
60
61// ChannelFactory instantiates specific Channel imülementations.
62type ChannelFactory func() Channel
63
64// Known channel implementations.
65var channelImpl = map[string]ChannelFactory{
66 "unix": NewSocketChannel,
67 "tcp": NewTCPChannel,
68 "udp": NewUDPChannel,
69}
70
71// NewChannel creates a new channel to the specified endpoint.
72// Called by a client to connect to a service.
73func NewChannel(spec string) (Channel, error) {
74 parts := strings.Split(spec, "+")
75 if fac, ok := channelImpl[parts[0]]; ok {
76 inst := fac()
77 err := inst.Open(spec)
78 return inst, err
79 }
80 return nil, ErrChannelNotImplemented
81}
82
83////////////////////////////////////////////////////////////////////////
84// CHANNEL SERVER
85
86// ChannelServer creates a listener for the specified endpoint.
87// The specification string has the same format as for Channel with slightly
88// different semantics (for TCP, and ICMP the address specifies is a mask
89// for client addresses accepted for a channel request).
90type ChannelServer interface {
91 Open(spec string, hdlr chan<- Channel) error
92 Close() error
93}
94
95// ChannelServerFactory instantiates specific ChannelServer imülementations.
96type ChannelServerFactory func() ChannelServer
97
98// Known channel server implementations.
99var channelServerImpl = map[string]ChannelServerFactory{
100 "unix": NewSocketChannelServer,
101 "tcp": NewTCPChannelServer,
102 "udp": NewUDPChannelServer,
103}
104
105// NewChannelServer creates a new channel server instance
106func NewChannelServer(spec string, hdlr chan<- Channel) (cs ChannelServer, err error) {
107 parts := strings.Split(spec, "+")
108
109 if fac, ok := channelServerImpl[parts[0]]; ok {
110 // check if the basedir for the spec exists...
111 if err = util.EnforceDirExists(path.Dir(parts[1])); err != nil {
112 return
113 }
114 // instantiate server implementation
115 cs = fac()
116 // create the domain socket and listen to it.
117 err = cs.Open(spec, hdlr)
118 return
119 }
120 return nil, ErrChannelNotImplemented
121}
122
123////////////////////////////////////////////////////////////////////////
124// MESSAGE CHANNEL
125
126// MsgChannel s a wrapper around a generic channel for GNUnet message exchange.
127type MsgChannel struct {
128 ch Channel
129 buf []byte
130}
131
132// NewMsgChannel wraps a plain Channel for GNUnet message exchange.
133func NewMsgChannel(ch Channel) *MsgChannel {
134 return &MsgChannel{
135 ch: ch,
136 buf: make([]byte, 65536),
137 }
138}
139
140// Close a MsgChannel by closing the wrapped plain Channel.
141func (c *MsgChannel) Close() error {
142 return c.ch.Close()
143}
144
145// Send a GNUnet message over a channel.
146func (c *MsgChannel) Send(msg message.Message, sig *concurrent.Signaller) error {
147 // convert message to binary data
148 data, err := data.Marshal(msg)
149 if err != nil {
150 return err
151 }
152 logger.Printf(logger.DBG, "==> %v\n", msg)
153 logger.Printf(logger.DBG, " [%s]\n", hex.EncodeToString(data))
154
155 // check message header size and packet size
156 mh, err := message.GetMsgHeader(data)
157 if err != nil {
158 return err
159 }
160 if len(data) != int(mh.MsgSize) {
161 return errors.New("send: message size mismatch")
162 }
163
164 // send packet
165 n, err := c.ch.Write(data, sig)
166 if err != nil {
167 return err
168 }
169 if n != len(data) {
170 return errors.New("incomplete send")
171 }
172 return nil
173}
174
175// Receive GNUnet messages over a plain Channel.
176func (c *MsgChannel) Receive(sig *concurrent.Signaller) (message.Message, error) {
177 // get bytes from channel
178 get := func(pos, count int) error {
179 n, err := c.ch.Read(c.buf[pos:pos+count], sig)
180 if err != nil {
181 return err
182 }
183 if n != count {
184 return errors.New("not enough bytes on network")
185 }
186 return nil
187 }
188
189 if err := get(0, 4); err != nil {
190 return nil, err
191 }
192 mh, err := message.GetMsgHeader(c.buf[:4])
193 if err != nil {
194 return nil, err
195 }
196
197 if err := get(4, int(mh.MsgSize)-4); err != nil {
198 return nil, err
199 }
200 msg, err := message.NewEmptyMessage(mh.MsgType)
201 if err != nil {
202 return nil, err
203 }
204 if msg == nil {
205 return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
206 }
207 if err = data.Unmarshal(msg, c.buf[:mh.MsgSize]); err != nil {
208 return nil, err
209 }
210 logger.Printf(logger.DBG, "<== %v\n", msg)
211 logger.Printf(logger.DBG, " [%s]\n", hex.EncodeToString(c.buf[:mh.MsgSize]))
212 return msg, nil
213}
diff --git a/src/gnunet/transport/channel_netw.go b/src/gnunet/transport/channel_netw.go
deleted file mode 100644
index c0de978..0000000
--- a/src/gnunet/transport/channel_netw.go
+++ /dev/null
@@ -1,285 +0,0 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package transport
20
21import (
22 "net"
23 "os"
24 "strconv"
25 "strings"
26
27 "github.com/bfix/gospel/concurrent"
28 "github.com/bfix/gospel/logger"
29)
30
31// ChannelResult for read/write operations on channels.
32type ChannelResult struct {
33 count int // number of bytes read/written
34 err error // error (or nil)
35}
36
37// NewChannelResult instanciates a new object with given attributes.
38func NewChannelResult(n int, err error) *ChannelResult {
39 return &ChannelResult{
40 count: n,
41 err: err,
42 }
43}
44
45// Values returns the attributes of a result instance (for passing up the
46// call stack).
47func (cr *ChannelResult) Values() (int, error) {
48 return cr.count, cr.err
49}
50
51////////////////////////////////////////////////////////////////////////
52// Generic network-based Channel
53
54// NetworkChannel represents a network-based channel
55type NetworkChannel struct {
56 network string // network protocol identifier ("tcp", "unix", ...)
57 conn net.Conn // associated connection
58}
59
60// NewNetworkChannel creates a new channel for a given network protocol.
61// The channel is in pending state and need to be opened before use.
62func NewNetworkChannel(netw string) Channel {
63 return &NetworkChannel{
64 network: netw,
65 conn: nil,
66 }
67}
68
69// Open a network channel based on specification:
70// The specification is a string separated into parts by the '+' delimiter
71// (e.g. "unix+/tmp/gnunet-service-gns-go.sock+perm=0770"). The network
72// identifier (first part) must match the network specification of the
73// underlaying NetworkChannel instance.
74func (c *NetworkChannel) Open(spec string) (err error) {
75 parts := strings.Split(spec, "+")
76 // check for correct protocol
77 if parts[0] != c.network {
78 return ErrChannelNotImplemented
79 }
80 // open connection
81 c.conn, err = net.Dial(c.network, parts[1])
82 return
83}
84
85// Close a network channel
86func (c *NetworkChannel) Close() error {
87 if c.conn != nil {
88 rc := c.conn.Close()
89 c.conn = nil
90 return rc
91 }
92 return ErrChannelNotOpened
93}
94
95// IsOpen returns true if the channel is opened
96func (c *NetworkChannel) IsOpen() bool {
97 return c.conn != nil
98}
99
100// Read bytes from a network channel into buffer: Returns the number of read
101// bytes and an error code. Only works on open channels ;)
102// The read can be aborted by sending 'true' on the cmd interface; the
103// channel is closed after such interruption.
104func (c *NetworkChannel) Read(buf []byte, sig *concurrent.Signaller) (int, error) {
105 // check if the channel is open
106 if c.conn == nil {
107 return 0, ErrChannelNotOpened
108 }
109 // perform operation in go-routine
110 result := make(chan *ChannelResult)
111 go func() {
112 result <- NewChannelResult(c.conn.Read(buf))
113 }()
114
115 listener := sig.Listen()
116 defer sig.Drop(listener)
117 for {
118 select {
119 // handle terminate command
120 case x := <-listener:
121 switch val := x.(type) {
122 case bool:
123 if val {
124 return 0, ErrChannelInterrupted
125 }
126 }
127 // handle result of read operation
128 case res := <-result:
129 return res.Values()
130 }
131 }
132}
133
134// Write buffer to a network channel: Returns the number of written bytes and
135// an error code. The write operation can be aborted by sending 'true' on the
136// command channel; the network channel is closed after such interrupt.
137func (c *NetworkChannel) Write(buf []byte, sig *concurrent.Signaller) (int, error) {
138 // check if we have an open channel to write to.
139 if c.conn == nil {
140 return 0, ErrChannelNotOpened
141 }
142 // perform operation in go-routine
143 result := make(chan *ChannelResult)
144 go func() {
145 result <- NewChannelResult(c.conn.Write(buf))
146 }()
147
148 listener := sig.Listen()
149 defer sig.Drop(listener)
150 for {
151 select {
152 // handle terminate command
153 case x := <-listener:
154 switch val := x.(type) {
155 case bool:
156 if val {
157 return 0, ErrChannelInterrupted
158 }
159 }
160 // handle result of read operation
161 case res := <-result:
162 return res.Values()
163 }
164 }
165}
166
167////////////////////////////////////////////////////////////////////////
168// Generic network-based ChannelServer
169
170// NetworkChannelServer represents a network-based channel server
171type NetworkChannelServer struct {
172 network string // network protocol to listen on
173 listener net.Listener // reference to listener object
174}
175
176// NewNetworkChannelServer creates a new network-based channel server
177func NewNetworkChannelServer(netw string) ChannelServer {
178 return &NetworkChannelServer{
179 network: netw,
180 listener: nil,
181 }
182}
183
184// Open a network channel server (= start running it) based on the given
185// specification. For every client connection to the server, the associated
186// network channel for the connection is send via the hdlr channel.
187func (s *NetworkChannelServer) Open(spec string, hdlr chan<- Channel) (err error) {
188 parts := strings.Split(spec, "+")
189 // check for correct protocol
190 if parts[0] != s.network {
191 return ErrChannelNotImplemented
192 }
193 // create listener
194 if s.listener, err = net.Listen(s.network, parts[1]); err != nil {
195 return
196 }
197 // handle additional parameters ('key[=value]')
198 for _, param := range parts[2:] {
199 frag := strings.Split(param, "=")
200 switch frag[0] {
201 case "perm": // set permissions on 'unix'
202 if s.network == "unix" {
203 if perm, err := strconv.ParseInt(frag[1], 8, 32); err == nil {
204 if err := os.Chmod(parts[1], os.FileMode(perm)); err != nil {
205 logger.Printf(
206 logger.ERROR,
207 "NetworkChannelServer: Failed to set permissions: %s\n",
208 err.Error())
209
210 }
211 } else {
212 logger.Printf(
213 logger.ERROR,
214 "NetworkChannelServer: Invalid permissions '%s'\n",
215 frag[1])
216 }
217 }
218 }
219 }
220 // run go routine to handle channel requests from clients
221 go func() {
222 for {
223 conn, err := s.listener.Accept()
224 if err != nil {
225 // signal failure and terminate
226 hdlr <- nil
227 break
228 }
229 // send channel to handler
230 hdlr <- &NetworkChannel{
231 network: s.network,
232 conn: conn,
233 }
234 }
235 if s.listener != nil {
236 s.listener.Close()
237 }
238 }()
239
240 return nil
241}
242
243// Close a network channel server (= stop the server)
244func (s *NetworkChannelServer) Close() error {
245 if s.listener != nil {
246 err := s.listener.Close()
247 s.listener = nil
248 return err
249 }
250 return nil
251}
252
253////////////////////////////////////////////////////////////////////////
254// helper functions to instantiate network channels and servers for
255// common network protocols
256
257// NewSocketChannel implements a Unix Domain Socket connection
258func NewSocketChannel() Channel {
259 return NewNetworkChannel("unix")
260}
261
262// NewTCPChannel implements a: TCP connection
263func NewTCPChannel() Channel {
264 return NewNetworkChannel("tcp")
265}
266
267// NewUDPChannel implements an UDP connection
268func NewUDPChannel() Channel {
269 return NewNetworkChannel("udp")
270}
271
272// NewSocketChannelServer implements an Unix Domain Socket listener
273func NewSocketChannelServer() ChannelServer {
274 return NewNetworkChannelServer("unix")
275}
276
277// NewTCPChannelServer implements a TCP listener
278func NewTCPChannelServer() ChannelServer {
279 return NewNetworkChannelServer("tcp")
280}
281
282// NewUDPChannelServer implements an UDP listener
283func NewUDPChannelServer() ChannelServer {
284 return NewNetworkChannelServer("udp")
285}
diff --git a/src/gnunet/transport/channel_test.go b/src/gnunet/transport/channel_test.go
deleted file mode 100644
index f028171..0000000
--- a/src/gnunet/transport/channel_test.go
+++ /dev/null
@@ -1,232 +0,0 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package transport
20
21import (
22 "bytes"
23 "fmt"
24 "testing"
25 "time"
26
27 "github.com/bfix/gospel/concurrent"
28)
29
30// TODO: These test cases fail from time to time for no obvious reason.
31// This needs to be investigated.
32
33const (
34 SockAddr = "/tmp/gnunet-go-test.sock"
35 TCPAddrClient = "gnunet.org:80"
36 TCPAddrServer = "127.0.0.1:9876"
37)
38
39type TestChannelServer struct {
40 hdlr chan Channel
41 srvc ChannelServer
42 running bool
43}
44
45func NewTestChannelServer() *TestChannelServer {
46 return &TestChannelServer{
47 hdlr: make(chan Channel),
48 srvc: nil,
49 running: false,
50 }
51}
52
53func (s *TestChannelServer) handle(ch Channel, sig *concurrent.Signaller) {
54 buf := make([]byte, 4096)
55 for {
56 n, err := ch.Read(buf, sig)
57 if err != nil {
58 break
59 }
60 _, err = ch.Write(buf[:n], sig)
61 if err != nil {
62 break
63 }
64 }
65 ch.Close()
66}
67
68func (s *TestChannelServer) Start(spec string) (err error) {
69 // check if we are already running
70 if s.running {
71 return fmt.Errorf("Server already running")
72 }
73
74 // start channel server
75 if s.srvc, err = NewChannelServer(spec, s.hdlr); err != nil {
76 return
77 }
78 s.running = true
79
80 // handle clients
81 sig := concurrent.NewSignaller()
82 go func() {
83 for s.running {
84 in := <-s.hdlr
85 if in == nil {
86 break
87 }
88 switch x := in.(type) {
89 case Channel:
90 go s.handle(x, sig)
91 }
92 }
93 s.srvc.Close()
94 s.running = false
95 }()
96 return nil
97}
98
99func (s *TestChannelServer) Stop() {
100 s.running = false
101}
102
103func TestChannelServerTCPSingle(t *testing.T) {
104 time.Sleep(time.Second)
105 s := NewTestChannelServer()
106 err := s.Start("tcp+" + TCPAddrServer)
107 defer s.Stop()
108 if err != nil {
109 t.Fatal(err)
110 }
111}
112
113func TestChannelServerTCPTwice(t *testing.T) {
114 time.Sleep(time.Second)
115 s1 := NewTestChannelServer()
116 err := s1.Start("tcp+" + TCPAddrServer)
117 defer s1.Stop()
118 if err != nil {
119 t.Fatal(err)
120 }
121 time.Sleep(time.Second)
122 s2 := NewTestChannelServer()
123 err = s2.Start("tcp+" + TCPAddrServer)
124 defer s2.Stop()
125 if err == nil {
126 t.Fatal("SocketServer started twice!!")
127 }
128}
129
130func TestChannelClientTCP(t *testing.T) {
131 time.Sleep(time.Second)
132 ch, err := NewChannel("tcp+" + TCPAddrClient)
133 if err != nil {
134 t.Fatal(err)
135 }
136 defer ch.Close()
137
138 sig := concurrent.NewSignaller()
139 msg := []byte("GET /\n\n")
140 n, err := ch.Write(msg, sig)
141 if err != nil {
142 t.Fatal(err)
143 }
144 if n != len(msg) {
145 t.Fatal("Send size mismatch")
146 }
147 buf := make([]byte, 4096)
148 n = 0
149 start := time.Now().Unix()
150 for n == 0 && (time.Now().Unix()-start) < 3 {
151 if n, err = ch.Read(buf, sig); err != nil {
152 t.Fatal(err)
153 }
154 }
155 t.Logf("'%s' [%d]\n", string(buf[:n]), n)
156}
157
158func TestChannelClientServerTCP(t *testing.T) {
159 time.Sleep(time.Second)
160 s := NewTestChannelServer()
161 err := s.Start("tcp+" + TCPAddrServer)
162 defer s.Stop()
163 if err != nil {
164 t.Fatal(err)
165 }
166
167 ch, err := NewChannel("tcp+" + TCPAddrServer)
168 if err != nil {
169 t.Fatal(err)
170 }
171 sig := concurrent.NewSignaller()
172 msg := []byte("GET /\n\n")
173 n, err := ch.Write(msg, sig)
174 if err != nil {
175 t.Fatal(err)
176 }
177 if n != len(msg) {
178 t.Fatal("Send size mismatch")
179 }
180 buf := make([]byte, 4096)
181 n = 0
182 start := time.Now().Unix()
183 for n == 0 && (time.Now().Unix()-start) < 3 {
184 if n, err = ch.Read(buf, sig); err != nil {
185 t.Fatal(err)
186 }
187 }
188 if err = ch.Close(); err != nil {
189 t.Fatal(err)
190 }
191 if !bytes.Equal(buf[:n], msg) {
192 t.Fatal("message send/receive mismatch")
193 }
194}
195
196func TestChannelClientServerSock(t *testing.T) {
197 time.Sleep(time.Second)
198 s := NewTestChannelServer()
199 if err := s.Start("unix+" + SockAddr); err != nil {
200 t.Fatal(err)
201 }
202
203 ch, err := NewChannel("unix+" + SockAddr)
204 if err != nil {
205 t.Fatal(err)
206 }
207 sig := concurrent.NewSignaller()
208 msg := []byte("This is just a test -- please ignore...")
209 n, err := ch.Write(msg, sig)
210 if err != nil {
211 t.Fatal(err)
212 }
213 if n != len(msg) {
214 t.Fatal("Send size mismatch")
215 }
216 buf := make([]byte, 4096)
217 n = 0
218 start := time.Now().Unix()
219 for n == 0 && (time.Now().Unix()-start) < 3 {
220 if n, err = ch.Read(buf, sig); err != nil {
221 t.Fatal(err)
222 }
223 }
224 if err = ch.Close(); err != nil {
225 t.Fatal(err)
226 }
227 if !bytes.Equal(buf[:n], msg) {
228 t.Fatal("message send/receive mismatch")
229 }
230
231 s.Stop()
232}
diff --git a/src/gnunet/transport/connection.go b/src/gnunet/transport/connection.go
index cc2c909..8337260 100644
--- a/src/gnunet/transport/connection.go
+++ b/src/gnunet/transport/connection.go
@@ -1,5 +1,5 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang. 1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y< 2// Copyright (C) 2019-2022 Bernd Fix >Y<
3// 3//
4// gnunet-go is free software: you can redistribute it and/or modify it 4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published 5// under the terms of the GNU Affero General Public License as published
@@ -19,59 +19,97 @@
19package transport 19package transport
20 20
21import ( 21import (
22 "gnunet/core" 22 "context"
23 "errors"
23 "gnunet/message" 24 "gnunet/message"
25 "net"
26)
24 27
25 "github.com/bfix/gospel/concurrent" 28// Error codes
29var (
30 ErrConnectionNotOpened = errors.New("connection not opened")
31 ErrConnectionInterrupted = errors.New("connection interrupted")
26) 32)
27 33
28// Connection for communicating peers 34//----------------------------------------------------------------------
35
36// Connection is a net.Conn for GNUnet message exchange (send/receive)
29type Connection struct { 37type Connection struct {
30 from, to *core.Peer 38 conn net.Conn // associated connection
31 ch *MsgChannel 39 buf []byte // read/write buffer
32 bandwidth uint32
33 state int
34 shared []byte
35} 40}
36 41
37// NewConnection instanciates a new connection between peers communicating 42// NewConnection creates a new connection from an existing net.Conn
38// over a message channel (Connections are authenticated and secured). 43// This is usually used by clients to connect to a service.
39func NewConnection(ch *MsgChannel, from, to *core.Peer) *Connection { 44func NewConnection(ctx context.Context, conn net.Conn) *Connection {
40 return &Connection{ 45 return &Connection{
41 from: from, 46 conn: conn,
42 to: to, 47 buf: make([]byte, 65536),
43 state: 1,
44 ch: ch,
45 } 48 }
46} 49}
47 50
48// SharedSecret computes the shared secret the two endpoints of a connection. 51// Close connection
49func (c *Connection) SharedSecret(secret []byte) { 52func (s *Connection) Close() error {
50 c.shared = make([]byte, len(secret)) 53 if s.conn != nil {
51 copy(c.shared, secret) 54 rc := s.conn.Close()
55 s.conn = nil
56 return rc
57 }
58 return ErrConnectionNotOpened
52} 59}
53 60
54// GetState returns the current state of the connection. 61// Send a GNUnet message over connection
55func (c *Connection) GetState() int { 62func (s *Connection) Send(ctx context.Context, msg message.Message) error {
56 return c.state 63 return WriteMessage(ctx, s.conn, msg)
57} 64}
58 65
59// SetBandwidth to control transfer rates on the connection 66// Receive GNUnet messages from socket.
60func (c *Connection) SetBandwidth(bw uint32) { 67func (s *Connection) Receive(ctx context.Context) (message.Message, error) {
61 c.bandwidth = bw 68 return ReadMessage(ctx, s.conn, s.buf)
62} 69}
63 70
64// Close connection between two peers. 71//----------------------------------------------------------------------
65func (c *Connection) Close() error { 72
66 return c.ch.Close() 73// ConnectionManager handles client connections on a net.Listener
74type ConnectionManager struct {
75 listener net.Listener // reference to listener object
67} 76}
68 77
69// Send a message on the connection 78// NewConnectionManager creates a new net.Listener connection manager.
70func (c *Connection) Send(msg message.Message, sig *concurrent.Signaller) error { 79// Incoming connections from clients are dispatched to a handler channel.
71 return c.ch.Send(msg, sig) 80func NewConnectionManager(ctx context.Context, listener net.Listener, hdlr chan *Connection) (cs *ConnectionManager, err error) {
81 // instantiate connection manager
82 cs = &ConnectionManager{
83 listener: listener,
84 }
85 // run watch dog for termination
86 go func() {
87 <-ctx.Done()
88 cs.listener.Close()
89 }()
90 // run go routine to handle channel requests from clients
91 go func() {
92 for {
93 conn, err := cs.listener.Accept()
94 if err != nil {
95 return
96 }
97 // handle connection
98 c := &Connection{
99 conn: conn,
100 buf: make([]byte, 65536),
101 }
102 hdlr <- c
103 }
104 }()
105 return cs, nil
72} 106}
73 107
74// Receive a message on the connection 108// Close a connection manager (= stop the server)
75func (c *Connection) Receive(sig *concurrent.Signaller) (message.Message, error) { 109func (s *ConnectionManager) Close() (err error) {
76 return c.ch.Receive(sig) 110 if s.listener != nil {
111 err = s.listener.Close()
112 s.listener = nil
113 }
114 return
77} 115}
diff --git a/src/gnunet/transport/endpoint.go b/src/gnunet/transport/endpoint.go
new file mode 100644
index 0000000..b54ee4e
--- /dev/null
+++ b/src/gnunet/transport/endpoint.go
@@ -0,0 +1,282 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package transport
20
21import (
22 "bytes"
23 "context"
24 "errors"
25 "gnunet/message"
26 "gnunet/util"
27 "net"
28)
29
30var (
31 ErrEndpNotAvailable = errors.New("no endpoint for address available")
32 ErrEndpProtocolMismatch = errors.New("transport protocol mismatch")
33)
34
35// Endpoint represents a local endpoint that can send and receive messages.
36// Implementations need to manage the relations between peer IDs and
37// remote endpoints for TCP and UDP traffic.
38type Endpoint interface {
39 // Run the endpoint and send received messages to channel
40 Run(context.Context, chan *TransportMessage) error
41
42 // Send message on endpoint
43 Send(context.Context, net.Addr, *TransportMessage) error
44
45 // Address returns the listening address for the endpoint
46 Address() net.Addr
47
48 // CanSendTo returns true if the endpoint can sent to address
49 CanSendTo(net.Addr) bool
50
51 // Return endpoint identifier
52 ID() int
53}
54
55//----------------------------------------------------------------------
56
57// NewEndpoint returns a suitable endpoint for the address.
58func NewEndpoint(addr net.Addr) (ep Endpoint, err error) {
59 switch epMode(addr.Network()) {
60 case "packet":
61 ep, err = newPacketEndpoint(addr)
62 case "stream":
63 ep, err = newStreamEndpoint(addr)
64 default:
65 err = ErrEndpNotAvailable
66 }
67 return
68}
69
70//----------------------------------------------------------------------
71// Packet-oriented endpoint
72//----------------------------------------------------------------------
73
74// PacketEndpoint for packet-oriented network protocols
75type PaketEndpoint struct {
76 id int // endpoint identifier
77 addr net.Addr // endpoint address
78 conn net.PacketConn // packet connection
79 buf []byte // buffer for read/write operations
80}
81
82// Run packet endpoint: send incoming messages to the handler.
83func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan *TransportMessage) (err error) {
84 // create listener
85 var lc net.ListenConfig
86 if ep.conn, err = lc.ListenPacket(ctx, ep.addr.Network(), ep.addr.String()); err != nil {
87 return
88 }
89
90 // run watch dog for termination
91 go func() {
92 <-ctx.Done()
93 ep.conn.Close()
94 }()
95 // run go routine to handle messages from clients
96 go func() {
97 for {
98 // read next message from packet
99 n, _, err := ep.conn.ReadFrom(ep.buf)
100 if err != nil {
101 break
102 }
103 rdr := bytes.NewBuffer(util.Clone(ep.buf[:n]))
104 msg, err := ReadMessageDirect(rdr, ep.buf)
105 if err != nil {
106 break
107 }
108 // check for transport message
109 if msg.Header().MsgType == message.DUMMY {
110 // set transient attributes
111 tm := msg.(*TransportMessage)
112 tm.endp = ep.id
113 tm.conn = 0
114 // send to handler
115 go func() {
116 hdlr <- tm
117 }()
118 }
119 }
120 // connection ended.
121 ep.conn.Close()
122 }()
123 return
124}
125
126// Send message to address from endpoint
127func (ep *PaketEndpoint) Send(ctx context.Context, addr net.Addr, msg *TransportMessage) (err error) {
128 var a *net.UDPAddr
129 a, err = net.ResolveUDPAddr(addr.Network(), addr.String())
130 var buf []byte
131 if buf, err = msg.Bytes(); err != nil {
132 return
133 }
134 _, err = ep.conn.WriteTo(buf, a)
135 return
136}
137
138// Address returms the
139func (ep *PaketEndpoint) Address() net.Addr {
140 if ep.conn != nil {
141 return ep.conn.LocalAddr()
142 }
143 return ep.addr
144}
145
146// CanSendTo returns true if the endpoint can sent to address
147func (ep *PaketEndpoint) CanSendTo(addr net.Addr) bool {
148 return epMode(addr.Network()) == "packet"
149}
150
151// ID returns the endpoint identifier
152func (ep *PaketEndpoint) ID() int {
153 return ep.id
154}
155
156func newPacketEndpoint(addr net.Addr) (ep *PaketEndpoint, err error) {
157 // check for matching protocol
158 if epMode(addr.Network()) != "packet" {
159 err = ErrEndpProtocolMismatch
160 return
161 }
162 // create endpoint
163 ep = &PaketEndpoint{
164 id: util.NextID(),
165 addr: addr,
166 buf: make([]byte, 65536),
167 }
168 return
169}
170
171//----------------------------------------------------------------------
172// Stream-oriented endpoint
173//----------------------------------------------------------------------
174
175// StreamEndpoint for stream-oriented network protocols
176type StreamEndpoint struct {
177 id int // endpoint identifier
178 addr net.Addr // listening address
179 listener net.Listener // listener instance
180 conns *util.Map[int, net.Conn] // active connections
181 buf []byte // read/write buffer
182}
183
184// Run packet endpoint: send incoming messages to the handler.
185func (ep *StreamEndpoint) Run(ctx context.Context, hdlr chan *TransportMessage) (err error) {
186 // create listener
187 var lc net.ListenConfig
188 if ep.listener, err = lc.Listen(ctx, ep.addr.Network(), ep.addr.String()); err != nil {
189 return
190 }
191 // run watch dog for termination
192 go func() {
193 <-ctx.Done()
194 ep.listener.Close()
195 }()
196 // run go routine to handle messages from clients
197 go func() {
198 for {
199 // get next client connection
200 conn, err := ep.listener.Accept()
201 if err != nil {
202 return
203 }
204 session := util.NextID()
205 ep.conns.Put(session, conn)
206 go func() {
207 for {
208 // read next message from connection
209 msg, err := ReadMessage(ctx, conn, ep.buf)
210 if err != nil {
211 break
212 }
213 // check for transport message
214 if msg.Header().MsgType == message.DUMMY {
215 // set transient attributes
216 tm := msg.(*TransportMessage)
217 tm.endp = ep.id
218 tm.conn = session
219 // send to handler
220 go func() {
221 hdlr <- tm
222 }()
223 }
224 }
225 // connection ended.
226 conn.Close()
227 ep.conns.Delete(session)
228 }()
229 }
230 }()
231 return
232}
233
234// Send message to address from endpoint
235func (ep *StreamEndpoint) Send(ctx context.Context, addr net.Addr, msg *TransportMessage) error {
236 return nil
237}
238
239// Address returns the actual listening endpoint address
240func (ep *StreamEndpoint) Address() net.Addr {
241 if ep.listener != nil {
242 return ep.listener.Addr()
243 }
244 return ep.addr
245}
246
247// CanSendTo returns true if the endpoint can sent to address
248func (ep *StreamEndpoint) CanSendTo(addr net.Addr) bool {
249 return epMode(addr.Network()) == "stream"
250}
251
252// ID returns the endpoint identifier
253func (ep *StreamEndpoint) ID() int {
254 return ep.id
255}
256
257func newStreamEndpoint(addr net.Addr) (ep *StreamEndpoint, err error) {
258 // check for matching protocol
259 if epMode(addr.Network()) != "stream" {
260 err = ErrEndpProtocolMismatch
261 return
262 }
263 // create endpoint
264 ep = &StreamEndpoint{
265 id: util.NextID(),
266 addr: addr,
267 conns: util.NewMap[int, net.Conn](),
268 buf: make([]byte, 65536),
269 }
270 return
271}
272
273// epMode returns the endpoint mode (packet or stream) for a given network
274func epMode(netw string) string {
275 switch netw {
276 case "udp", "udp4", "udp6", "r5n+ip+udp":
277 return "packet"
278 case "tcp", "unix":
279 return "stream"
280 }
281 return ""
282}
diff --git a/src/gnunet/transport/reader_writer.go b/src/gnunet/transport/reader_writer.go
new file mode 100644
index 0000000..2e5f14a
--- /dev/null
+++ b/src/gnunet/transport/reader_writer.go
@@ -0,0 +1,157 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package transport
20
21import (
22 "context"
23 "errors"
24 "fmt"
25 "gnunet/message"
26 "io"
27
28 "github.com/bfix/gospel/data"
29)
30
31// WriteMessageDirect writes directly to io.Writer
32func WriteMessageDirect(wrt io.Writer, msg message.Message) error {
33 dwc := &directWriteCloser{wrt}
34 return WriteMessage(context.Background(), dwc, msg)
35}
36
37// WriteMessage to io.WriteCloser
38func WriteMessage(ctx context.Context, wrt io.WriteCloser, msg message.Message) (err error) {
39 // convert message to binary data
40 var buf []byte
41 if buf, err = data.Marshal(msg); err != nil {
42 return err
43 }
44 // check message header size and packet size
45 mh, err := message.GetMsgHeader(buf)
46 if err != nil {
47 return err
48 }
49 if len(buf) != int(mh.MsgSize) {
50 return errors.New("WriteMessage: message size mismatch")
51 }
52 // watch dog for write operation
53 go func() {
54 for {
55 select {
56 case <-ctx.Done():
57 wrt.Close()
58 }
59 }
60 }()
61 // perform write operation
62 var n int
63 if n, err = wrt.Write(buf); err != nil {
64 return
65 }
66 if n != len(buf) {
67 err = fmt.Errorf("WriteMessage incomplete (%d of %d)", n, len(buf))
68 }
69 return
70}
71
72//----------------------------------------------------------------------
73
74// ReadMessageDirect reads directly from io.Reader
75func ReadMessageDirect(rdr io.Reader, buf []byte) (msg message.Message, err error) {
76 drc := &directReadCloser{
77 rdr: rdr,
78 }
79 return ReadMessage(context.Background(), drc, buf)
80}
81
82// ReadMessage from io.ReadCloser
83func ReadMessage(ctx context.Context, rdr io.ReadCloser, buf []byte) (msg message.Message, err error) {
84 // watch dog for write operation
85 go func() {
86 for {
87 select {
88 case <-ctx.Done():
89 rdr.Close()
90 }
91 }
92 }()
93 // get bytes from reader
94 if buf == nil {
95 buf = make([]byte, 65536)
96 }
97 get := func(pos, count int) error {
98 n, err := rdr.Read(buf[pos : pos+count])
99 if err == nil && n != count {
100 err = fmt.Errorf("not enough bytes on reader (%d of %d)", n, count)
101 }
102 return err
103 }
104 // read header first
105 if err := get(0, 4); err != nil {
106 return nil, err
107 }
108 var mh *message.Header
109 if mh, err = message.GetMsgHeader(buf[:4]); err != nil {
110 return nil, err
111 }
112 // get rest of message
113 if err = get(4, int(mh.MsgSize)-4); err != nil {
114 return nil, err
115 }
116 // handle transport message case
117 if mh.MsgType == message.DUMMY {
118 msg = NewTransportMessage(nil, nil)
119 } else if msg, err = message.NewEmptyMessage(mh.MsgType); err != nil {
120 return nil, err
121 }
122 if msg == nil {
123 return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
124 }
125 if err = data.Unmarshal(msg, buf[:mh.MsgSize]); err != nil {
126 return nil, err
127 }
128 return msg, nil
129}
130
131//----------------------------------------------------------------------
132// helper for wrapped ReadCloser/WriteCloser (close is nop)
133//----------------------------------------------------------------------
134
135type directReadCloser struct {
136 rdr io.Reader
137}
138
139func (drc *directReadCloser) Read(buf []byte) (int, error) {
140 return drc.rdr.Read(buf)
141}
142
143func (drc *directReadCloser) Close() error {
144 return nil
145}
146
147type directWriteCloser struct {
148 wrt io.Writer
149}
150
151func (dwc *directWriteCloser) Write(buf []byte) (int, error) {
152 return dwc.wrt.Write(buf)
153}
154
155func (dwc *directWriteCloser) Close() error {
156 return nil
157}
diff --git a/src/gnunet/transport/transport.go b/src/gnunet/transport/transport.go
new file mode 100644
index 0000000..14def98
--- /dev/null
+++ b/src/gnunet/transport/transport.go
@@ -0,0 +1,151 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package transport
20
21import (
22 "bytes"
23 "context"
24 "errors"
25 "gnunet/message"
26 "gnunet/util"
27 "net"
28)
29
30// Trnsport layer error codes
31var (
32 ErrTransNoEndpoint = errors.New("no matching endpoint found")
33)
34
35//======================================================================
36// Network-oriented transport implementation
37//======================================================================
38
39// TransportMessage is the unit processed by the transport mechanism.
40// Peer refers to the remote endpoint (sender/receiver) and
41// Msg is the exchanged GNUnet message. The packet itself satisfies the
42// message.Message interface.
43type TransportMessage struct {
44 Hdr *message.Header `` // message header
45 Peer *util.PeerID `` // remote peer
46 Payload []byte `size:"*"` // GNUnet message
47
48 // package-local attributes (transient)
49 msg message.Message
50 endp int // id of endpoint (incoming message)
51 conn int // id of connection (optional, incoming message)
52}
53
54func (msg *TransportMessage) Header() *message.Header {
55 return msg.Hdr
56}
57
58func (msg *TransportMessage) Message() (m message.Message, err error) {
59 if m = msg.msg; m == nil {
60 rdr := bytes.NewBuffer(msg.Payload)
61 m, err = ReadMessageDirect(rdr, nil)
62 }
63 return
64}
65
66// Bytes returns the binary representation of a transport message
67func (msg *TransportMessage) Bytes() ([]byte, error) {
68 buf := new(bytes.Buffer)
69 err := WriteMessageDirect(buf, msg)
70 return buf.Bytes(), err
71}
72
73// String returns the message in human-readable form
74func (msg *TransportMessage) String() string {
75 return "TransportMessage{...}"
76}
77
78// NewTransportMessage creates a message suitable for transfer
79func NewTransportMessage(peer *util.PeerID, payload []byte) (tm *TransportMessage) {
80 if peer == nil {
81 peer = util.NewPeerID(nil)
82 }
83 msize := 0
84 if payload != nil {
85 msize = len(payload)
86 }
87 tm = &TransportMessage{
88 Hdr: &message.Header{
89 MsgSize: uint16(36 + msize),
90 MsgType: message.DUMMY,
91 },
92 Peer: peer,
93 Payload: payload,
94 }
95 return
96}
97
98//----------------------------------------------------------------------
99
100// Transport enables network-oriented (like IP, UDP, TCP or UDS)
101// message exchange on multiple endpoints.
102type Transport struct {
103 incoming chan *TransportMessage // messages as received from the network
104 endpoints map[int]Endpoint // list of available endpoints
105}
106
107// NewTransport creates and runs a new transport layer implementation.
108func NewTransport(ctx context.Context, ch chan *TransportMessage) (t *Transport) {
109 // create transport instance
110 return &Transport{
111 incoming: ch,
112 endpoints: make(map[int]Endpoint),
113 }
114}
115
116// Send a message over suitable endpoint
117func (t *Transport) Send(ctx context.Context, addr net.Addr, msg *TransportMessage) (err error) {
118 for _, ep := range t.endpoints {
119 if ep.CanSendTo(addr) {
120 err = ep.Send(ctx, addr, msg)
121 break
122 }
123 }
124 return
125}
126
127//----------------------------------------------------------------------
128// Endpoint handling
129//----------------------------------------------------------------------
130
131// AddEndpoint instantiates and run a new endpoint handler for the
132// given address (must map to a network interface).
133func (t *Transport) AddEndpoint(ctx context.Context, addr net.Addr) (a net.Addr, err error) {
134 // register endpoint
135 var ep Endpoint
136 if ep, err = NewEndpoint(addr); err != nil {
137 return
138 }
139 t.endpoints[ep.ID()] = ep
140 ep.Run(ctx, t.incoming)
141 return ep.Address(), nil
142}
143
144// Endpoints returns a list of listening addresses managed by transport.
145func (t *Transport) Endpoints() (list []net.Addr) {
146 list = make([]net.Addr, 0)
147 for _, ep := range t.endpoints {
148 list = append(list, ep.Address())
149 }
150 return
151}
diff --git a/src/gnunet/util/address.go b/src/gnunet/util/address.go
index d272742..106e671 100644
--- a/src/gnunet/util/address.go
+++ b/src/gnunet/util/address.go
@@ -19,44 +19,81 @@
19package util 19package util
20 20
21import ( 21import (
22 "encoding/hex" 22 "bytes"
23 "fmt" 23 "fmt"
24 "net" 24 "net"
25 "strings"
25) 26)
26 27
27// Address specifies how a peer is reachable on the network. 28// Address specifies how a peer is reachable on the network.
28type Address struct { 29type Address struct {
29 Transport string // transport protocol 30 Netw string `` // network protocol
30 Options uint32 `order:"big"` // address options 31 Options uint32 `order:"big"` // address options
31 Address []byte `size:"*"` // address data (protocol-dependent) 32 Expires AbsoluteTime `` // expiration date for address
33 Address []byte `size:"*"` // address data (protocol-dependent)
32} 34}
33 35
34// NewAddress returns a new Address for the given transport and specs 36// NewAddress returns a new Address for the given transport and specs
35func NewAddress(transport string, addr []byte) *Address { 37func NewAddress(transport string, addr []byte) *Address {
36 a := &Address{ 38 return &Address{
37 Transport: transport, 39 Netw: transport,
38 Options: 0, 40 Options: 0,
39 Address: make([]byte, len(addr)), 41 Address: Clone(addr),
42 Expires: AbsoluteTimeNever(),
40 } 43 }
41 copy(a.Address, addr)
42 return a
43} 44}
44 45
46func NewAddressWrap(addr net.Addr) *Address {
47 return &Address{
48 Netw: addr.Network(),
49 Options: 0,
50 Address: []byte(addr.String()),
51 Expires: AbsoluteTimeNever(),
52 }
53}
54
55// ParseAddress translates a GNUnet address string like
56// "r5n+ip+udp://1.2.3.4:6789" or "gnunet+tcp://12.3.4.5/".
57// It can also handle standard strings like "udp:127.0.0.1:6735".
58func ParseAddress(s string) (addr *Address, err error) {
59 p := strings.SplitN(s, ":", 2)
60 if len(p) != 2 {
61 err = fmt.Errorf("invalid address format: '%s'", s)
62 return
63 }
64 addr = NewAddress(p[0], []byte(strings.Trim(p[1], "/")))
65 return
66}
67
68// Equals return true if two addresses match.
69func (a *Address) Equals(b *Address) bool {
70 return a.Netw == b.Netw &&
71 a.Options == b.Options &&
72 bytes.Equal(a.Address, b.Address)
73}
74
75// StringAll returns a human-readable representation of an address.
76func (a *Address) StringAll() string {
77 return a.Netw + "://" + string(a.Address)
78}
79
80// implement net.Addr interface methods:
81
45// String returns a human-readable representation of an address. 82// String returns a human-readable representation of an address.
46func (a *Address) String() string { 83func (a *Address) String() string {
47 return fmt.Sprintf("Address{%s}", AddressString(a.Transport, a.Address)) 84 return string(a.Address)
85}
86
87// Network returns the protocol specifier.
88func (a *Address) Network() string {
89 return a.Netw
48} 90}
49 91
50//---------------------------------------------------------------------- 92//----------------------------------------------------------------------
51 93
52// AddressString returns a string representaion of an address. 94// AddressString returns a string representaion of an address.
53func AddressString(transport string, addr []byte) string { 95func AddressString(network string, addr []byte) string {
54 if transport == "tcp" || transport == "udp" { 96 return network + "://" + string(addr)
55 alen := len(addr)
56 port := uint(addr[alen-2])*256 + uint(addr[alen-1])
57 return fmt.Sprintf("%s:%s:%d", transport, net.IP(addr[:alen-2]).String(), port)
58 }
59 return fmt.Sprintf("%s:%s", transport, hex.EncodeToString(addr))
60} 97}
61 98
62//---------------------------------------------------------------------- 99//----------------------------------------------------------------------
@@ -76,3 +113,71 @@ func NewIPAddress(host []byte, port uint16) *IPAddress {
76 copy(ip.Host, host) 113 copy(ip.Host, host)
77 return ip 114 return ip
78} 115}
116
117//----------------------------------------------------------------------
118
119// PeerAddrList is a list of addresses per peer ID.
120type PeerAddrList struct {
121 list *Map[string, []*Address]
122}
123
124// NewPeerAddrList returns a new and empty address list.
125func NewPeerAddrList() *PeerAddrList {
126 return &PeerAddrList{
127 list: NewMap[string, []*Address](),
128 }
129}
130
131// Add address for peer. The returned mode is 0=not added, 1=new peer,
132// 2=new address
133func (a *PeerAddrList) Add(id string, addr *Address) (mode int) {
134 // check for expired address.
135 mode = 0
136 if !addr.Expires.Expired() {
137 // run add operation
138 a.list.Process(func() error {
139 list, ok := a.list.Get(id)
140 if !ok {
141 list = make([]*Address, 0)
142 mode = 1
143 } else {
144 for _, a := range list {
145 if a.Equals(addr) {
146 return nil
147 }
148 }
149 mode = 2
150 }
151 list = append(list, addr)
152 a.list.Put(id, list)
153 return nil
154 })
155 }
156 return
157}
158
159// Get address for peer
160func (a *PeerAddrList) Get(id string, transport string) *Address {
161 list, ok := a.list.Get(id)
162 if ok {
163 for _, addr := range list {
164 // check for expired address.
165 if addr.Expires.Expired() {
166 // skip expired
167 continue
168 }
169 // check for matching protocol
170 if len(transport) > 0 && transport != addr.Netw {
171 // skip other transports
172 continue
173 }
174 return addr
175 }
176 }
177 return nil
178}
179
180// Delete a list entry by key.
181func (a *PeerAddrList) Delete(id string) {
182 a.list.Delete(id)
183}
diff --git a/src/gnunet/util/array.go b/src/gnunet/util/array.go
index 254610b..c6d6371 100644
--- a/src/gnunet/util/array.go
+++ b/src/gnunet/util/array.go
@@ -24,44 +24,68 @@ import (
24 24
25// Error variables 25// Error variables
26var ( 26var (
27 ErrUtilArrayTooSmall = fmt.Errorf("Array to small") 27 ErrUtilArrayTooSmall = fmt.Errorf("array to small")
28) 28)
29 29
30//---------------------------------------------------------------------- 30//----------------------------------------------------------------------
31// Byte array helpers 31// generic array helpers
32//---------------------------------------------------------------------- 32//----------------------------------------------------------------------
33 33
34// Clone creates a new array of same content as the argument. 34// Clone creates a new array of same content as the argument.
35func Clone(d []byte) []byte { 35func Clone[T []E, E any](d T) T {
36 r := make([]byte, len(d)) 36 r := make(T, len(d))
37 copy(r, d) 37 copy(r, d)
38 return r 38 return r
39} 39}
40 40
41// Reverse the content of a byte array 41// Equals returns true if two arrays match.
42func Reverse(b []byte) []byte { 42func Equals[T []E, E comparable](a, b T) bool {
43 if len(a) != len(b) {
44 return false
45 }
46 for i, e := range a {
47 if e != b[i] {
48 return false
49 }
50 }
51 return true
52}
53
54// Reverse the content of an array
55func Reverse[T []E, E any](b T) T {
43 bl := len(b) 56 bl := len(b)
44 r := make([]byte, bl) 57 r := make(T, bl)
45 for i := 0; i < bl; i++ { 58 for i := 0; i < bl; i++ {
46 r[bl-i-1] = b[i] 59 r[bl-i-1] = b[i]
47 } 60 }
48 return r 61 return r
49} 62}
50 63
51// IsNull returns true if all bytes in an array are set to 0. 64// IsAll returns true if all elements in an array are set to null.
52func IsNull(b []byte) bool { 65func IsAll[T []E, E comparable](b T, null E) bool {
53 for _, v := range b { 66 for _, v := range b {
54 if v != 0 { 67 if v != null {
55 return false 68 return false
56 } 69 }
57 } 70 }
58 return true 71 return true
59} 72}
60 73
61// CopyBlock copies 'in' to 'out' so that 'out' is filled completely. 74// Fill an array with a value
75func Fill[T []E, E any](b T, val E) {
76 for i := range b {
77 b[i] = val
78 }
79}
80
81//----------------------------------------------------------------------
82// byte array helpers
83//----------------------------------------------------------------------
84
85// CopyAlignedBlock copies 'in' to 'out' so that 'out' is filled completely.
62// - If 'in' is larger than 'out', it is left-truncated before copy 86// - If 'in' is larger than 'out', it is left-truncated before copy
63// - If 'in' is smaller than 'out', it is left-padded with 0 before copy 87// - If 'in' is smaller than 'out', it is left-padded with 0 before copy
64func CopyBlock(out, in []byte) { 88func CopyAlignedBlock(out, in []byte) {
65 count := len(in) 89 count := len(in)
66 size := len(out) 90 size := len(out)
67 from, to := 0, 0 91 from, to := 0, 0
@@ -76,27 +100,10 @@ func CopyBlock(out, in []byte) {
76 copy(out[to:], in[from:]) 100 copy(out[to:], in[from:])
77} 101}
78 102
79// Fill an array with a value
80func Fill(b []byte, val byte) {
81 for i := 0; i < len(b); i++ {
82 b[i] = val
83 }
84}
85
86//---------------------------------------------------------------------- 103//----------------------------------------------------------------------
87// String list helpers 104// String list helpers
88//---------------------------------------------------------------------- 105//----------------------------------------------------------------------
89 106
90// ReverseStringList reverse an array of strings
91func ReverseStringList(s []string) []string {
92 sl := len(s)
93 r := make([]string, sl)
94 for i := 0; i < sl; i++ {
95 r[sl-i-1] = s[i]
96 }
97 return r
98}
99
100// StringList converts a binary representation of a string list. Each string 107// StringList converts a binary representation of a string list. Each string
101// is '\0'-terminated. The whole byte array is parsed; if the final string is 108// is '\0'-terminated. The whole byte array is parsed; if the final string is
102// not terminated, it is skipped. 109// not terminated, it is skipped.
diff --git a/src/gnunet/util/database.go b/src/gnunet/util/database.go
index 5805a8f..a1198fd 100644
--- a/src/gnunet/util/database.go
+++ b/src/gnunet/util/database.go
@@ -19,6 +19,7 @@
19package util 19package util
20 20
21import ( 21import (
22 "context"
22 "database/sql" 23 "database/sql"
23 "fmt" 24 "fmt"
24 "os" 25 "os"
@@ -34,7 +35,96 @@ var (
34 ErrSQLNoDatabase = fmt.Errorf("Database not found") 35 ErrSQLNoDatabase = fmt.Errorf("Database not found")
35) 36)
36 37
37// ConnectSQLDatabase connects to an SQL database (various types and flavors): 38//----------------------------------------------------------------------
39// Connection to a database instance. There can be multiple connections
40// on the same instance, managed by the database pool.
41//----------------------------------------------------------------------
42
43// DbConn is a database connection suitable for executing SQL commands.
44type DbConn struct {
45 conn *sql.Conn // connection to database instance
46 pool *dbPool // reference to managng pool
47 key string // database identifier (connect string)
48}
49
50// Close database connection.
51func (db *DbConn) Close() (err error) {
52 if err = db.conn.Close(); err != nil {
53 return
54 }
55 err = db.pool.remove(db.key)
56 return
57}
58
59// QueryRow returns a single record for a query
60func (db *DbConn) QueryRow(query string, args ...any) *sql.Row {
61 return db.conn.QueryRowContext(db.pool.ctx, query, args...)
62}
63
64// Query returns all matching records for a query
65func (db *DbConn) Query(query string, args ...any) (*sql.Rows, error) {
66 return db.conn.QueryContext(db.pool.ctx, query, args...)
67}
68
69// Exec a SQL statement
70func (db *DbConn) Exec(query string, args ...any) (sql.Result, error) {
71 return db.conn.ExecContext(db.pool.ctx, query, args...)
72}
73
74// TODO: add more SQL methods
75
76//----------------------------------------------------------------------
77// DbPool holds all database instances used: Connecting with the same
78// connect string returns the same instance.
79//----------------------------------------------------------------------
80
81// global instance for the database pool (singleton)
82var (
83 DbPool *dbPool
84)
85
86// DbPoolEntry holds information about a database instance.
87type DbPoolEntry struct {
88 db *sql.DB // reference to the database engine
89 refs int // number of open connections (reference count)
90 connect string // SQL connect string
91}
92
93// package initialization
94func init() {
95 // construct database pool
96 DbPool = new(dbPool)
97 DbPool.insts = NewMap[string, *DbPoolEntry]()
98 DbPool.ctx, DbPool.cancel = context.WithCancel(context.Background())
99}
100
101// dbPool keeps a mapping between connect string and database instance
102type dbPool struct {
103 ctx context.Context // connection context
104 cancel context.CancelFunc // cancel function
105 insts *Map[string, *DbPoolEntry] // map of database instances
106}
107
108// remove a database instance from the pool based on its connect string.
109func (p *dbPool) remove(key string) error {
110 return p.insts.Process(func() (err error) {
111 // get pool entry
112 pe, ok := p.insts.Get(key)
113 if !ok {
114 return nil
115 }
116 // decrement ref count
117 pe.refs--
118 if pe.refs == 0 {
119 // no more refs: close database
120 err = pe.db.Close()
121 p.insts.Delete(key)
122 }
123 return
124 })
125}
126
127// Connect to a SQL database (various types and flavors):
38// The 'spec' option defines the arguments required to connect to a database; 128// The 'spec' option defines the arguments required to connect to a database;
39// the meaning and format of the arguments depends on the specific SQL database. 129// the meaning and format of the arguments depends on the specific SQL database.
40// The arguments are seperated by the '+' character; the first (and mandatory) 130// The arguments are seperated by the '+' character; the first (and mandatory)
@@ -46,27 +136,50 @@ var (
46// * 'mysql': A MySQL-compatible database; the second argument specifies the 136// * 'mysql': A MySQL-compatible database; the second argument specifies the
47// information required to log into the database (e.g. 137// information required to log into the database (e.g.
48// "[user[:passwd]@][proto[(addr)]]/dbname[?param1=value1&...]"). 138// "[user[:passwd]@][proto[(addr)]]/dbname[?param1=value1&...]").
49func ConnectSQLDatabase(spec string) (db *sql.DB, err error) { 139func (p *dbPool) Connect(spec string) (db *DbConn, err error) {
50 // split spec string into segments 140 err = p.insts.Process(func() error {
51 specs := strings.Split(spec, ":") 141 // check if we have a connection to this database.
52 if len(specs) < 2 { 142 inst, ok := p.insts.Get(spec)
53 return nil, ErrSQLInvalidDatabaseSpec 143 if !ok {
54 } 144 inst = new(DbPoolEntry)
55 switch specs[0] { 145 inst.refs = 0
56 case "sqlite3": 146 inst.connect = spec
57 // check if the database file exists 147
58 var fi os.FileInfo 148 // No: create new database instance.
59 if fi, err = os.Stat(specs[1]); err != nil { 149 // split spec string into segments
60 return nil, ErrSQLNoDatabase 150 specs := strings.Split(spec, ":")
61 } 151 if len(specs) < 2 {
62 if fi.IsDir() { 152 return ErrSQLInvalidDatabaseSpec
63 return nil, ErrSQLNoDatabase 153 }
154 // create database object
155 switch specs[0] {
156 case "sqlite3":
157 // check if the database file exists
158 var fi os.FileInfo
159 if fi, err = os.Stat(specs[1]); err != nil {
160 return ErrSQLNoDatabase
161 }
162 if fi.IsDir() {
163 return ErrSQLNoDatabase
164 }
165 // open the database file
166 inst.db, err = sql.Open("sqlite3", specs[1])
167 case "mysql":
168 // just connect to the database
169 inst.db, err = sql.Open("mysql", specs[1])
170 default:
171 return ErrSQLInvalidDatabaseSpec
172 }
173 // save database in pool
174 p.insts.Put(spec, inst)
175 ok = true
64 } 176 }
65 // open the database file 177 // increment reference count
66 return sql.Open("sqlite3", specs[1]) 178 inst.refs++
67 case "mysql": 179 // return a new connection to the database.
68 // just connect to the database 180 db = new(DbConn)
69 return sql.Open("mysql", specs[1]) 181 db.conn, err = inst.db.Conn(p.ctx)
70 } 182 return err
71 return nil, ErrSQLInvalidDatabaseSpec 183 })
184 return
72} 185}
diff --git a/src/gnunet/util/fs.go b/src/gnunet/util/fs.go
index 009ef62..b2a464e 100644
--- a/src/gnunet/util/fs.go
+++ b/src/gnunet/util/fs.go
@@ -25,7 +25,7 @@ import (
25 "github.com/bfix/gospel/logger" 25 "github.com/bfix/gospel/logger"
26) 26)
27 27
28// EnforceDirExists make sure that the path 28// EnforceDirExists make sure that the path is created
29func EnforceDirExists(path string) error { 29func EnforceDirExists(path string) error {
30 logger.Printf(logger.DBG, "[util] Checking directory '%s'...\n", path) 30 logger.Printf(logger.DBG, "[util] Checking directory '%s'...\n", path)
31 fi, err := os.Lstat(path) 31 fi, err := os.Lstat(path)
diff --git a/src/gnunet/util/key_value_store.go b/src/gnunet/util/key_value_store.go
deleted file mode 100644
index 0658218..0000000
--- a/src/gnunet/util/key_value_store.go
+++ /dev/null
@@ -1,188 +0,0 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package util
20
21import (
22 "context"
23 "database/sql"
24 "fmt"
25 "strconv"
26 "strings"
27
28 redis "github.com/go-redis/redis/v8"
29)
30
31// Error messages related to the key/value-store implementations
32var (
33 ErrKVSInvalidSpec = fmt.Errorf("Invalid KVStore specification")
34 ErrKVSNotAvailable = fmt.Errorf("KVStore not available")
35)
36
37// KeyValueStore interface for implementations that store and retrieve
38// key/value pairs. Keys and values are strings.
39type KeyValueStore interface {
40 Put(key string, value string) error // put a key/value pair into store
41 Get(key string) (string, error) // retrieve a value for a key from store
42 List() ([]string, error) // get all keys from the store
43}
44
45// OpenKVStore opens a key/value store for further put/get operations.
46// The 'spec' option specifies the arguments required to connect to a specific
47// persistence mechanism. The arguments in the 'spec' string are separated by
48// the '+' character.
49// The first argument specifies the type of key/value store to be used; the
50// meaning and format of the following arguments depend on this type.
51//
52// Key/Value Store types defined:
53// * 'redis': Use a Redis server for persistance; the specification is
54// "redis+addr+[passwd]+db". 'db' must be an integer value.
55// * 'mysql': MySQL-compatible database (see 'database.go' for details)
56// * 'sqlite3': SQLite3-compatible database (see 'database.go' for details)
57func OpenKVStore(spec string) (KeyValueStore, error) {
58 // check specification string
59 specs := strings.Split(spec, "+")
60 if len(specs) < 2 {
61 return nil, ErrKVSInvalidSpec
62 }
63 switch specs[0] {
64 case "redis":
65 //--------------------------------------------------------------
66 // NoSQL-based persistance
67 //--------------------------------------------------------------
68 if len(specs) < 4 {
69 return nil, ErrKVSInvalidSpec
70 }
71 db, err := strconv.Atoi(specs[3])
72 if err != nil {
73 return nil, ErrKVSInvalidSpec
74 }
75 kvs := new(KvsRedis)
76 kvs.db = db
77 kvs.client = redis.NewClient(&redis.Options{
78 Addr: specs[1],
79 Password: specs[2],
80 DB: db,
81 })
82 if kvs.client == nil {
83 err = ErrKVSNotAvailable
84 }
85 return kvs, err
86
87 case "sqlite3", "mysql":
88 //--------------------------------------------------------------
89 // SQL-based persistance
90 //--------------------------------------------------------------
91 kvs := new(KvsSQL)
92 var err error
93
94 // connect to SQL database
95 kvs.db, err = ConnectSQLDatabase(spec)
96 if err != nil {
97 return nil, err
98 }
99 // get number of key/value pairs (as a check for existing table)
100 row := kvs.db.QueryRow("select count(*) from store")
101 var num int
102 if row.Scan(&num) != nil {
103 return nil, ErrKVSNotAvailable
104 }
105 return kvs, nil
106 }
107 return nil, ErrKVSInvalidSpec
108}
109
110//======================================================================
111// NoSQL-based key-value-stores
112//======================================================================
113
114// KvsRedis represents a redis-based key/value store
115type KvsRedis struct {
116 client *redis.Client // client connection
117 db int // index to database
118}
119
120// Put a key/value pair into the store
121func (kvs *KvsRedis) Put(key string, value string) error {
122 return kvs.client.Set(context.TODO(), key, value, 0).Err()
123}
124
125// Get a value for a given key from store
126func (kvs *KvsRedis) Get(key string) (value string, err error) {
127 return kvs.client.Get(context.TODO(), key).Result()
128}
129
130// List all keys in store
131func (kvs *KvsRedis) List() (keys []string, err error) {
132 var (
133 crs uint64
134 segm []string
135 ctx = context.TODO()
136 )
137 for {
138 segm, crs, err = kvs.client.Scan(ctx, crs, "*", 10).Result()
139 if err != nil {
140 return nil, err
141 }
142 if crs == 0 {
143 break
144 }
145 keys = append(keys, segm...)
146 }
147 return
148}
149
150//======================================================================
151// SQL-based key-value-store
152//======================================================================
153
154// KvsSQL represents a SQL-based key/value store
155type KvsSQL struct {
156 db *sql.DB
157}
158
159// Put a key/value pair into the store
160func (kvs *KvsSQL) Put(key string, value string) error {
161 _, err := kvs.db.Exec("insert into store(key,value) values(?,?)", key, value)
162 return err
163}
164
165// Get a value for a given key from store
166func (kvs *KvsSQL) Get(key string) (value string, err error) {
167 row := kvs.db.QueryRow("select value from store where key=?", key)
168 err = row.Scan(&value)
169 return
170}
171
172// List all keys in store
173func (kvs *KvsSQL) List() (keys []string, err error) {
174 var (
175 rows *sql.Rows
176 key string
177 )
178 rows, err = kvs.db.Query("select key from store")
179 if err == nil {
180 for rows.Next() {
181 if err = rows.Scan(&key); err != nil {
182 break
183 }
184 keys = append(keys, key)
185 }
186 }
187 return
188}
diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/misc.go
index 66744bd..7240757 100644
--- a/src/gnunet/util/misc.go
+++ b/src/gnunet/util/misc.go
@@ -20,13 +20,18 @@ package util
20 20
21import ( 21import (
22 "strings" 22 "strings"
23 "sync"
23) 24)
24 25
25// CounterMap is a metric with single key 26//----------------------------------------------------------------------
26type CounterMap map[interface{}]int 27// Count occurence of multiple instance at the same time.
28//----------------------------------------------------------------------
29
30// Counter is a metric with single key
31type Counter[T comparable] map[T]int
27 32
28// Add one to themetric for a given key and return current value 33// Add one to themetric for a given key and return current value
29func (cm CounterMap) Add(i interface{}) int { 34func (cm Counter[T]) Add(i T) int {
30 count, ok := cm[i] 35 count, ok := cm[i]
31 if !ok { 36 if !ok {
32 count = 1 37 count = 1
@@ -38,7 +43,7 @@ func (cm CounterMap) Add(i interface{}) int {
38} 43}
39 44
40// Num returns the metric for a given key 45// Num returns the metric for a given key
41func (cm CounterMap) Num(i interface{}) int { 46func (cm Counter[T]) Num(i T) int {
42 count, ok := cm[i] 47 count, ok := cm[i]
43 if !ok { 48 if !ok {
44 count = 0 49 count = 0
@@ -46,6 +51,68 @@ func (cm CounterMap) Num(i interface{}) int {
46 return count 51 return count
47} 52}
48 53
54//----------------------------------------------------------------------
55// Thread-safe map implementation
56//----------------------------------------------------------------------
57
58// Map keys to values
59type Map[K comparable, V any] struct {
60 list map[K]V
61 mtx sync.RWMutex
62 inProcess bool
63}
64
65// NewMap allocates a new mapping.
66func NewMap[K comparable, V any]() *Map[K, V] {
67 return &Map[K, V]{
68 list: make(map[K]V),
69 inProcess: false,
70 }
71}
72
73// Process a function in the locked map context. Calls
74// to other map functions in 'f' will use additional locks.
75func (m *Map[K, V]) Process(f func() error) error {
76 m.mtx.Lock()
77 defer m.mtx.Unlock()
78 m.inProcess = true
79 err := f()
80 m.inProcess = false
81 return err
82}
83
84// Put value into map under given key.
85func (m *Map[K, V]) Put(key K, value V) {
86 if !m.inProcess {
87 m.mtx.Lock()
88 defer m.mtx.Unlock()
89 }
90 m.list[key] = value
91}
92
93// Get value with iven key from map.
94func (m *Map[K, V]) Get(key K) (value V, ok bool) {
95 if !m.inProcess {
96 m.mtx.RLock()
97 defer m.mtx.RUnlock()
98 }
99 value, ok = m.list[key]
100 return
101}
102
103// Delete key/value pair from map.
104func (m *Map[K, V]) Delete(key K) {
105 if !m.inProcess {
106 m.mtx.Lock()
107 defer m.mtx.Unlock()
108 }
109 delete(m.list, key)
110}
111
112//----------------------------------------------------------------------
113// additional helpers
114//----------------------------------------------------------------------
115
49// StripPathRight returns a dot-separated path without 116// StripPathRight returns a dot-separated path without
50// its last (right-most) element. 117// its last (right-most) element.
51func StripPathRight(s string) string { 118func StripPathRight(s string) string {
diff --git a/src/gnunet/util/peer_id.go b/src/gnunet/util/peer_id.go
index f4c14bd..a8202a1 100644
--- a/src/gnunet/util/peer_id.go
+++ b/src/gnunet/util/peer_id.go
@@ -1,5 +1,5 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang. 1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019, 2020 Bernd Fix >Y< 2// Copyright (C) 2019-2022 Bernd Fix >Y<
3// 3//
4// gnunet-go is free software: you can redistribute it and/or modify it 4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published 5// under the terms of the GNU Affero General Public License as published
@@ -18,6 +18,8 @@
18 18
19package util 19package util
20 20
21import "bytes"
22
21// PeerID is the 32-byte binary representation od a Ed25519 key 23// PeerID is the 32-byte binary representation od a Ed25519 key
22type PeerID struct { 24type PeerID struct {
23 Key []byte `size:"32"` 25 Key []byte `size:"32"`
@@ -33,7 +35,7 @@ func NewPeerID(data []byte) *PeerID {
33 data = data[:32] 35 data = data[:32]
34 } else if size < 32 { 36 } else if size < 32 {
35 buf := make([]byte, 32) 37 buf := make([]byte, 32)
36 CopyBlock(buf, data) 38 CopyAlignedBlock(buf, data)
37 data = buf 39 data = buf
38 } 40 }
39 } 41 }
@@ -42,6 +44,11 @@ func NewPeerID(data []byte) *PeerID {
42 } 44 }
43} 45}
44 46
47// Equals returns true if two peer IDs match.
48func (p *PeerID) Equals(q *PeerID) bool {
49 return bytes.Equal(p.Key, q.Key)
50}
51
45// String returns a human-readable representation of a peer id. 52// String returns a human-readable representation of a peer id.
46func (p *PeerID) String() string { 53func (p *PeerID) String() string {
47 return EncodeBinaryToString(p.Key) 54 return EncodeBinaryToString(p.Key)
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index e70635c..70b91e1 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -43,6 +43,13 @@ func NewAbsoluteTime(t time.Time) AbsoluteTime {
43 } 43 }
44} 44}
45 45
46// NewAbsoluteTimeEpoch set the point in time to the given time value
47func NewAbsoluteTimeEpoch(secs uint64) AbsoluteTime {
48 return AbsoluteTime{
49 Val: uint64(secs * 1000000),
50 }
51}
52
46// AbsoluteTimeNow returns the current point in time. 53// AbsoluteTimeNow returns the current point in time.
47func AbsoluteTimeNow() AbsoluteTime { 54func AbsoluteTimeNow() AbsoluteTime {
48 return NewAbsoluteTime(time.Now()) 55 return NewAbsoluteTime(time.Now())
@@ -53,6 +60,11 @@ func AbsoluteTimeNever() AbsoluteTime {
53 return AbsoluteTime{math.MaxUint64} 60 return AbsoluteTime{math.MaxUint64}
54} 61}
55 62
63// Epoch returns the seconds since Unix epoch.
64func (t AbsoluteTime) Epoch() uint64 {
65 return t.Val / 1000000
66}
67
56// String returns a human-readable notation of an absolute time. 68// String returns a human-readable notation of an absolute time.
57func (t AbsoluteTime) String() string { 69func (t AbsoluteTime) String() string {
58 if t.Val == math.MaxUint64 { 70 if t.Val == math.MaxUint64 {
@@ -133,3 +145,8 @@ func (t RelativeTime) String() string {
133 } 145 }
134 return time.Duration(t.Val * 1000).String() 146 return time.Duration(t.Val * 1000).String()
135} 147}
148
149// Add two durations
150func (t RelativeTime) Add(t2 RelativeTime) {
151 t.Val += t2.Val
152}