[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnunet-go] branch master updated: Reworked gnunet/transport and gnunet/
From: |
gnunet |
Subject: |
[gnunet-go] branch master updated: Reworked gnunet/transport and gnunet/service. |
Date: |
Wed, 01 Jun 2022 21:03:22 +0200 |
This is an automated email from the git hooks/post-receive script.
bernd-fix pushed a commit to branch master
in repository gnunet-go.
The following commit(s) were added to refs/heads/master by this push:
new 913c80f Reworked gnunet/transport and gnunet/service.
913c80f is described below
commit 913c80f317270b41f339048afa5d63278eecf8f4
Author: Bernd Fix <brf@hoi-polloi.org>
AuthorDate: Wed Jun 1 20:59:52 2022 +0200
Reworked gnunet/transport and gnunet/service.
---
README.md | 125 ++++---
src/cmd/peer_mockup/main.go | 68 ----
src/cmd/peer_mockup/peers.go | 46 ---
src/cmd/peer_mockup/process.go | 128 -------
src/cmd/revoke-zonekey/main.go | 202 -----------
src/gnunet/build.sh | 3 +
src/{ => gnunet}/cmd/.gitignore | 0
.../cmd/gnunet-service-dht-test-go}/main.go | 74 ++--
src/{ => gnunet}/cmd/gnunet-service-gns-go/main.go | 31 +-
.../cmd/gnunet-service-revocation-go/main.go | 29 +-
src/gnunet/cmd/peer_mockup/main.go | 178 ++++++++++
src/gnunet/cmd/revoke-zonekey/main.go | 336 ++++++++++++++++++
src/{ => gnunet}/cmd/vanityid/main.go | 0
src/gnunet/config/config.go | 68 +++-
src/gnunet/config/gnunet-config.json | 48 ++-
src/gnunet/core/core.go | 234 +++++++++++++
src/gnunet/core/core_test.go | 150 ++++++++
src/gnunet/core/event.go | 109 ++++++
src/gnunet/core/peer.go | 108 +++++-
src/gnunet/core/peer_test.go | 72 ++++
src/gnunet/crypto/gns.go | 9 +-
src/gnunet/crypto/hash.go | 16 +-
src/gnunet/go.mod | 26 +-
src/gnunet/go.sum | 128 ++-----
src/gnunet/message/factory.go | 4 +
src/gnunet/message/message.go | 8 +
src/gnunet/message/msg_dht.go | 95 +++++-
src/gnunet/message/msg_gns.go | 94 -----
src/gnunet/message/msg_hello.go | 103 ++++++
src/gnunet/message/msg_namecache.go | 12 +-
src/gnunet/message/msg_transport.go | 82 +----
src/gnunet/modules.go | 28 +-
src/gnunet/service/client.go | 30 +-
src/gnunet/service/connection.go | 280 +++++++++++++++
src/gnunet/service/context.go | 87 -----
src/gnunet/service/dht/blocks/generic.go | 196 +++++++++++
src/gnunet/service/dht/blocks/generic_test.go | 67 ++++
src/gnunet/service/dht/blocks/gns.go | 172 ++++++++++
src/gnunet/service/dht/blocks/hello.go | 226 ++++++++++++
.../fs.go => service/dht/blocks/hello_test.go} | 37 +-
.../session.go => service/dht/blocks/types.go} | 15 +-
src/gnunet/service/dht/bloomfilter.go | 123 +++++++
src/gnunet/service/dht/dhtstore_test.go | 89 +++++
src/gnunet/service/dht/module.go | 99 +++++-
src/gnunet/service/dht/routingtable.go | 305 +++++++++++++++++
src/gnunet/service/dht/routingtable_test.go | 140 ++++++++
src/gnunet/service/dht/service.go | 134 ++++++++
src/gnunet/service/gns/block_handler.go | 18 +-
src/gnunet/service/gns/dns.go | 4 +-
src/gnunet/service/gns/module.go | 135 +++++---
src/gnunet/service/gns/service.go | 235 ++++++-------
src/gnunet/service/module.go | 70 ++++
src/gnunet/service/namecache/module.go | 31 +-
src/gnunet/service/revocation/module.go | 100 ++++--
src/gnunet/service/revocation/pow.go | 46 ++-
src/gnunet/service/revocation/pow_test.go | 8 +-
src/gnunet/service/revocation/service.go | 192 +++++------
src/gnunet/service/service.go | 200 +++++------
src/gnunet/service/store.go | 379 +++++++++++++++++++++
src/gnunet/test.sh | 3 +
src/gnunet/transport/channel.go | 213 ------------
src/gnunet/transport/channel_netw.go | 285 ----------------
src/gnunet/transport/channel_test.go | 232 -------------
src/gnunet/transport/connection.go | 108 ++++--
src/gnunet/transport/endpoint.go | 282 +++++++++++++++
src/gnunet/transport/reader_writer.go | 157 +++++++++
src/gnunet/transport/transport.go | 151 ++++++++
src/gnunet/util/address.go | 141 +++++++-
src/gnunet/util/array.go | 65 ++--
src/gnunet/util/database.go | 159 +++++++--
src/gnunet/util/fs.go | 2 +-
src/gnunet/util/key_value_store.go | 188 ----------
src/gnunet/util/misc.go | 75 +++-
src/gnunet/util/peer_id.go | 11 +-
src/gnunet/util/time.go | 17 +
75 files changed, 5641 insertions(+), 2480 deletions(-)
diff --git a/README.md b/README.md
index e721a94..1a1545f 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,103 @@
-# GNUnet in Go
+# gnunet-go: GNUnet implementation in Go
-This repository has two parts:
+Copyright (C) 2019-2022 Bernd Fix >Y<
-* `src/` contains a Go implementation of GNUnet: It is WIP and only provides a
-very limited coverage of GNUnet. The goal is to have a complete, functionally
-equivalent implementation of the GNUnet protocol in Go.
+gnunet-go is free software: you can redistribute it and/or modify it
+under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License,
+or (at your option) any later version.
-* `doc/` contains documents for an implementation-agnostic specification of the
-GNUnet P2P protocols. It focuses on the peer messages, but also provides
-information on the internal messages.
+gnunet-go is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Affero General Public License for more details.
-## Author(s)
- * Bernd Fix <brf@hoi-polloi.org>
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
-All files are licensed under GNU AGPL-3.0. Copyright by the authors.
+SPDX-License-Identifier: AGPL3.0-or-later
## Caveat
THIS IS WORK-IN-PROGRESS AT A VERY EARLY STATE. DON'T EXPECT ANY COMPLETE
DOCUMENTATION OR COMPILABLE, RUNNABLE OR EVEN OPERATIONAL SOURCE CODE.
+## TL;DR
+
+Go v1.18+ is required to compile the code.
+
+```bash
+git clone https://github.com/bfix/gnunet-go
+cd gnunet-go/src/gnunet
+go mod tidy
+go install ./...
+go test ./...
+```
+
+The binaries are stored in `${GOPATH}/bin`.
+
## Source code
-All source code is written in Golang (version 1.13+).
+All source code is written for Go v1.18+.
-### Dependencies
+3rd party libraries are managed by the Go module framework. After downloading
+the source code, make sure you run `go mod tidy` in the `src/gnunet` folder
+to install all dependencies.
-3rd party libraries are used to provide helper functionality (logging, MPI,
-Ed25519 support and other crypto-related packages). Make sure the dependent
-packages are accessible through `GOPATH`. To install the dependencies:
+### `./src/gnunet`
-```bash
-$ go get -u golang.org/x/crypto/...
-$ go get -u golang.org/x/text/...
-$ go get -u github.com/miekg/dns/...
-$ go get -u github.com/bfix/gospel/...
-```
+The folder `src/gnunet` contains a Go implementation of GNUnet: It is WIP
+and only provides a very limited coverage of GNUnet. The goal is to have
+a complete, functionally equivalent implementation of the GNUnet protocol
+in Go. Currently only some aspects of Transport, GNS, Revocation, Namecache
+and DHT are implemented.
+
+Use `./build.sh` to build the executables (services and utilities, see
+below). The resulting programs are stored in `${GOPATH}/bin`.
+
+To run the unit tests, use `./test.sh`.
+
+### `./src/gnunet/cmd`
+
+#### `gnunet-service-dht-test-go`: Implementation of the DHT core service
(testbed).
+
+#### `gnunet-service-gns-go`: Implementation of the GNS core service.
-### ./src/cmd folder
+Stand-alone GNS service that could be used with other GNUnet utilities and
+services.
+#### `gnunet-service-revocation-go`: Implementation of the GNS revocation
service.
-#### `gnunet-service-gns-go`: Implementation of the GNS service.
+Stand-alone Revocation service that could be used with other GNUnet utilities
+and services.
+
+#### `revoke-zonekey`: Implementation of a stand-alone program to calculate
revocations.
+
+This program creates a zone key revocation block. Depending on the parameters
+the calculation can take days or even weeks. The program can be interrupted
+at any time using `^C`; restarting the program with the exact same parameters
+continues the calculation.
+
+The following command-line options are available:
+
+* **`-b`**: Number of leading zero bits (difficulty, default: 24). The minimum
+difficulty `D` is fixed at 23. The expiration of a revocation is derived using
+`(b-D+1)*(1.1*EPOCH)`, where `EPOCH` is 365 days and it is extended by 10% in
+order to deal with unsynchronized clocks.
+
+The default difficulty will create a revocation valid for ~2 years.
+
+* **`-z`**: Zone key to be revoked (zone ID)
+
+* **`-f`**: Name of file to store revocation data
+
+* **`-t`**: testing mode: allow small difficulties for test runs.
+
+* **`-v`**: verbose output
#### `peer_mockup`: test message exchange on the lowest level (transport).
-#### `vanityid`: Compute GNUnet vanity peer and ego id for a given regexp
pattern.
+#### `vanityid`: Compute GNUnet vanity peer id for a given regexp pattern.
N.B.: Key generation is slow at the moment, so be patient! To generate a single
matching key some 1,000,000 keys need to be generated for a four letter prefix;
@@ -65,21 +118,11 @@ found; `time` is the time needed to find a match.
To generate the key files, make sure GNUnet **is not running** and do:
```bash
-$ # use a vanity peer id:
-$ echo "<hex.seed>" | xxd -r -p >
/var/lib/gnunet/.local/share/gnunet/private_key.ecc
-$ sudo chown gnunet:gnunet /var/lib/gnunet/.local/share/gnunet/private_key.ecc
-$ sudo chmod 600 /var/lib/gnunet/.local/share/gnunet/private_key.ecc
-$ # use a vanity ego id:
-$ echo "<hex.scalar>" | xxd -r -p >
~/.local/share/gnunet/identity/egos/<vanity_ego>
-$ chmod 600 ~/.local/share/gnunet/identity/egos/<vanity_ego>
+echo "<hex.seed>" | xxd -r -p >
/var/lib/gnunet/.local/share/gnunet/private_key.ecc
+chown gnunet:gnunet /var/lib/gnunet/.local/share/gnunet/private_key.ecc
+chmod 600 /var/lib/gnunet/.local/share/gnunet/private_key.ecc
```
-### ./src/gnunet folder
-
-Packages used to implement GNUnet protocols (currently only some of TRANSPORT
-and GNS).
-
-## Documentation
-
-* raw: raw ASCII protocol definition
-* specification: texinfo protocol definition
+For gnunet-go configuration files you need to paste the result of
+`echo "<hex.seed>" | xxd -r -p | base64` into the `PrivateSeed` field in the
+`NodeConfig` section.
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 @@
-package main
-
-import (
- "encoding/hex"
- "flag"
- "fmt"
-
- "github.com/bfix/gospel/logger"
- "gnunet/core"
- "gnunet/transport"
-)
-
-var (
- p *core.Peer // local peer (with private key)
- t *core.Peer // remote peer
-)
-
-func main() {
- // handle command line arguments
- var (
- asServer bool
- err error
- ch transport.Channel
- )
- flag.BoolVar(&asServer, "s", false, "accept incoming connections")
- flag.Parse()
-
- // setup peer instances from static data
- if err = setupPeers(false); err != nil {
- fmt.Println(err.Error())
- return
- }
-
-
fmt.Println("======================================================================")
- fmt.Println("GNUnet peer mock-up (EXPERIMENTAL) (c) 2018,2019 by
Bernd Fix, >Y<")
- fmt.Printf(" Identity '%s'\n", p.GetIDString())
- fmt.Printf(" [%s]\n", hex.EncodeToString(p.GetID()))
-
fmt.Println("======================================================================")
-
- if asServer {
- // run as server
- fmt.Println("Waiting for connections...")
- hdlr := make(chan transport.Channel)
- go func() {
- for {
- select {
- case ch = <-hdlr:
- mc := transport.NewMsgChannel(ch)
- if err = process(mc, t, p); err != nil {
- logger.Println(logger.ERROR,
err.Error())
- }
- }
- }
- }()
- _, err = transport.NewChannelServer("tcp+0.0.0.0:2086", hdlr)
- } else {
- // connect to peer
- fmt.Println("Connecting to target peer")
- if ch, err = transport.NewChannel("tcp+172.17.0.5:2086"); err
!= nil {
- logger.Println(logger.ERROR, err.Error())
- }
- mc := transport.NewMsgChannel(ch)
- err = process(mc, p, t)
- }
- if err != nil {
- fmt.Println(err)
- }
-}
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 @@
-package main
-
-import (
- "github.com/bfix/gospel/data"
- "gnunet/core"
- "gnunet/util"
-)
-
-func setupPeers(rnd bool) (err error) {
-
- //------------------------------------------------------------------
- // create local peer
- //------------------------------------------------------------------
- secret := []byte{
- 0x78, 0xde, 0xcf, 0xc0, 0x26, 0x9e, 0x62, 0x3d,
- 0x17, 0x24, 0xe6, 0x1b, 0x98, 0x25, 0xec, 0x2f,
- 0x40, 0x6b, 0x1e, 0x39, 0xa5, 0x19, 0xac, 0x9b,
- 0xb2, 0xdd, 0xf4, 0x6c, 0x12, 0x83, 0xdb, 0x86,
- }
- if rnd {
- util.RndArray(secret)
- }
- p, err = core.NewPeer(secret, true)
- if err != nil {
- return
- }
- addr, _ := data.Marshal(util.NewIPAddress([]byte{172, 17, 0, 6}, 2086))
- p.AddAddress(util.NewAddress("tcp", addr))
-
- //------------------------------------------------------------------
- // create remote peer
- //------------------------------------------------------------------
- id := []byte{
- 0x92, 0xdc, 0xbf, 0x39, 0x40, 0x2d, 0xc6, 0x3c,
- 0x97, 0xa6, 0x81, 0xe0, 0xfc, 0xd8, 0x7c, 0x74,
- 0x17, 0xd3, 0xa3, 0x8c, 0x52, 0xfd, 0xe0, 0x49,
- 0xbc, 0xd0, 0x1c, 0x0a, 0x0b, 0x8c, 0x02, 0x51,
- }
- t, err = core.NewPeer(id, false)
- if err != nil {
- return
- }
- addr, _ = data.Marshal(util.NewIPAddress([]byte{172, 17, 0, 5}, 2086))
- t.AddAddress(util.NewAddress("tcp", addr))
- return
-}
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 @@
-package main
-
-import (
- "errors"
- "fmt"
-
- "gnunet/core"
- "gnunet/crypto"
- "gnunet/message"
- "gnunet/transport"
- "gnunet/util"
-
- "github.com/bfix/gospel/concurrent"
-)
-
-var (
- sig = concurrent.NewSignaller()
-)
-
-func process(ch *transport.MsgChannel, from, to *core.Peer) (err error) {
- // create a new connection instance
- c := transport.NewConnection(ch, from, to)
- defer c.Close()
-
- // read and push next message
- in := make(chan message.Message)
- go func() {
- for {
- msg, err := c.Receive(sig)
- if err != nil {
- fmt.Printf("Receive: %s\n", err.Error())
- return
- }
- in <- msg
- }
- }()
-
- // are we initiating the connection?
- init := (from == p)
- if init {
- peerid := util.NewPeerID(p.GetID())
- c.Send(message.NewTransportTcpWelcomeMsg(peerid), sig)
- }
-
- // remember peer addresses (only ONE!)
- pAddr := p.GetAddressList()[0]
- tAddr := t.GetAddressList()[0]
-
- send := make(map[uint16]bool)
- //received := make(map[uint16]bool)
- pending := make(map[uint16]message.Message)
-
- // process loop
- for {
- select {
- case m := <-in:
- switch msg := m.(type) {
-
- case *message.TransportTcpWelcomeMsg:
- peerid := util.NewPeerID(p.GetID())
- if init {
- c.Send(message.NewHelloMsg(peerid), sig)
- target := util.NewPeerID(t.GetID())
-
c.Send(message.NewTransportPingMsg(target, tAddr), sig)
- } else {
-
c.Send(message.NewTransportTcpWelcomeMsg(peerid), sig)
- }
-
- case *message.HelloMsg:
-
- case *message.TransportPingMsg:
- mOut :=
message.NewTransportPongMsg(msg.Challenge, pAddr)
- if err := mOut.Sign(p.PrvKey()); err != nil {
- return err
- }
- c.Send(mOut, sig)
-
- case *message.TransportPongMsg:
- rc, err := msg.Verify(t.PubKey())
- if err != nil {
- return err
- }
- if !rc {
- return errors.New("PONG verification
failed")
- }
- send[message.TRANSPORT_PONG] = true
- if mOut, ok :=
pending[message.TRANSPORT_SESSION_SYN]; ok {
- c.Send(mOut, sig)
- }
-
- case *message.SessionSynMsg:
- mOut := message.NewSessionSynAckMsg()
- mOut.Timestamp = msg.Timestamp
- if send[message.TRANSPORT_PONG] {
- c.Send(mOut, sig)
- } else {
- pending[message.TRANSPORT_SESSION_SYN]
= mOut
- }
-
- case *message.SessionQuotaMsg:
- c.SetBandwidth(msg.Quota)
-
- case *message.SessionAckMsg:
-
- case *message.SessionKeepAliveMsg:
-
c.Send(message.NewSessionKeepAliveRespMsg(msg.Nonce), sig)
-
- case *message.EphemeralKeyMsg:
- rc, err := msg.Verify(t.PubKey())
- if err != nil {
- return err
- }
- if !rc {
- return errors.New("EPHKEY verification
failed")
- }
- t.SetEphKeyMsg(msg)
- c.Send(p.EphKeyMsg(), sig)
- secret := crypto.SharedSecret(p.EphPrvKey(),
t.EphKeyMsg().Public())
- c.SharedSecret(util.Clone(secret.Bits[:]))
-
- default:
- fmt.Printf("!!! %v\n", msg)
- }
- default:
- }
- }
- return nil
-}
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 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package main
-
-import (
- "context"
- "encoding/hex"
- "flag"
- "log"
- "os"
- "os/signal"
- "sync"
- "syscall"
-
- "gnunet/service/revocation"
- "gnunet/util"
-
- "github.com/bfix/gospel/data"
-)
-
-func main() {
- log.Println("*** Compute revocation data for a zone key")
- log.Println("*** Copyright (c) 2020, Bernd Fix >Y<")
- log.Println("*** This is free software distributed under the Affero GPL
v3.")
-
- // handle command line arguments
- var (
- verbose bool // be verbose with messages
- bits int // number of leading zero-bit requested
- zonekey string // zonekey to be revoked
- filename string // name of file for persistance
- )
- flag.IntVar(&bits, "b", 25, "Number of leading zero bits")
- flag.BoolVar(&verbose, "v", false, "verbose output")
- flag.StringVar(&zonekey, "z", "", "Zone key to be revoked")
- flag.StringVar(&filename, "f", "", "Name of file to store revocation")
- flag.Parse()
-
- if len(filename) == 0 {
- log.Fatal("Missing '-f' argument (filename fot revocation
data)")
- }
-
- // define layout of persistant data
- var revData struct {
- Rd *revocation.RevDataCalc // Revocation data
- T util.RelativeTime // time spend in calculations
- Last uint64 // last value used for PoW test
- Numbits uint8 // number of leading zero-bits
- }
- dataBuf := make([]byte, 450)
-
- // read revocation object from file
- file, err := os.Open(filename)
- cont := true
- if err != nil {
- if len(zonekey) != 52 {
- log.Fatal("Missing or invalid zonekey and no file
specified -- aborting")
- }
- keyData, err := util.DecodeStringToBinary(zonekey, 32)
- if err != nil {
- log.Fatal("Invalid zonekey: " + err.Error())
- }
- revData.Rd = revocation.NewRevDataCalc(keyData)
- revData.Numbits = uint8(bits)
- revData.T = util.NewRelativeTime(0)
- cont = false
- } else {
- n, err := file.Read(dataBuf)
- if err != nil {
- log.Fatal("Error reading file: " + err.Error())
- }
- if n != len(dataBuf) {
- log.Fatal("File corrupted -- aborting")
- }
- if err = data.Unmarshal(&revData, dataBuf); err != nil {
- log.Fatal("File corrupted: " + err.Error())
- }
- bits = int(revData.Numbits)
- if err = file.Close(); err != nil {
- log.Fatal("Error closing file: " + err.Error())
- }
- }
-
- if cont {
- log.Printf("Revocation calculation started at %s\n",
revData.Rd.Timestamp.String())
- log.Printf("Time spent on calculation: %s\n",
revData.T.String())
- log.Printf("Last tested PoW value: %d\n", revData.Last)
- log.Println("Continuing...")
- } else {
- log.Println("Starting new revocation calculation...")
- }
- log.Println("Press ^C to abort...")
-
- // pre-set difficulty
- log.Printf("Difficulty: %d\n", bits)
- if bits < 25 {
- log.Println("WARNING: difficulty is less than 25!")
- }
-
- // Start or continue calculation
- ctx, cancelFcn := context.WithCancel(context.Background())
- wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
- defer wg.Done()
- cb := func(average float64, last uint64) {
- log.Printf("Improved PoW: %f average zero bits, %d
steps\n", average, last)
- }
-
- startTime := util.AbsoluteTimeNow()
- average, last := revData.Rd.Compute(ctx, bits, revData.Last, cb)
- if average < float64(bits) {
- log.Printf("Incomplete revocation: Only %f zero bits on
average!\n", average)
- } else {
- log.Println("Revocation data object:")
- log.Println(" 0x" +
hex.EncodeToString(revData.Rd.Blob()))
- log.Println("Status:")
- rc := revData.Rd.Verify(false)
- switch {
- case rc == -1:
- log.Println(" Missing/invalid signature")
- case rc == -2:
- log.Println(" Expired revocation")
- case rc == -3:
- log.Println(" Wrong PoW sequence order")
- case rc < 25:
- log.Println(" Difficulty to small")
- default:
- log.Printf(" Difficulty: %d\n", rc)
- }
- }
- if !cont || last != revData.Last {
- revData.Last = last
- revData.T = util.AbsoluteTimeNow().Diff(startTime)
-
- log.Println("Writing revocation data to file...")
- file, err := os.Create(filename)
- if err != nil {
- log.Fatal("Can't write to output file: " +
err.Error())
- }
- buf, err := data.Marshal(&revData)
- if err != nil {
- log.Fatal("Internal error: " + err.Error())
- }
- if len(buf) != len(dataBuf) {
- log.Fatalf("Internal error: Buffer mismatch %d
!= %d", len(buf), len(dataBuf))
- }
- n, err := file.Write(buf)
- if err != nil {
- log.Fatal("Can't write to output file: " +
err.Error())
- }
- if n != len(dataBuf) {
- log.Fatal("Can't write data to output file!")
- }
- if err = file.Close(); err != nil {
- log.Fatal("Error closing file: " + err.Error())
- }
- }
- }()
-
- go func() {
- // handle OS signals
- sigCh := make(chan os.Signal, 5)
- signal.Notify(sigCh)
- loop:
- for {
- select {
- // handle OS signals
- case sig := <-sigCh:
- switch sig {
- case syscall.SIGKILL, syscall.SIGINT,
syscall.SIGTERM:
- log.Printf("Terminating (on signal
'%s')\n", sig)
- cancelFcn()
- break loop
- case syscall.SIGHUP:
- log.Println("SIGHUP")
- case syscall.SIGURG:
- // TODO:
https://github.com/golang/go/issues/37942
- default:
- log.Println("Unhandled signal: " +
sig.String())
- }
- }
- }
- }()
- wg.Wait()
-}
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 @@
+#!/bin/bash
+
+go install -v -gcflags "-N -l" ./...
diff --git a/src/cmd/.gitignore b/src/gnunet/cmd/.gitignore
similarity index 100%
rename from src/cmd/.gitignore
rename to src/gnunet/cmd/.gitignore
diff --git a/src/cmd/gnunet-service-gns-go/main.go
b/src/gnunet/cmd/gnunet-service-dht-test-go/main.go
similarity index 55%
copy from src/cmd/gnunet-service-gns-go/main.go
copy to src/gnunet/cmd/gnunet-service-dht-test-go/main.go
index 57eec7e..cd2bd1a 100644
--- a/src/cmd/gnunet-service-gns-go/main.go
+++ b/src/gnunet/cmd/gnunet-service-dht-test-go/main.go
@@ -1,5 +1,5 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -28,71 +28,93 @@ import (
"time"
"gnunet/config"
+ "gnunet/core"
"gnunet/rpc"
"gnunet/service"
- "gnunet/service/gns"
+ "gnunet/service/dht"
"github.com/bfix/gospel/logger"
)
func main() {
defer func() {
- logger.Println(logger.INFO, "[gns] Bye.")
+ logger.Println(logger.INFO, "[dht] Bye.")
// flush last messages
logger.Flush()
}()
- logger.Println(logger.INFO, "[gns] Starting service...")
+ logger.Println(logger.INFO, "[dht] Starting service...")
var (
cfgFile string
- srvEndp string
+ socket string
+ param string
err error
logLevel int
rpcEndp string
)
// handle command line arguments
flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet
configuration file")
- flag.StringVar(&srvEndp, "s", "", "GNS service end-point")
- flag.IntVar(&logLevel, "L", logger.INFO, "GNS log level (default:
INFO)")
+ flag.StringVar(&socket, "s", "", "GNS service socket")
+ flag.StringVar(¶m, "p", "", "socket parameters (<key>=<value>,...)")
+ flag.IntVar(&logLevel, "L", logger.INFO, "DHT log level (default:
INFO)")
flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
flag.Parse()
// read configuration file and set missing arguments.
if err = config.ParseConfig(cfgFile); err != nil {
- logger.Printf(logger.ERROR, "[gns] Invalid configuration file:
%s\n", err.Error())
+ logger.Printf(logger.ERROR, "[dht] Invalid configuration file:
%s\n", err.Error())
return
}
// apply configuration
logger.SetLogLevel(logLevel)
- if len(srvEndp) == 0 {
- srvEndp = config.Cfg.GNS.Endpoint
+ if len(socket) == 0 {
+ socket = config.Cfg.GNS.Service.Socket
+ }
+ params := make(map[string]string)
+ if len(param) == 0 {
+ for _, p := range strings.Split(param, ",") {
+ kv := strings.SplitN(p, "=", 2)
+ params[kv[0]] = kv[1]
+ }
+ } else {
+ params = config.Cfg.GNS.Service.Params
+ }
+
+ // instantiate core service
+ ctx, cancel := context.WithCancel(context.Background())
+ var local *core.Peer
+ if local, err = core.NewLocalPeer(config.Cfg.Local); err != nil {
+ logger.Printf(logger.ERROR, "[dht] No local peer: %s\n",
err.Error())
+ return
+ }
+ var c *core.Core
+ if c, err = core.NewCore(ctx, local); err != nil {
+ logger.Printf(logger.ERROR, "[dht] core failed: %s\n",
err.Error())
+ return
}
- // start a new GNS service
- gns := gns.NewService()
- srv := service.NewServiceImpl("gns", gns)
- if err = srv.Start(srvEndp); err != nil {
- logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error())
+ // start a new DHT service
+ dht := dht.NewService(ctx, c)
+ srv := service.NewSocketHandler("dht", dht)
+ if err = srv.Start(ctx, socket, params); err != nil {
+ logger.Printf(logger.ERROR, "[dht] Failed to start DHT service:
'%s'", err.Error())
return
}
// start JSON-RPC server on request
- var cancel func() = func() {}
if len(rpcEndp) > 0 {
- var ctx context.Context
- ctx, cancel = context.WithCancel(context.Background())
- parts := strings.Split(rpcEndp, "+")
+ parts := strings.Split(rpcEndp, ":")
if parts[0] != "tcp" {
- logger.Println(logger.ERROR, "[gns] RPC must have a
TCP/IP endpoint")
+ logger.Println(logger.ERROR, "[dht] RPC must have a
TCP/IP endpoint")
return
}
config.Cfg.RPC.Endpoint = parts[1]
if err = rpc.Start(ctx); err != nil {
- logger.Printf(logger.ERROR, "[gns] RPC failed to start:
%s", err.Error())
+ logger.Printf(logger.ERROR, "[dht] RPC failed to start:
%s", err.Error())
return
}
- rpc.Register(gns)
+ rpc.Register(dht)
}
// handle OS signals
@@ -109,18 +131,18 @@ loop:
case sig := <-sigCh:
switch sig {
case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
- logger.Printf(logger.INFO, "[gns] Terminating
service (on signal '%s')\n", sig)
+ logger.Printf(logger.INFO, "[dht] Terminating
service (on signal '%s')\n", sig)
break loop
case syscall.SIGHUP:
- logger.Println(logger.INFO, "[gns] SIGHUP")
+ logger.Println(logger.INFO, "[dht] SIGHUP")
case syscall.SIGURG:
// TODO:
https://github.com/golang/go/issues/37942
default:
- logger.Println(logger.INFO, "[gns] Unhandled
signal: "+sig.String())
+ logger.Println(logger.INFO, "[dht] Unhandled
signal: "+sig.String())
}
// handle heart beat
case now := <-tick.C:
- logger.Println(logger.INFO, "[gns] Heart beat at
"+now.String())
+ logger.Println(logger.INFO, "[dht] Heart beat at
"+now.String())
}
}
diff --git a/src/cmd/gnunet-service-gns-go/main.go
b/src/gnunet/cmd/gnunet-service-gns-go/main.go
similarity index 81%
rename from src/cmd/gnunet-service-gns-go/main.go
rename to 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() {
var (
cfgFile string
- srvEndp string
+ socket string
+ param string
err error
logLevel int
rpcEndp string
)
// handle command line arguments
flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet
configuration file")
- flag.StringVar(&srvEndp, "s", "", "GNS service end-point")
+ flag.StringVar(&socket, "s", "", "GNS service socket")
+ flag.StringVar(¶m, "p", "", "socket parameters (<key>=<value>,...)")
flag.IntVar(&logLevel, "L", logger.INFO, "GNS log level (default:
INFO)")
flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
flag.Parse()
@@ -63,26 +65,33 @@ func main() {
return
}
- // apply configuration
+ // apply configuration (from file and command-line)
logger.SetLogLevel(logLevel)
- if len(srvEndp) == 0 {
- srvEndp = config.Cfg.GNS.Endpoint
+ if len(socket) == 0 {
+ socket = config.Cfg.GNS.Service.Socket
+ }
+ params := make(map[string]string)
+ if len(param) == 0 {
+ for _, p := range strings.Split(param, ",") {
+ kv := strings.SplitN(p, "=", 2)
+ params[kv[0]] = kv[1]
+ }
+ } else {
+ params = config.Cfg.GNS.Service.Params
}
// start a new GNS service
+ ctx, cancel := context.WithCancel(context.Background())
gns := gns.NewService()
- srv := service.NewServiceImpl("gns", gns)
- if err = srv.Start(srvEndp); err != nil {
+ srv := service.NewSocketHandler("gns", gns)
+ if err = srv.Start(ctx, socket, params); err != nil {
logger.Printf(logger.ERROR, "[gns] Error: '%s'", err.Error())
return
}
// start JSON-RPC server on request
- var cancel func() = func() {}
if len(rpcEndp) > 0 {
- var ctx context.Context
- ctx, cancel = context.WithCancel(context.Background())
- parts := strings.Split(rpcEndp, "+")
+ parts := strings.Split(rpcEndp, ":")
if parts[0] != "tcp" {
logger.Println(logger.ERROR, "[gns] RPC must have a
TCP/IP endpoint")
return
diff --git a/src/cmd/gnunet-service-revocation-go/main.go
b/src/gnunet/cmd/gnunet-service-revocation-go/main.go
similarity index 83%
rename from src/cmd/gnunet-service-revocation-go/main.go
rename to 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() {
var (
cfgFile string
- srvEndp string
+ socket string
+ param string
err error
logLevel int
rpcEndp string
)
// handle command line arguments
flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet
configuration file")
- flag.StringVar(&srvEndp, "s", "", "REVOCATION service end-point")
+ flag.StringVar(&socket, "s", "", "GNS service socket")
+ flag.StringVar(¶m, "p", "", "socket parameters (<key>=<value>,...)")
flag.IntVar(&logLevel, "L", logger.INFO, "REVOCATION log level
(default: INFO)")
flag.StringVar(&rpcEndp, "R", "", "JSON-RPC endpoint (default: none)")
flag.Parse()
@@ -65,24 +67,31 @@ func main() {
// apply configuration
logger.SetLogLevel(logLevel)
- if len(srvEndp) == 0 {
- srvEndp = config.Cfg.GNS.Endpoint
+ if len(socket) == 0 {
+ socket = config.Cfg.GNS.Service.Socket
+ }
+ params := make(map[string]string)
+ if len(param) == 0 {
+ for _, p := range strings.Split(param, ",") {
+ kv := strings.SplitN(p, "=", 2)
+ params[kv[0]] = kv[1]
+ }
+ } else {
+ params = config.Cfg.GNS.Service.Params
}
// start a new REVOCATION service
+ ctx, cancel := context.WithCancel(context.Background())
rvc := revocation.NewService()
- srv := service.NewServiceImpl("revocation", rvc)
- if err = srv.Start(srvEndp); err != nil {
+ srv := service.NewSocketHandler("revocation", rvc)
+ if err = srv.Start(ctx, socket, params); err != nil {
logger.Printf(logger.ERROR, "[revocation] Error: '%s'\n",
err.Error())
return
}
// start JSON-RPC server on request
- var cancel func() = func() {}
if len(rpcEndp) > 0 {
- var ctx context.Context
- ctx, cancel = context.WithCancel(context.Background())
- parts := strings.Split(rpcEndp, "+")
+ parts := strings.Split(rpcEndp, ":")
if parts[0] != "tcp" {
logger.Println(logger.ERROR, "[revocation] RPC must
have a TCP/IP endpoint")
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 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "gnunet/config"
+ "gnunet/core"
+ "gnunet/crypto"
+ "gnunet/message"
+ "gnunet/service"
+
+ "github.com/bfix/gospel/logger"
+)
+
+var (
+ // configuration for local node
+ localCfg = &config.NodeConfig{
+ PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
+ Endpoints: []string{
+ "udp:127.0.0.1:2086",
+ },
+ }
+ // configuration for remote node
+ remoteCfg = "3GXXMNb5YpIUO7ejIR2Yy0Cf5texuLfDjHkXcqbPxkc="
+ remoteAddr = "udp:172.17.0.5:2086"
+
+ // top-level variables used accross functions
+ local *core.Peer // local peer (with private key)
+ remote *core.Peer // remote peer
+ c *core.Core
+ secret *crypto.HashCode
+)
+
+func main() {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ // handle command line arguments
+ var (
+ asServer bool
+ err error
+ )
+ flag.BoolVar(&asServer, "s", false, "wait for incoming connections")
+ flag.Parse()
+
+ // setup peer and core instances
+ if local, err = core.NewLocalPeer(localCfg); err != nil {
+ fmt.Println("local failed: " + err.Error())
+ return
+ }
+ if c, err = core.NewCore(ctx, local); err != nil {
+ fmt.Println("core failed: " + err.Error())
+ return
+ }
+ if remote, err = core.NewPeer(remoteCfg); err != nil {
+ fmt.Println("remote failed: " + err.Error())
+ return
+ }
+
+
fmt.Println("======================================================================")
+ fmt.Println("GNUnet peer mock-up (EXPERIMENTAL) (c) 2018-2022 by
Bernd Fix, >Y<")
+ fmt.Printf(" Identity '%s'\n", local.GetIDString())
+ fmt.Printf(" [%s]\n", local.GetID().String())
+
fmt.Println("======================================================================")
+
+ // handle messages coming from network
+ module := service.NewModuleImpl()
+ listener := module.Run(ctx, process, nil)
+ c.Register("mockup", listener)
+
+ if !asServer {
+ // we start the message exchange
+ c.Send(ctx, remote.GetID(),
message.NewTransportTCPWelcomeMsg(c.PeerID()))
+ }
+
+ // handle OS signals
+ sigCh := make(chan os.Signal, 5)
+ signal.Notify(sigCh)
+
+ // heart beat
+ tick := time.NewTicker(5 * time.Minute)
+
+loop:
+ for {
+ select {
+ // handle OS signals
+ case sig := <-sigCh:
+ switch sig {
+ case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM:
+ logger.Printf(logger.INFO, "Terminating service
(on signal '%s')\n", sig)
+ break loop
+ case syscall.SIGHUP:
+ logger.Println(logger.INFO, "SIGHUP")
+ case syscall.SIGURG:
+ // TODO:
https://github.com/golang/go/issues/37942
+ default:
+ logger.Println(logger.INFO, "Unhandled signal:
"+sig.String())
+ }
+ // handle heart beat
+ case now := <-tick.C:
+ logger.Println(logger.INFO, "Heart beat at
"+now.String())
+ }
+ }
+ // terminate pending routines
+ cancel()
+}
+
+// process incoming messages and send responses; it is used for protocol
exploration only.
+// it tries to mimick the message flow between "real" GNUnet peers.
+func process(ctx context.Context, ev *core.Event) {
+
+ logger.Printf(logger.DBG, "<<< %s", ev.Msg.String())
+
+ switch msg := ev.Msg.(type) {
+
+ case *message.TransportTCPWelcomeMsg:
+ c.Send(ctx, ev.Peer, message.NewTransportPingMsg(ev.Peer, nil))
+
+ case *message.HelloMsg:
+
+ case *message.TransportPingMsg:
+ mOut := message.NewTransportPongMsg(msg.Challenge, nil)
+ if err := mOut.Sign(local.PrvKey()); err != nil {
+ logger.Println(logger.ERROR, "PONG: signing failed")
+ return
+ }
+ c.Send(ctx, ev.Peer, mOut)
+ logger.Printf(logger.DBG, ">>> %s", mOut)
+
+ case *message.TransportPongMsg:
+ rc, err := msg.Verify(remote.PubKey())
+ if err != nil {
+ logger.Println(logger.ERROR, "PONG verification:
"+err.Error())
+ }
+ if !rc {
+ logger.Println(logger.ERROR, "PONG verification failed")
+ }
+
+ case *message.SessionSynMsg:
+ mOut := message.NewSessionSynAckMsg()
+ mOut.Timestamp = msg.Timestamp
+ c.Send(ctx, ev.Peer, mOut)
+ logger.Printf(logger.DBG, ">>> %s", mOut)
+
+ case *message.SessionQuotaMsg:
+
+ case *message.SessionAckMsg:
+
+ case *message.SessionKeepAliveMsg:
+ mOut := message.NewSessionKeepAliveRespMsg(msg.Nonce)
+ c.Send(ctx, ev.Peer, mOut)
+ logger.Printf(logger.DBG, ">>> %s", mOut)
+
+ case *message.EphemeralKeyMsg:
+ rc, err := msg.Verify(remote.PubKey())
+ if err != nil {
+ logger.Println(logger.ERROR, "EPHKEY verification:
"+err.Error())
+ return
+ } else if !rc {
+ logger.Println(logger.ERROR, "EPHKEY verification
failed")
+ return
+ }
+ remote.SetEphKeyMsg(msg)
+ mOut := local.EphKeyMsg()
+ c.Send(ctx, ev.Peer, mOut)
+ logger.Printf(logger.DBG, ">>> %s", mOut)
+ secret = crypto.SharedSecret(local.EphPrvKey(),
remote.EphKeyMsg().Public())
+
+ default:
+ fmt.Printf("!!! %v\n", msg)
+ }
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package main
+
+import (
+ "context"
+ "encoding/base64"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+
+ "gnunet/crypto"
+ "gnunet/service/revocation"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/data"
+)
+
+//----------------------------------------------------------------------
+// Data structure used to calculate a valid revocation for a given
+// zone key.
+//----------------------------------------------------------------------
+
+// State of RevData calculation
+const (
+ S_NEW = iota // start new PoW calculation
+ S_CONT // continue PoW calculation
+ S_DONE // PoW calculation done
+ S_SIGNED // revocation data signed
+)
+
+// RevData is the storage layout for persistent data used by this program.
+// Data is read from and written to a file
+type RevData struct {
+ Rd *revocation.RevDataCalc `` // Revocation data
+ T util.RelativeTime `` // time spend in
calculations
+ Last uint64 `order:"big"` // last value used for
PoW test
+ Numbits uint8 `` // number of leading
zero-bits (difficulty)
+ State uint8 `` // processing state
+}
+
+// ReadRevData restores revocation data from perstistent storage. If no
+// stored data is found, a new revocation data structure is returned.
+func ReadRevData(filename string, bits int, zk *crypto.ZoneKey) (rd *RevData,
err error) {
+ // create new initialized revocation instance with no PoWs.
+ rd = &RevData{
+ Rd: revocation.NewRevDataCalc(zk),
+ Numbits: uint8(bits),
+ T: util.NewRelativeTime(0),
+ State: S_NEW,
+ }
+
+ // read revocation object from file. If the file does not exist, a new
+ // calculation is started; otherwise the old calculation will continue.
+ var file *os.File
+ if file, err = os.Open(filename); err != nil {
+ return
+ }
+ // read existing file
+ dataBuf := make([]byte, rd.size())
+ var n int
+ if n, err = file.Read(dataBuf); err != nil {
+ err = fmt.Errorf("Error reading file: " + err.Error())
+ return
+ }
+ if n != len(dataBuf) {
+ err = fmt.Errorf("File size mismatch")
+ return
+ }
+ if err = data.Unmarshal(&rd, dataBuf); err != nil {
+ err = fmt.Errorf("File corrupted: " + err.Error())
+ return
+ }
+ if !zk.Equal(&rd.Rd.RevData.ZoneKeySig.ZoneKey) {
+ err = fmt.Errorf("Zone key mismatch")
+ return
+ }
+ bits = int(rd.Numbits)
+ if err = file.Close(); err != nil {
+ err = fmt.Errorf("Error closing file: " + err.Error())
+ }
+ return
+}
+
+// Write revocation data to file
+func (r *RevData) Write(filename string) (err error) {
+ var file *os.File
+ if file, err = os.Create(filename); err != nil {
+ return fmt.Errorf("Can't write to output file: " + err.Error())
+ }
+ var buf []byte
+ if buf, err = data.Marshal(r); err != nil {
+ return fmt.Errorf("Internal error: " + err.Error())
+ }
+ if len(buf) != r.size() {
+ return fmt.Errorf("Internal error: Buffer mismatch %d != %d",
len(buf), r.size())
+ }
+ var n int
+ if n, err = file.Write(buf); err != nil {
+ return fmt.Errorf("Can't write to output file: " + err.Error())
+ }
+ if n != len(buf) {
+ return fmt.Errorf("Can't write data to output file!")
+ }
+ if err = file.Close(); err != nil {
+ return fmt.Errorf("Error closing file: " + err.Error())
+ }
+ return
+}
+
+// size of the RevData instance in bytes.
+func (r *RevData) size() int {
+ return 18 + r.Rd.Size()
+}
+
+// revoke-zonekey generates a revocation message in a multi-step/multi-state
+// process run stand-alone from other GNUnet services:
+//
+// (1) Generate the desired PoWs for the public zone key:
+// This process can be started, stopped and resumed, so the long
+// calculation time (usually days or even weeks) can be interruped if
+// desired. For security reasons you should only pass the "-z" argument to
+// this step but not the "-k" argument (private key) as it is not required
+// to calculate the PoWs.
+//
+//
+// (2) A fully generated PoW set can be signed with the private key to create
+// the final revocation data to be send out. This requires to pass the "-k"
+// and "-z" argument.
+//
+// The two steps can be run (sequentially) on separate machines; step one
requires
+// computing power nd memory and step two requires a trusted environment.
+func main() {
+ log.Println("*** Compute revocation data for a zone key")
+ log.Println("*** Copyright (c) 2020-2022, Bernd Fix >Y<")
+ log.Println("*** This is free software distributed under the Affero GPL
v3.")
+
+ //------------------------------------------------------------------
+ // handle command line arguments
+ //------------------------------------------------------------------
+ var (
+ verbose bool // be verbose with messages
+ bits int // number of leading zero-bit requested
+ zonekey string // zonekey to be revoked
+ prvkey string // private zonekey (base64-encoded key data)
+ testing bool // test mode (no minimum difficulty)
+ filename string // name of file for persistance
+ )
+ minDiff := revocation.MinDifficulty
+ flag.IntVar(&bits, "b", minDiff+1, "Number of leading zero bits")
+ flag.StringVar(&zonekey, "z", "", "Zone key to be revoked (zone ID)")
+ flag.StringVar(&prvkey, "k", "", "Private zone key (base54-encoded)")
+ flag.StringVar(&filename, "f", "", "Name of file to store revocation")
+ flag.BoolVar(&verbose, "v", false, "verbose output")
+ flag.BoolVar(&testing, "t", false, "test-mode only")
+ flag.Parse()
+
+ // check arguments (difficulty, zonekey and filename)
+ if bits < minDiff {
+ if testing {
+ log.Printf("WARNING: difficulty is less than %d!",
minDiff)
+ } else {
+ log.Printf("INFO: difficulty set to %d (required
minimum)", minDiff)
+ bits = minDiff
+ }
+ }
+ if len(filename) == 0 {
+ log.Fatal("Missing '-f' argument (filename for revocation
data)")
+ }
+
+ //------------------------------------------------------------------
+ // Handle zone keys.
+ //------------------------------------------------------------------
+ var (
+ keyData []byte // binary key data
+ zk *crypto.ZoneKey // GNUnet zone key
+ sk *crypto.ZonePrivate // GNUnet private zone key
+ err error
+ )
+ // reconstruct public key
+ if keyData, err = util.DecodeStringToBinary(zonekey, 32); err != nil {
+ log.Fatal("Invalid zonekey encoding: " + err.Error())
+ }
+ if zk, err = crypto.NewZoneKey(keyData); err != nil {
+ log.Fatal("Invalid zonekey format: " + err.Error())
+ }
+ // reconstruct private key (optional)
+ if len(prvkey) > 0 {
+ if keyData, err = base64.StdEncoding.DecodeString(prvkey); err
!= nil {
+ log.Fatal("Invalid private zonekey encoding: " +
err.Error())
+ }
+ if sk, err = crypto.NewZonePrivate(zk.Type, keyData); err !=
nil {
+ log.Fatal("Invalid zonekey format: " + err.Error())
+ }
+ // verify consistency
+ if !zk.Equal(sk.Public()) {
+ log.Fatal("Public and private zone keys don't match.")
+ }
+ }
+
+ //------------------------------------------------------------------
+ // Read revocation data from file to continue calculation or to sign
+ // the revocation. If no file exists, a new (empty) instance is
+ // returned.
+ //------------------------------------------------------------------
+ rd, err := ReadRevData(filename, bits, zk)
+
+ // handle revocation data state
+ switch rd.State {
+ case S_NEW:
+ log.Println("Starting new revocation calculation...")
+ rd.State = S_CONT
+
+ case S_CONT:
+ log.Printf("Revocation calculation started at %s\n",
rd.Rd.Timestamp.String())
+ log.Printf("Time spent on calculation: %s\n", rd.T.String())
+ log.Printf("Last tested PoW value: %d\n", rd.Last)
+ log.Println("Continuing...")
+
+ case S_DONE:
+ // calculation complete: sign with private key
+ if sk == nil {
+ log.Fatal("Need to sign revocation: private key is
missing.")
+ }
+ log.Println("Signing revocation with private key")
+ if err := rd.Rd.Sign(sk); err != nil {
+ log.Fatal("Failed to sign revocation: " + err.Error())
+ }
+ // write final revocation
+ rd.State = S_SIGNED
+ if err = rd.Write(filename); err != nil {
+ log.Fatal("Failed to write revocation: " + err.Error())
+ }
+ log.Println("Revocation complete and ready for (later) use.")
+ return
+ }
+ // Continue (or start) calculation
+ log.Println("Press ^C to abort...")
+ log.Printf("Difficulty: %d\n", bits)
+
+ ctx, cancelFcn := context.WithCancel(context.Background())
+ wg := new(sync.WaitGroup)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ // show progress messages
+ cb := func(average float64, last uint64) {
+ log.Printf("Improved PoW: %.2f average zero bits, %d
steps\n", average, last)
+ }
+
+ // calculate revocation data until the required difficulty is
met
+ // or the process is terminated by the user (by pressing ^C).
+ startTime := util.AbsoluteTimeNow()
+ average, last := rd.Rd.Compute(ctx, bits, rd.Last, cb)
+
+ // check achieved diffiulty (average)
+ if average < float64(bits) {
+ // The calculation was interrupted; we still need to
compute
+ // more and better PoWs...
+ log.Printf("Incomplete revocation: Only %f zero bits on
average!\n", average)
+ rd.State = S_CONT
+ } else {
+ // we have reached the required PoW difficulty
+ rd.State = S_DONE
+ // check if we have a valid revocation.
+ log.Println("Revocation calculation complete:")
+ diff, rc := rd.Rd.Verify(false)
+ switch {
+ case rc == -1:
+ log.Println(" Missing/invalid signature")
+ case rc == -2:
+ log.Println(" Expired revocation")
+ case rc == -3:
+ log.Println(" Wrong PoW sequence order")
+ case diff < float64(revocation.MinAvgDifficulty):
+ log.Println(" Difficulty to small")
+ default:
+ log.Printf(" Difficulty is %.2f\n", diff)
+ }
+ }
+ // update elapsed time
+ rd.T.Add(util.AbsoluteTimeNow().Diff(startTime))
+ rd.Last = last
+
+ log.Println("Writing revocation data to file...")
+ if err = rd.Write(filename); err != nil {
+ log.Fatal("Can't write to file: " + err.Error())
+ }
+ }()
+
+ go func() {
+ // handle OS signals
+ sigCh := make(chan os.Signal, 5)
+ signal.Notify(sigCh)
+ loop:
+ for {
+ select {
+ // handle OS signals
+ case sig := <-sigCh:
+ switch sig {
+ case syscall.SIGKILL, syscall.SIGINT,
syscall.SIGTERM:
+ log.Printf("Terminating (on signal
'%s')\n", sig)
+ cancelFcn()
+ break loop
+ case syscall.SIGHUP:
+ log.Println("SIGHUP")
+ case syscall.SIGURG:
+ // TODO:
https://github.com/golang/go/issues/37942
+ default:
+ log.Println("Unhandled signal: " +
sig.String())
+ }
+ }
+ }
+ }()
+ wg.Wait()
+}
diff --git a/src/cmd/vanityid/main.go b/src/gnunet/cmd/vanityid/main.go
similarity index 100%
rename from src/cmd/vanityid/main.go
rename to 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 (
"github.com/bfix/gospel/logger"
)
-///////////////////////////////////////////////////////////////////////
+//----------------------------------------------------------------------
+// Configuration for local node
+//----------------------------------------------------------------------
+
+// NodeConfig holds parameters for the local node instance
+type NodeConfig struct {
+ PrivateSeed string `json:"privateSeed"` // Node private key seed
(base64)
+ Endpoints []string `json:"endpoints"` // list of endpoints available
+}
+
+//----------------------------------------------------------------------
+// Bootstrap configuration
+//----------------------------------------------------------------------
+
+// BootstrapConfig holds parameters for the initial connection to the network.
+type BootstrapConfig struct {
+ Nodes []string `json:"nodes"` // bootstrap nodes
+}
+
+//----------------------------------------------------------------------
// RPC configuration
+//----------------------------------------------------------------------
// RPCConfig contains parameters for the JSON-RPC service
type RPCConfig struct {
- Endpoint string `json:"endpoint"` // end-point of JSON-RPC service
+ Endpoint string `json:"endpoint"` // endpoint for JSON-RPC service
+}
+
+//----------------------------------------------------------------------
+// Generic service endpoint configuration (socket)
+//----------------------------------------------------------------------
+
+type ServiceConfig struct {
+ Socket string `json:"socket"` // socket file name
+ Params map[string]string `json:"params"` // socket parameters
}
-///////////////////////////////////////////////////////////////////////
+//----------------------------------------------------------------------
// GNS configuration
+//----------------------------------------------------------------------
// GNSConfig contains parameters for the GNU Name System service
type GNSConfig struct {
- Endpoint string `json:"endpoint"` // end-point of GNS service
- DHTReplLevel int `json:"dhtReplLevel"` // DHT replication level
- MaxDepth int `json:"maxDepth"` // maximum recursion depth in
resolution
+ Service *ServiceConfig `json:"service"` // socket for GNS
service
+ DHTReplLevel int `json:"dhtReplLevel"` // DHT replication
level
+ MaxDepth int `json:"maxDepth"` // maximum recursion
depth in resolution
}
-///////////////////////////////////////////////////////////////////////
+//----------------------------------------------------------------------
// DHT configuration
+//----------------------------------------------------------------------
// DHTConfig contains parameters for the distributed hash table (DHT)
type DHTConfig struct {
- Endpoint string `json:"endpoint"` // end-point of DHT service
+ Service *ServiceConfig `json:"service"` // socket for DHT service
+ Storage string `json:"storage"` // filesystem storage location
+ Cache string `json:"cache"` // key/value cache
}
-///////////////////////////////////////////////////////////////////////
+//----------------------------------------------------------------------
// Namecache configuration
+//----------------------------------------------------------------------
// NamecacheConfig contains parameters for the local name cache
type NamecacheConfig struct {
- Endpoint string `json:"endpoint"` // end-point of Namecache service
+ Service *ServiceConfig `json:"service"` // socket for Namecache service
+ Storage string `json:"storage"` // key/value cache
}
-///////////////////////////////////////////////////////////////////////
+//----------------------------------------------------------------------
// Revocation configuration
+//----------------------------------------------------------------------
// RevocationConfig contains parameters for the key revocation service
type RevocationConfig struct {
- Endpoint string `json:"endpoint"` // end-point of Revocation service
- Storage string `json:"storage"` // persistance mechanism for
revocation data
+ Service *ServiceConfig `json:"service"` // socket for Revocation service
+ Storage string `json:"storage"` // persistance mechanism for
revocation data
}
-///////////////////////////////////////////////////////////////////////
+//----------------------------------------------------------------------
+// Combined configuration
+//----------------------------------------------------------------------
// Environment settings
type Environment map[string]string
// Config is the aggregated configuration for GNUnet.
type Config struct {
+ Local *NodeConfig `json:"local"`
+ Bootstrap *BootstrapConfig `json:"bootstrap"`
Env Environment `json:"environ"`
RPC *RPCConfig `json:"rpc"`
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 @@
{
+ "local": {
+ "privateSeed": "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
+ "endpoints": [
+ "r5n+ip+udp:127.0.0.1:6666"
+ ]
+ },
+ "bootstrap": {
+ "nodes": [
+
"gnunet://hello/7KTBJ90340HF1Q2GB0A57E2XJER4FDHX8HP5GHEB9125VPWPD27G/BNMDFN6HJCPWSPNBSEC06MC1K8QN1Z2DHRQSRXDTFR7FTBD4JHNBJ2RJAAEZ31FWG1Q3PMN3PXGZQ3Q7NTNEKQZFA7TE2Y46FM8E20R/1653499308?r5n%2Bip%2Budp%3A127.0.0.1%3A7654"
+ ]
+ },
"environ": {
"TMP": "/tmp",
"RT_SYS": "${TMP}/gnunet-system-runtime"
},
"dht": {
- "endpoint": "unix+${RT_SYS}/gnunet-service-dht.sock"
+ "service": {
+ "socket": "${RT_SYS}/gnunet-service-dht.sock",
+ "params": {
+ "perm": "0770"
+ }
+ },
+ "storage": "dht_file_store+/var/lib/gnunet/dht/store",
+ "cache": "dht_file_cache+/var/lib/gnunet/dht/cache+1000"
},
"gns": {
- "endpoint":
"unix+${RT_SYS}/gnunet-service-gns-go.sock+perm=0770",
+ "service": {
+ "socket": "${RT_SYS}/gnunet-service-gns-go.sock",
+ "params": {
+ "perm": "0770"
+ }
+ },
"dhtReplLevel": 10,
"maxDepth": 250
},
"namecache": {
- "endpoint": "unix+${RT_SYS}/gnunet-service-namecache.sock"
+ "service": {
+ "socket": "${RT_SYS}/gnunet-service-namecache.sock",
+ "params": {
+ "perm": "0770"
+ }
+ },
+ "storage": "dht_file_cache:/var/lib/gnunet/namecache:1000"
},
"revocation": {
- "endpoint":
"unix+${RT_SYS}/gnunet-service-revocation-go.sock+perm=0770",
- "storage": "redis+localhost:6397++15"
+ "service": {
+ "socket": "${RT_SYS}/gnunet-service-revocation-go.sock",
+ "params": {
+ "perm": "0770"
+ }
+ },
+ "storage": "redis:localhost:6397::15"
},
"rpc": {
- "endpoint": "tcp+127.0.0.1:80"
+ "endpoint": "tcp:127.0.0.1:80"
}
-}
+}
\ 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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package core
+
+import (
+ "context"
+ "gnunet/message"
+ "gnunet/service/dht/blocks"
+ "gnunet/transport"
+ "gnunet/util"
+ "net"
+ "time"
+
+ "github.com/bfix/gospel/data"
+)
+
+// Core service
+type Core struct {
+ // local peer instance
+ local *Peer
+
+ // incoming messages from transport
+ incoming chan *transport.TransportMessage
+
+ // reference to transport implementation
+ trans *transport.Transport
+
+ // registered listeners
+ listeners map[string]*Listener
+
+ // list of known peers with addresses
+ peers *util.PeerAddrList
+}
+
+//----------------------------------------------------------------------
+
+// NewCore creates and runs a new core instance.
+func NewCore(ctx context.Context, local *Peer) (c *Core, err error) {
+ // create new core instance
+ incoming := make(chan *transport.TransportMessage)
+ c = &Core{
+ local: local,
+ incoming: incoming,
+ listeners: make(map[string]*Listener),
+ trans: transport.NewTransport(ctx, incoming),
+ peers: util.NewPeerAddrList(),
+ }
+ // add all local peer endpoints to transport.
+ for _, addr := range local.addrList {
+ if _, err = c.trans.AddEndpoint(ctx, addr); err != nil {
+ return
+ }
+ }
+ // run message pump
+ go func() {
+ // wait for incoming messages
+ for {
+ select {
+ // get (next) message from transport
+ case tm := <-c.incoming:
+ var ev *Event
+
+ // inspect message for peer state events
+ m, err := tm.Message()
+ if err == nil {
+ switch msg := m.(type) {
+ case *message.HelloMsg:
+ // keep peer addresses
+ for _, addr := range
msg.Addresses {
+ a := &util.Address{
+ Netw:
addr.Transport,
+ Address:
addr.Address,
+ Expires:
addr.ExpireOn,
+ }
+ c.Learn(ctx,
msg.PeerID, a)
+ }
+ // generate EV_CONNECT event
+ ev = new(Event)
+ ev.ID = EV_CONNECT
+ ev.Peer = tm.Peer
+ ev.Msg = msg
+ c.dispatch(ev)
+ }
+ }
+ // generate EV_MESSAGE event
+ ev = new(Event)
+ ev.ID = EV_MESSAGE
+ ev.Peer = tm.Peer
+ ev.Msg, _ = tm.Message()
+ c.dispatch(ev)
+
+ // wait for termination
+ case <-ctx.Done():
+ return
+ }
+ }
+ }()
+ return
+}
+
+//----------------------------------------------------------------------
+
+// Send is a function that allows the local peer to send a protocol
+// message to a remote peer.
+func (c *Core) Send(ctx context.Context, peer *util.PeerID, msg
message.Message) error {
+ // TODO: select best endpoint protocol for transport; now fixed to UDP
+ netw := "udp"
+ addr := c.peers.Get(peer.String(), netw)
+ payload, err := data.Marshal(msg)
+ if err != nil {
+ return err
+ }
+ tm := transport.NewTransportMessage(c.PeerID(), payload)
+ return c.trans.Send(ctx, addr, tm)
+}
+
+// Learn a (new) address for peer
+func (c *Core) Learn(ctx context.Context, peer *util.PeerID, addr
*util.Address) (err error) {
+ if c.peers.Add(peer.String(), addr) == 1 {
+ // new peer id: send HELLO message to newly added peer
+ node := c.local
+ var hello *blocks.HelloBlock
+ hello, err = node.HelloData(time.Hour)
+ if err != nil {
+ return
+ }
+ msg := message.NewHelloMsg(node.GetID())
+ for _, a := range hello.Addresses() {
+ ha := message.NewHelloAddress(a)
+ msg.AddAddress(ha)
+ }
+ err = c.Send(ctx, peer, msg)
+ }
+ return
+}
+
+// PeerID returns the peer id of the local node.
+func (c *Core) PeerID() *util.PeerID {
+ return c.local.GetID()
+}
+
+// TryConnect is a function which allows the local peer to attempt the
+// establishment of a connection to another peer using an address.
+// When the connection attempt is successful, information on the new
+// peer is offered through the PEER_CONNECTED signal.
+func (c *Core) TryConnect(peer *util.PeerID, addr net.Addr) error {
+ // select endpoint for address
+ if ep := c.findEndpoint(peer, addr); ep == nil {
+ return transport.ErrTransNoEndpoint
+ }
+ return nil
+}
+
+func (c *Core) findEndpoint(peer *util.PeerID, addr net.Addr)
transport.Endpoint {
+ return nil
+}
+
+// Hold is a function which tells the underlay to keep a hold on to a
+// connection to a peer P. Underlays are usually limited in the number
+// of active connections. With this function the DHT can indicate to the
+// underlay which connections should preferably be preserved.
+func (c *Core) Hold(peer *util.PeerID) {}
+
+// Drop is a function which tells the underlay to drop the connection to a
+// peer P. This function is only there for symmetry and used during the
+// peer's shutdown to release all of the remaining HOLDs. As R5N always
+// prefers the longest-lived connections, it would never drop an active
+// connection that it has called HOLD() on before. Nevertheless, underlay
+// implementations should not rely on this always being true. A call to
+// DROP() also does not imply that the underlay must close the connection:
+// it merely removes the preference to preserve the connection that was
+// established by HOLD().
+func (c *Core) Drop(peer *util.PeerID) {}
+
+// L2NSE is ESTIMATE_NETWORK_SIZE(), a procedure that provides estimates
+// on the base-2 logarithm of the network size L2NSE, that is the base-2
+// logarithm number of peers in the network, for use by the routing
+// algorithm.
+func (c *Core) L2NSE() float64 {
+ return 0.
+}
+
+//----------------------------------------------------------------------
+// Event listener and event dispatch.
+//----------------------------------------------------------------------
+
+// Register a named event listener.
+func (c *Core) Register(name string, l *Listener) {
+ c.listeners[name] = l
+}
+
+// Unregister named event listener.
+func (c *Core) Unregister(name string) *Listener {
+ if l, ok := c.listeners[name]; ok {
+ delete(c.listeners, name)
+ return l
+ }
+ return nil
+}
+
+// internal: dispatch event to listeners
+func (c *Core) dispatch(ev *Event) {
+ // dispatch event to listeners
+ for _, l := range c.listeners {
+ if l.filter.CheckEvent(ev.ID) {
+ mt := ev.Msg.Header().MsgType
+ if ev.ID == EV_MESSAGE {
+ if mt != 0 && !l.filter.CheckMsgType(mt) {
+ // skip event
+ return
+ }
+ }
+ go func() {
+ l.ch <- ev
+ }()
+ }
+ }
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package core
+
+import (
+ "context"
+ "gnunet/config"
+ "gnunet/util"
+ "testing"
+ "time"
+)
+
+var (
+ peer1Cfg = &config.NodeConfig{
+ PrivateSeed: "iYK1wSi5XtCP774eNFk1LYXqKlOPEpwKBw+2/bMkE24=",
+ Endpoints: []string{"udp://127.0.0.1:20861"},
+ }
+
+ peer2Cfg = &config.NodeConfig{
+ PrivateSeed: "Bv9umksEO51jjWWrOGEH+4r8wl9Vi+LItpdBpTOi2PE=",
+ Endpoints: []string{"udp://127.0.0.1:20862"},
+ }
+)
+
+//----------------------------------------------------------------------
+// create and run a node with given spec
+//----------------------------------------------------------------------
+
+type TestNode struct {
+ id int
+ t *testing.T
+ peer *Peer
+ core *Core
+ addr *util.Address
+}
+
+func (n *TestNode) Learn(ctx context.Context, peer *util.PeerID, addr
*util.Address) {
+ n.t.Logf("[%d] Learning %s for %s", n.id, addr.StringAll(),
peer.String())
+ n.core.Learn(ctx, peer, addr)
+}
+
+func NewTestNode(t *testing.T, ctx context.Context, cfg *config.NodeConfig)
(node *TestNode, err error) {
+
+ // create test node
+ node = new(TestNode)
+ node.t = t
+ node.id = util.NextID()
+
+ // create peer object
+ if node.peer, err = NewLocalPeer(cfg); err != nil {
+ return
+ }
+ t.Logf("[%d] Node %s starting", node.id, node.peer.GetID())
+
+ // create core service
+ if node.core, err = NewCore(ctx, node.peer); err != nil {
+ return
+ }
+ for _, addr := range node.core.trans.Endpoints() {
+ s := addr.Network() + ":" + addr.String()
+ if node.addr, err = util.ParseAddress(s); err != nil {
+ continue
+ }
+ t.Logf("[%d] Listening on %s", node.id, s)
+ }
+
+ // register as event listener
+ incoming := make(chan *Event)
+ node.core.Register("test", NewListener(incoming, nil))
+
+ // heart beat
+ tick := time.NewTicker(5 * time.Minute)
+
+ // run event handler
+ go func() {
+ for {
+ select {
+ // show incoming event
+ case ev := <-incoming:
+ switch ev.ID {
+ case EV_CONNECT:
+ t.Logf("[%d] <<< Peer %s connected",
node.id, ev.Peer)
+ case EV_DISCONNECT:
+ t.Logf("[%d] <<< Peer %s diconnected",
node.id, ev.Peer)
+ case EV_MESSAGE:
+ t.Logf("[%d] <<< Msg from %s of type
%d", node.id, ev.Peer, ev.Msg.Header().MsgType)
+ }
+
+ // handle termination signal
+ case <-ctx.Done():
+ t.Logf("[%d] Shutting down node", node.id)
+ return
+
+ // handle heart beat
+ case now := <-tick.C:
+ t.Logf("[%d] Heart beat at %s", node.id,
now.String())
+ }
+ }
+ }()
+ return
+}
+
+//----------------------------------------------------------------------
+// Two node GNUnet (smallest and simplest network)
+//----------------------------------------------------------------------
+
+// TestCoreSimple test a two node network
+func TestCoreSimple(t *testing.T) {
+
+ // setup execution context
+ ctx, cancel := context.WithCancel(context.Background())
+ defer func() {
+ cancel()
+ time.Sleep(time.Second)
+ }()
+
+ // create and run nodes
+ node1, err := NewTestNode(t, ctx, peer1Cfg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ node2, err := NewTestNode(t, ctx, peer2Cfg)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // learn peer addresses (triggers HELLO)
+ for _, addr := range node2.core.trans.Endpoints() {
+ node1.Learn(ctx, node2.peer.GetID(), util.NewAddressWrap(addr))
+ }
+
+ // wait for 5 seconds
+ time.Sleep(5 * time.Second)
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package core
+
+import (
+ "gnunet/message"
+ "gnunet/util"
+)
+
+//----------------------------------------------------------------------
+// Core events and listeners
+//----------------------------------------------------------------------
+
+// Event types
+const (
+ EV_CONNECT = iota // peer connected
+ EV_DISCONNECT // peer disconnected
+ EV_MESSAGE // incoming message
+)
+
+// EventFilter is a filter for events a listener is interested in.
+// The filter works on event types; if EV_MESSAGE is set, messages
+// can be filtered by message type also.
+type EventFilter struct {
+ evTypes map[int]bool
+ msgTypes map[uint16]bool
+}
+
+// NewEventFilter creates a new empty filter instance.
+func NewEventFilter() *EventFilter {
+ return &EventFilter{
+ evTypes: make(map[int]bool),
+ msgTypes: make(map[uint16]bool),
+ }
+}
+
+// AddEvent add an event id to filter
+func (f *EventFilter) AddEvent(ev int) {
+ f.evTypes[ev] = true
+}
+
+// AddMsgType adds a message type to filter
+func (f *EventFilter) AddMsgType(mt uint16) {
+ f.evTypes[EV_MESSAGE] = true
+ f.msgTypes[mt] = true
+}
+
+// CheckEvent returns true if an event id is matched
+// by the filter or the filter is empty.
+func (f *EventFilter) CheckEvent(ev int) bool {
+ if len(f.evTypes) == 0 {
+ return true
+ }
+ _, ok := f.evTypes[ev]
+ return ok
+}
+
+// CheckMsgType returns true if a message type is matched
+// by the filter or the filter is empty.
+func (f *EventFilter) CheckMsgType(mt uint16) bool {
+ if len(f.msgTypes) == 0 {
+ return true
+ }
+ _, ok := f.msgTypes[mt]
+ return ok
+}
+
+// Event sent to listeners
+type Event struct {
+ ID int // event type
+ Peer *util.PeerID // remote peer
+ Msg message.Message // GNUnet message (can be nil)
+}
+
+//----------------------------------------------------------------------
+
+// Listener for network events
+type Listener struct {
+ ch chan *Event // listener channel
+ filter *EventFilter // event filter settimgs
+}
+
+// NewListener for given filter and receiving channel
+func NewListener(ch chan *Event, f *EventFilter) *Listener {
+ if f == nil {
+ // set empty default filter
+ f = NewEventFilter()
+ }
+ return &Listener{
+ ch: ch,
+ filter: f,
+ }
+}
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 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -19,14 +19,30 @@
package core
import (
+ "encoding/base64"
"fmt"
+ "time"
+ "gnunet/config"
"gnunet/message"
+ "gnunet/service/dht/blocks"
"gnunet/util"
"github.com/bfix/gospel/crypto/ed25519"
)
+//----------------------------------------------------------------------
+// GNUnet P2P network node (local or remote):
+//
+// * A LOCAL node has a long-term EdDSA key pair used for signing. The
+// public key is the node identifier (PeerID).
+// Local nodes hold additional attributes like ephemeral keys for message
+// exchange or a list of network addresses the node can be reached on.
+//
+// * A REMOTE node only has a public EdDSA key used by the local node
+// to verify signatures from the remote node.
+//----------------------------------------------------------------------
+
// Peer represents a node in the GNUnet P2P network.
type Peer struct {
prv *ed25519.PrivateKey // node private key (long-term
signing key)
@@ -37,27 +53,87 @@ type Peer struct {
ephMsg *message.EphemeralKeyMsg // ephemeral signing key message
}
-// NewPeer instantiates a new peer object from given data. If a local peer
-// is created, the data is the seed for generating the private key of the node;
-// for a remote peer the data is the binary representation of its public key.
-func NewPeer(data []byte, local bool) (p *Peer, err error) {
+//----------------------------------------------------------------------
+// Create new peer objects
+//----------------------------------------------------------------------
+
+// NewLocalPeer creates a new local node from configuration data.
+func NewLocalPeer(cfg *config.NodeConfig) (p *Peer, err error) {
p = new(Peer)
- if local {
- p.prv = ed25519.NewPrivateKeyFromSeed(data)
- p.pub = p.prv.Public()
- p.ephPrv, p.ephMsg, err =
message.NewEphemeralKey(p.pub.Bytes(), p.prv)
- if err != nil {
+
+ // get the key material for local node
+ var data []byte
+ if data, err = base64.StdEncoding.DecodeString(cfg.PrivateSeed); err !=
nil {
+ return
+ }
+ p.prv = ed25519.NewPrivateKeyFromSeed(data)
+ p.pub = p.prv.Public()
+ p.idString = util.EncodeBinaryToString(p.pub.Bytes())
+ p.ephPrv, p.ephMsg, err = message.NewEphemeralKey(p.pub.Bytes(), p.prv)
+ if err != nil {
+ return
+ }
+ // set the endpoint addresses for local node
+ p.addrList = make([]*util.Address, len(cfg.Endpoints))
+ var addr *util.Address
+ for i, a := range cfg.Endpoints {
+ if addr, err = util.ParseAddress(a); err != nil {
return
}
- } else {
- p.prv = nil
- p.pub = ed25519.NewPublicKeyFromBytes(data)
+ addr.Expires = util.NewAbsoluteTime(time.Now().Add(12 *
time.Hour))
+ p.addrList[i] = addr
}
+ return
+}
+
+// NewPeer instantiates a new (remote) peer object from given peer ID string.
+func NewPeer(peerID string) (p *Peer, err error) {
+ p = new(Peer)
+
+ // get the key material for local node
+ var data []byte
+ if data, err = util.DecodeStringToBinary(peerID, 32); err != nil {
+ return
+ }
+ p.prv = nil
+ p.pub = ed25519.NewPublicKeyFromBytes(data)
p.idString = util.EncodeBinaryToString(p.pub.Bytes())
p.addrList = make([]*util.Address, 0)
return
}
+//----------------------------------------------------------------------
+//----------------------------------------------------------------------
+
+// Address returns a peer address for the given transport protocol
+func (p *Peer) Address(transport string) *util.Address {
+ for _, addr := range p.addrList {
+ // skip expired entries
+ if addr.Expires.Expired() {
+ continue
+ }
+ // filter by transport protocol
+ if len(transport) > 0 && transport != addr.Netw {
+ continue
+ }
+ return addr
+ }
+ return nil
+}
+
+// HelloData returns the current HELLO data for the peer
+func (p *Peer) HelloData(ttl time.Duration) (h *blocks.HelloBlock, err error) {
+ // assemble HELLO data
+ h = new(blocks.HelloBlock)
+ h.PeerID = p.GetID()
+ h.Expire = util.NewAbsoluteTime(time.Now().Add(ttl))
+ h.SetAddresses(p.addrList)
+
+ // sign data
+ err = h.Sign(p.prv)
+ return
+}
+
// EphKeyMsg returns a new initialized message to negotiate session keys.
func (p *Peer) EphKeyMsg() *message.EphemeralKeyMsg {
return p.ephMsg
@@ -84,8 +160,10 @@ func (p *Peer) PubKey() *ed25519.PublicKey {
}
// GetID returns the node ID (public key) in binary format
-func (p *Peer) GetID() []byte {
- return p.pub.Bytes()
+func (p *Peer) GetID() *util.PeerID {
+ return &util.PeerID{
+ Key: util.Clone(p.pub.Bytes()),
+ }
}
// 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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package core
+
+import (
+ "gnunet/config"
+ "gnunet/service/dht/blocks"
+ "testing"
+ "time"
+)
+
+// test data
+var (
+ cfg = &config.NodeConfig{
+ PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
+ Endpoints: []string{
+ "r5n+ip+udp://127.0.0.1:6666",
+ },
+ }
+ TTL = 6 * time.Hour
+)
+
+func TestPeerHello(t *testing.T) {
+
+ // generate new local node
+ node, err := NewLocalPeer(cfg)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // get HELLO data for the node
+ h, err := node.HelloData(TTL)
+
+ // convert to URL and back
+ u := h.URL()
+ t.Log(u)
+ h2, err := blocks.ParseHelloURL(u)
+ if err != nil {
+ t.Fatal(err)
+ }
+ u2 := h2.URL()
+ t.Log(u2)
+
+ // check if HELLO data is the same
+ if !h.Equals(h2) {
+ t.Fatal("HELLO data mismatch")
+ }
+ // verify signature
+ ok, err := h.Verify()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !ok {
+ t.Fatal("failed to verify signature")
+ }
+}
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 (
// example the RSA crypto scheme is outlined:
//
// (1) Register/define a new GNS_TYPE_RSAKEY
-// (2) Add ZONE_RSAKEY to the "Zone types" declarations below.
+// (2) Add ZONE_RSAKEY and GNS_TYPE_RSAKEY to the "Zone types"
+// declarations in this file.
// (3) Code the implementation in a file named `gns_rsakey.go`:
// You have to implement three interfaces (ZonePrivateImpl,
// ZoneKeyImpl and ZoneSigImpl) in three separate custom types.
@@ -145,6 +146,12 @@ type ZoneSigImpl interface {
var (
ZONE_PKEY = uint32(enums.GNS_TYPE_PKEY)
ZONE_EDKEY = uint32(enums.GNS_TYPE_EDKEY)
+
+ // register available zone types for BlockHandler
+ ZoneTypes = []int{
+ enums.GNS_TYPE_PKEY,
+ enums.GNS_TYPE_EDKEY,
+ }
)
//----------------------------------------------------------------------
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 @@
package crypto
import (
+ "bytes"
"crypto/sha512"
"gnunet/util"
@@ -29,11 +30,20 @@ type HashCode struct {
Bits []byte `size:"64"`
}
-// NewHashCode creates a new, uninitalized hash value
-func NewHashCode() *HashCode {
- return &HashCode{
+// Equals tests if two hash results are equal.
+func (hc *HashCode) Equals(n *HashCode) bool {
+ return bytes.Equal(hc.Bits, n.Bits)
+}
+
+// NewHashCode creates a new (initalized) hash value
+func NewHashCode(buf []byte) *HashCode {
+ hc := &HashCode{
Bits: make([]byte, 64),
}
+ if buf != nil {
+ util.CopyAlignedBlock(hc.Bits, buf)
+ }
+ return hc
}
// 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 @@
module gnunet
-go 1.17
+go 1.18
require (
- github.com/bfix/gospel v1.2.10
- github.com/go-redis/redis/v8 v8.5.0
- github.com/go-sql-driver/mysql v1.5.0
+ github.com/bfix/gospel v1.2.11
+ github.com/go-redis/redis/v8 v8.11.5
+ github.com/go-sql-driver/mysql v1.6.0
github.com/gorilla/mux v1.8.0
- github.com/mattn/go-sqlite3 v1.14.6
- github.com/miekg/dns v1.1.26
- golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
+ github.com/mattn/go-sqlite3 v1.14.13
+ github.com/miekg/dns v1.1.49
+ golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
)
require (
- github.com/cespare/xxhash/v2 v2.1.1 // indirect
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f //
indirect
- go.opentelemetry.io/otel v0.16.0 // indirect
- golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
- golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
+ golang.org/x/mod v0.4.2 // indirect
+ golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
+ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
+ golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
+
+replace 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 @@
-github.com/bfix/gospel v1.2.10 h1:a8l/sET2y+FVKIO5M1l5hdTlqLxstvkhp+b6FpAkxOU=
-github.com/bfix/gospel v1.2.10/go.mod
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
-github.com/cespare/xxhash/v2 v2.1.1
h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/davecgh/go-spew v1.1.0
h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/bfix/gospel v1.2.11 h1:z/c6MFNq/lz4mO8+PK60a3NvH+lbTKAlLCShuFFZUvg=
+github.com/bfix/gospel v1.2.11/go.mod
h1:cdu63bA9ZdfeDoqZ+vnWOcbY9Puwdzmf5DMxMGMznRI=
+github.com/cespare/xxhash/v2 v2.1.2
h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod
h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/fsnotify/fsnotify v1.4.7/go.mod
h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9
h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
-github.com/fsnotify/fsnotify v1.4.9/go.mod
h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/go-redis/redis/v8 v8.5.0
h1:L3r1Q3I5WOUdXZGCP6g44EruKh0u3n6co5Hl5xWkdGA=
-github.com/go-redis/redis/v8 v8.5.0/go.mod
h1:YmEcgBDttjnkbMzDAhDtQxY9yVA7jMN6PCR5HeMvqFE=
-github.com/go-sql-driver/mysql v1.5.0
h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
-github.com/go-sql-driver/mysql v1.5.0/go.mod
h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
-github.com/golang/protobuf v1.2.0/go.mod
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod
h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod
h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod
h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod
h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod
h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.2/go.mod
h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/google/go-cmp v0.3.0/go.mod
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
-github.com/google/go-cmp v0.5.4/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/go-redis/redis/v8 v8.11.5
h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod
h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-sql-driver/mysql v1.6.0
h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod
h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod
h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/hpcloud/tail v1.0.0/go.mod
h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.0.0/go.mod
h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod
h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
-github.com/mattn/go-sqlite3 v1.14.6
h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
-github.com/mattn/go-sqlite3 v1.14.6/go.mod
h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
-github.com/miekg/dns v1.1.26/go.mod
h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
-github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
-github.com/nxadm/tail v1.4.4/go.mod
h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/onsi/ginkgo v1.6.0/go.mod
h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.1/go.mod
h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
-github.com/onsi/ginkgo v1.15.0/go.mod
h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
-github.com/onsi/gomega v1.7.1/go.mod
h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1/go.mod
h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
-github.com/onsi/gomega v1.10.5/go.mod
h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
-github.com/pmezard/go-difflib v1.0.0
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.6.1
h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
-github.com/stretchr/testify v1.6.1/go.mod
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/yuin/goldmark v1.2.1/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-go.opentelemetry.io/otel v0.16.0
h1:uIWEbdeb4vpKPGITLsRVUS44L5oDbDUCZxn8lkxhmgw=
-go.opentelemetry.io/otel v0.16.0/go.mod
h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA=
+github.com/mattn/go-sqlite3 v1.14.13
h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
+github.com/mattn/go-sqlite3 v1.14.13/go.mod
h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
+github.com/miekg/dns v1.1.49/go.mod
h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/yuin/goldmark v1.3.5/go.mod
h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod
h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod
h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA=
-golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod
h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
+golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod
h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod
h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
+golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod
h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod
h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod
h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2
h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
+golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod
h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod
h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod
h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod
h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod
h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.23.0/go.mod
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod
h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod
h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
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) {
//------------------------------------------------------------------
// DHT
//------------------------------------------------------------------
+ case DHT_CLIENT_PUT:
+ return NewDHTClientPutMsg(nil, 0, nil), nil
case DHT_CLIENT_GET:
return NewDHTClientGetMsg(nil), nil
case DHT_CLIENT_GET_STOP:
return NewDHTClientGetStopMsg(nil), nil
case DHT_CLIENT_RESULT:
return NewDHTClientResultMsg(nil), nil
+ case DHT_CLIENT_GET_RESULTS_KNOWN:
+ return NewDHTClientGetResultsKnownMsg(nil), nil
//------------------------------------------------------------------
// 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 (
ErrMsgHeaderTooSmall = errors.New("Message header too small")
)
+//----------------------------------------------------------------------
+
// Message is an interface for all GNUnet-specific messages.
type Message interface {
+ // Header of message
Header() *Header
+
+ // String returns a human-readable message
+ String() string
}
+//----------------------------------------------------------------------
+
// Header encapsulates the common part of all GNUnet messages (at the
// beginning of the data).
type 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
@@ -27,6 +27,54 @@ import (
"gnunet/util"
)
+//----------------------------------------------------------------------
+// DHT_CLIENT_PUT
+//----------------------------------------------------------------------
+
+// DHTClientPutMsg is the message for putting values into the DHT
+type DHTClientPutMsg struct {
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` // DHT_CLIENT_PUT (142)
+ Type uint32 `order:"big"` // The type of the data
(BLOCK_TYPE_???)
+ Options uint32 `order:"big"` // Message options
(DHT_RO_???)
+ ReplLevel uint32 `order:"big"` // Replication level for this
message
+ Expire util.AbsoluteTime // Expiration time
+ Key *crypto.HashCode // The key to be used
+ Data []byte `size:"*"` // Block data
+}
+
+// NewDHTClientPutMsg creates a new default DHTClientPutMsg object.
+func NewDHTClientPutMsg(key *crypto.HashCode, btype int, data []byte)
*DHTClientPutMsg {
+ if key == nil {
+ key = new(crypto.HashCode)
+ }
+ var size uint16 = 88
+ if data != nil {
+ size += uint16(len(data))
+ }
+ return &DHTClientPutMsg{
+ MsgSize: size,
+ MsgType: DHT_CLIENT_PUT,
+ Type: uint32(btype),
+ Options: uint32(enums.DHT_RO_NONE),
+ ReplLevel: 1,
+ Expire: util.AbsoluteTimeNever(),
+ Key: key,
+ Data: data,
+ }
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTClientPutMsg) String() string {
+ return
fmt.Sprintf("DHTClientPutMsg{Type=%d,Expire=%s,Options=%d,Repl=%d,Key=%s}",
+ m.Type, m.Expire, m.Options, m.ReplLevel,
hex.EncodeToString(m.Key.Bits))
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTClientPutMsg) Header() *Header {
+ return &Header{m.MsgSize, m.MsgType}
+}
+
//----------------------------------------------------------------------
// DHT_CLIENT_GET
//----------------------------------------------------------------------
@@ -102,7 +150,7 @@ type DHTClientResultMsg struct {
// NewDHTClientResultMsg creates a new default DHTClientResultMsg object.
func NewDHTClientResultMsg(key *crypto.HashCode) *DHTClientResultMsg {
if key == nil {
- key = crypto.NewHashCode()
+ key = crypto.NewHashCode(nil)
}
return &DHTClientResultMsg{
MsgSize: 64, // empty message size (no data)
@@ -163,3 +211,48 @@ func (m *DHTClientGetStopMsg) String() string {
func (m *DHTClientGetStopMsg) Header() *Header {
return &Header{m.MsgSize, m.MsgType}
}
+
+//----------------------------------------------------------------------
+// DHT_CLIENT_GET_RESULTS_KNOWN
+//----------------------------------------------------------------------
+
+// DHTClientGetResultsKnownMsg is the message for putting values into the DHT
+type DHTClientGetResultsKnownMsg struct {
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` //
DHT_CLIENT_GET_RESULTS_KNOWN (156)
+ Reserved uint32 `order:"big"` // Reserved for further use
+ Key *crypto.HashCode // The key to search for
+ ID uint64 `order:"big"` // Unique ID identifying this
request
+ Known []*crypto.HashCode `size:"*"` // list of known results
+}
+
+// NewDHTClientPutMsg creates a new default DHTClientPutMsg object.
+func NewDHTClientGetResultsKnownMsg(key *crypto.HashCode)
*DHTClientGetResultsKnownMsg {
+ if key == nil {
+ key = new(crypto.HashCode)
+ }
+ return &DHTClientGetResultsKnownMsg{
+ MsgSize: 80,
+ MsgType: DHT_CLIENT_GET_RESULTS_KNOWN,
+ Key: key,
+ ID: 0,
+ Known: make([]*crypto.HashCode, 0),
+ }
+}
+
+// AddKnown adds a known result to the list
+func (m *DHTClientGetResultsKnownMsg) AddKnown(hc *crypto.HashCode) {
+ m.Known = append(m.Known, hc)
+ m.MsgSize += 64
+}
+
+// String returns a human-readable representation of the message.
+func (m *DHTClientGetResultsKnownMsg) String() string {
+ return fmt.Sprintf("DHTClientGetResultsKnownMsg{Id:%d,Key=%s,Num=%d}",
+ m.ID, hex.EncodeToString(m.Key.Bits), len(m.Known))
+}
+
+// Header returns the message header in a separate instance.
+func (m *DHTClientGetResultsKnownMsg) Header() *Header {
+ return &Header{m.MsgSize, m.MsgType}
+}
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 (
"gnunet/enums"
"gnunet/util"
- "github.com/bfix/gospel/data"
"github.com/bfix/gospel/logger"
)
-// Error messages
-var (
- ErrBlockNotDecrypted = fmt.Errorf("GNS block not decrypted")
-)
-
//----------------------------------------------------------------------
// GNS_LOOKUP
//----------------------------------------------------------------------
@@ -147,94 +141,6 @@ func (rs *RecordSet) Expires() util.AbsoluteTime {
return expires
}
-// SignedBlockData represents the signed and encrypted list of resource
-// records stored in a GNSRecordSet
-type SignedBlockData struct {
- Purpose *crypto.SignaturePurpose `` // Size and purpose of
signature (8 bytes)
- Expire util.AbsoluteTime `` // Expiration time of the
block.
- EncData []byte `size:"*"` // encrypted GNSRecordSet
-
- // transient data (not serialized)
- data []byte // decrypted GNSRecord set
-}
-
-// Block is the result of GNS lookups for a given label in a zone.
-// An encrypted and signed container for GNS resource records that represents
-// the "atomic" data structure associated with a GNS label in a given zone.
-type Block struct {
- DerivedKeySig *crypto.ZoneSignature // Derived key used for signing
- Block *SignedBlockData
-
- // transient data (not serialized)
- checked bool // block integrity checked
- verified bool // block signature verified (internal)
- decrypted bool // block data decrypted (internal)
-}
-
-// String returns the human-readable representation of a GNSBlock
-func (b *Block) String() string {
- return fmt.Sprintf("GNSBlock{Verified=%v,Decrypted=%v,data=[%d]}",
- b.verified, b.decrypted, len(b.Block.EncData))
-}
-
-// Records returns the list of resource records in a block.
-func (b *Block) Records() ([]*ResourceRecord, error) {
- // check if block is decrypted
- if !b.decrypted {
- return nil, ErrBlockNotDecrypted
- }
- // parse block data into record set
- rs := NewRecordSet()
- if err := data.Unmarshal(rs, b.Block.data); err != nil {
- return nil, err
- }
- return rs.Records, nil
-}
-
-// Verify the integrity of the block data from a signature.
-func (b *Block) Verify(zkey *crypto.ZoneKey, label string) (err error) {
- // Integrity check performed
- b.checked = true
-
- // verify derived key
- dkey := b.DerivedKeySig.ZoneKey
- dkey2, _ := zkey.Derive(label, "gns")
- if !dkey.Equal(dkey2) {
- return fmt.Errorf("invalid signature key for GNS Block")
- }
- // verify signature
- var buf []byte
- if buf, err = data.Marshal(b.Block); err != nil {
- return
- }
- b.verified, err = b.DerivedKeySig.Verify(buf)
- return
-}
-
-// Decrypt block data with a key derived from zone key and label.
-func (b *Block) Decrypt(zkey *crypto.ZoneKey, label string) (err error) {
- // decrypt payload
- b.Block.data, err = zkey.Decrypt(b.Block.EncData, label, b.Block.Expire)
- b.decrypted = true
- return
-}
-
-// NewBlock instantiates an empty GNS block
-func NewBlock() *Block {
- return &Block{
- DerivedKeySig: nil,
- Block: &SignedBlockData{
- Purpose: new(crypto.SignaturePurpose),
- Expire: *new(util.AbsoluteTime),
- EncData: nil,
- data: nil,
- },
- checked: false,
- verified: false,
- decrypted: false,
- }
-}
-
// ResourceRecord is the GNUnet-specific representation of resource
// records (not to be confused with DNS resource records).
type 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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package message
+
+import (
+ "fmt"
+ "gnunet/util"
+)
+
+//----------------------------------------------------------------------
+// HELLO
+//
+// A HELLO message is used to exchange information about transports with
+// other peers. This struct is always followed by the actual network
+// addresses which have the format:
+//
+// 1) transport-name (0-terminated)
+// 2) address-length (uint16_t, network byte order)
+// 3) address expiration
+// 4) address (address-length bytes)
+//----------------------------------------------------------------------
+
+// HelloAddress represents a (generic) peer address with expiration date
+type HelloAddress struct {
+ Transport string // Name of transport
+ AddrSize uint16 `order:"big"` // Size of address entry
+ ExpireOn util.AbsoluteTime // Expiry date
+ Address []byte `size:"AddrSize"` // Address specification
+}
+
+// NewHelloAddress create a new HELLO address from the given address
+func NewHelloAddress(a *util.Address) *HelloAddress {
+ addr := &HelloAddress{
+ Transport: a.Netw,
+ AddrSize: uint16(len(a.Address)),
+ ExpireOn: a.Expires,
+ Address: make([]byte, len(a.Address)),
+ }
+ copy(addr.Address, a.Address)
+ return addr
+}
+
+// String returns a human-readable representation of the message.
+func (a *HelloAddress) String() string {
+ return fmt.Sprintf("Address{%s,expire=%s}",
+ util.AddressString(a.Transport, a.Address), a.ExpireOn)
+}
+
+// HelloMsg is a message send by peers to announce their presence
+type HelloMsg struct {
+ MsgSize uint16 `order:"big"` // total size of message
+ MsgType uint16 `order:"big"` // HELLO (17)
+ FriendOnly uint32 `order:"big"` // =1: do not gossip this HELLO
+ PeerID *util.PeerID // EdDSA public key (long-term)
+ Addresses []*HelloAddress `size:"*"` // List of end-point addressess
+}
+
+// NewHelloMsg creates a new HELLO msg for a given peer.
+func NewHelloMsg(peerid *util.PeerID) *HelloMsg {
+ if peerid == nil {
+ peerid = util.NewPeerID(nil)
+ }
+ return &HelloMsg{
+ MsgSize: 40,
+ MsgType: HELLO,
+ FriendOnly: 0,
+ PeerID: peerid,
+ Addresses: make([]*HelloAddress, 0),
+ }
+}
+
+// String returns a human-readable representation of the message.
+func (m *HelloMsg) String() string {
+ return fmt.Sprintf("HelloMsg{peer=%s,friendsonly=%d,addr=%v}",
+ m.PeerID, m.FriendOnly, m.Addresses)
+}
+
+// AddAddress adds a new address to the HELLO message.
+func (m *HelloMsg) AddAddress(a *HelloAddress) {
+ m.Addresses = append(m.Addresses, a)
+ m.MsgSize += uint16(len(a.Transport)) + a.AddrSize + 11
+}
+
+// Header returns the message header in a separate instance.
+func (m *HelloMsg) Header() *Header {
+ return &Header{m.MsgSize, m.MsgType}
+}
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
import (
"encoding/hex"
"fmt"
-
"gnunet/crypto"
+ "gnunet/service/dht/blocks"
"gnunet/util"
)
@@ -41,7 +41,7 @@ type NamecacheLookupMsg struct {
// NewNamecacheLookupMsg creates a new default message.
func NewNamecacheLookupMsg(query *crypto.HashCode) *NamecacheLookupMsg {
if query == nil {
- query = crypto.NewHashCode()
+ query = crypto.NewHashCode(nil)
}
return &NamecacheLookupMsg{
MsgSize: 72,
@@ -114,7 +114,7 @@ type NamecacheCacheMsg struct {
}
// NewNamecacheCacheMsg creates a new default message.
-func NewNamecacheCacheMsg(block *Block) *NamecacheCacheMsg {
+func NewNamecacheCacheMsg(block *blocks.GNSBlock) *NamecacheCacheMsg {
msg := &NamecacheCacheMsg{
MsgSize: 108,
MsgType: NAMECACHE_BLOCK_CACHE,
@@ -125,10 +125,10 @@ func NewNamecacheCacheMsg(block *Block)
*NamecacheCacheMsg {
}
if block != nil {
msg.DerivedKeySig = block.DerivedKeySig
- msg.Expire = block.Block.Expire
- size := len(block.Block.EncData)
+ msg.Expire = block.Body.Expire
+ size := len(block.Body.Data)
msg.EncData = make([]byte, size)
- copy(msg.EncData, block.Block.EncData)
+ copy(msg.EncData, block.Body.Data)
msg.MsgSize += uint16(size)
}
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 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -224,86 +224,6 @@ func (m *TransportPongMsg) Verify(pub *ed25519.PublicKey)
(bool, error) {
return pub.EdVerify(data, sig)
}
-//----------------------------------------------------------------------
-// HELLO
-//
-// A HELLO message is used to exchange information about
-// transports with other peers. This struct is always
-// followed by the actual network addresses which have
-// the format:
-//
-// 1) transport-name (0-terminated)
-// 2) address-length (uint16_t, network byte order)
-// 3) address expiration
-// 4) address (address-length bytes)
-//----------------------------------------------------------------------
-
-// HelloAddress represents a (generic) peer address with expiration date
-type HelloAddress struct {
- Transport string // Name of transport
- AddrSize uint16 `order:"big"` // Size of address entry
- ExpireOn util.AbsoluteTime // Expiry date
- Address []byte `size:"AddrSize"` // Address specification
-}
-
-// NewHelloAddress create a new HELLO address from the given address
-func NewHelloAddress(a *util.Address) *HelloAddress {
- addr := &HelloAddress{
- Transport: a.Transport,
- AddrSize: uint16(len(a.Address)),
- ExpireOn: util.AbsoluteTimeNow().Add(12 * time.Hour),
- Address: make([]byte, len(a.Address)),
- }
- copy(addr.Address, a.Address)
- return addr
-}
-
-// String returns a human-readable representation of the message.
-func (a *HelloAddress) String() string {
- return fmt.Sprintf("Address{%s,expire=%s}",
- util.AddressString(a.Transport, a.Address), a.ExpireOn)
-}
-
-// HelloMsg is a message send by peers to announce their presence
-type HelloMsg struct {
- MsgSize uint16 `order:"big"` // total size of message
- MsgType uint16 `order:"big"` // HELLO (17)
- FriendOnly uint32 `order:"big"` // =1: do not gossip this HELLO
- PeerID *util.PeerID // EdDSA public key (long-term)
- Addresses []*HelloAddress `size:"*"` // List of end-point addressess
-}
-
-// NewHelloMsg creates a new HELLO msg for a given peer.
-func NewHelloMsg(peerid *util.PeerID) *HelloMsg {
- if peerid == nil {
- peerid = util.NewPeerID(nil)
- }
- return &HelloMsg{
- MsgSize: 40,
- MsgType: HELLO,
- FriendOnly: 0,
- PeerID: peerid,
- Addresses: make([]*HelloAddress, 0),
- }
-}
-
-// String returns a human-readable representation of the message.
-func (m *HelloMsg) String() string {
- return fmt.Sprintf("HelloMsg{peer=%s,friendsonly=%d,addr=%v}",
- m.PeerID, m.FriendOnly, m.Addresses)
-}
-
-// AddAddress adds a new address to the HELLO message.
-func (m *HelloMsg) AddAddress(a *HelloAddress) {
- m.Addresses = append(m.Addresses, a)
- m.MsgSize += uint16(len(a.Transport)) + a.AddrSize + 11
-}
-
-// Header returns the message header in a separate instance.
-func (m *HelloMsg) Header() *Header {
- return &Header{m.MsgSize, m.MsgType}
-}
-
//----------------------------------------------------------------------
// TRANSPORT_SESSION_ACK
//----------------------------------------------------------------------
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() {
rpc.Register(inst.Revocation)
}
-// Local reference to instance list
+// Local reference to instance list. The list is initialized
+// by core.
var (
Modules Instances
)
+/* TODO: implement
// Initialize instance list and link module functions as required.
-func init() {
+// This function is called by core on start-up.
+func Init(ctx context.Context) {
// Namecache (no calls to other modules)
- Modules.Namecache = new(namecache.NamecacheModule)
+ Modules.Namecache = namecache.NewModule(ctx, c)
// DHT (no calls to other modules)
- Modules.DHT = new(dht.Module)
+ Modules.DHT = dht.NewModule(ctx, c)
// Revocation (no calls to other modules)
- Modules.Revocation = revocation.NewModule()
+ Modules.Revocation = revocation.NewModule(ctx, c)
// GNS (calls Namecache, DHT and Identity)
- Modules.GNS = &gns.Module{
- LookupLocal: Modules.Namecache.Get,
- StoreLocal: Modules.Namecache.Put,
- LookupRemote: Modules.DHT.Get,
- RevocationQuery: Modules.Revocation.Query,
- RevocationRevoke: Modules.Revocation.Revoke,
- }
+ gns := gns.NewModule(ctx, c)
+ Modules.GNS = gns
+ gns.LookupLocal = Modules.Namecache.Get
+ gns.StoreLocal = Modules.Namecache.Put
+ gns.LookupRemote = Modules.DHT.Get
+ gns.RevocationQuery = Modules.Revocation.Query
+ gns.RevocationRevoke = Modules.Revocation.Revoke
}
+*/
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 @@
package service
import (
+ "context"
"gnunet/message"
- "gnunet/transport"
"github.com/bfix/gospel/logger"
)
// Client type: Use to perform client-side interactions with GNUnet services.
type Client struct {
- ch *transport.MsgChannel // channel for message exchange
+ ch *Connection // channel for message exchange
}
-// NewClient creates a new client instance for the given channel endpoint.
-func NewClient(endp string) (*Client, error) {
- // create a new channel to endpoint.
- ch, err := transport.NewChannel(endp)
+// NewClient connects to a socket with given path
+func NewClient(ctx context.Context, path string) (*Client, error) {
+ // create a connection
+ ch, err := NewConnection(ctx, path)
if err != nil {
return nil, err
}
// wrap into a message channel for the client.
return &Client{
- ch: transport.NewMsgChannel(ch),
+ ch: ch,
}, nil
}
-// SendRequest sends a give message to the service.
-func (c *Client) SendRequest(ctx *SessionContext, req message.Message) error {
- return c.ch.Send(req, ctx.Signaller())
+// SendRequest sends a message to the service.
+func (c *Client) SendRequest(ctx context.Context, req message.Message) error {
+ return c.ch.Send(ctx, req)
}
// ReceiveResponse waits for a response from the service; it can be interrupted
// by sending "false" to the cmd channel.
-func (c *Client) ReceiveResponse(ctx *SessionContext) (message.Message, error)
{
- return c.ch.Receive(ctx.Signaller())
+func (c *Client) ReceiveResponse(ctx context.Context) (message.Message, error)
{
+ return c.ch.Receive(ctx)
}
// Close a client; no further message exchange is possible.
@@ -62,15 +62,15 @@ func (c *Client) Close() error {
// RequestResponse is a helper method for a one request - one response
// secenarios of client/serice interactions.
func RequestResponse(
- ctx *SessionContext,
+ ctx context.Context,
caller string,
callee string,
- endp string,
+ path string,
req message.Message) (message.Message, error) {
// client-connect to the service
logger.Printf(logger.DBG, "[%s] Connecting to %s service...\n", caller,
callee)
- cl, err := NewClient(endp)
+ cl, err := NewClient(ctx, path)
if err != nil {
return nil, err
}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package service
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "gnunet/message"
+ "net"
+ "os"
+ "strconv"
+
+ "github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
+)
+
+// Error codes
+var (
+ ErrConnectionNotOpened = errors.New("channel not opened")
+ ErrConnectionInterrupted = errors.New("channel interrupted")
+)
+
+//======================================================================
+
+// Connection is a channel for GNUnet message exchange (send/receive)
+// based on Unix domain sockets. It is used locally by services and
+// clients in the standard GNUnet environment.
+type Connection struct {
+ path string // file name of Unix socket
+ conn net.Conn // associated connection
+ buf []byte // read/write buffer
+}
+
+// NewConnection creates a new connection to a socket with given path.
+// This is used by clients to connect to a service.
+func NewConnection(ctx context.Context, path string) (s *Connection, err
error) {
+ var d net.Dialer
+ s = new(Connection)
+ s.path = path
+ s.buf = make([]byte, 65536)
+ s.conn, err = d.DialContext(ctx, "unix", path)
+ return
+}
+
+// Close a socket connection
+func (s *Connection) Close() error {
+ if s.conn != nil {
+ rc := s.conn.Close()
+ s.conn = nil
+ return rc
+ }
+ return ErrConnectionNotOpened
+}
+
+// Send a GNUnet message over a socket.
+func (s *Connection) Send(ctx context.Context, msg message.Message) error {
+ // convert message to binary data
+ data, err := data.Marshal(msg)
+ if err != nil {
+ return err
+ }
+ // check message header size and packet size
+ mh, err := message.GetMsgHeader(data)
+ if err != nil {
+ return err
+ }
+ if len(data) != int(mh.MsgSize) {
+ return errors.New("send: message size mismatch")
+ }
+
+ // send packet
+ n, err := s.write(ctx, data)
+ if err != nil {
+ return err
+ }
+ if n != len(data) {
+ return errors.New("incomplete send")
+ }
+ return nil
+}
+
+// Receive GNUnet messages from socket.
+func (s *Connection) Receive(ctx context.Context) (message.Message, error) {
+ // get bytes from socket
+ get := func(pos, count int) error {
+ n, err := s.read(ctx, s.buf[pos:pos+count])
+ if err != nil {
+ return err
+ }
+ if n != count {
+ return errors.New("not enough bytes on network")
+ }
+ return nil
+ }
+ // read header first
+ if err := get(0, 4); err != nil {
+ return nil, err
+ }
+ mh, err := message.GetMsgHeader(s.buf[:4])
+ if err != nil {
+ return nil, err
+ }
+ // get rest of message
+ if err := get(4, int(mh.MsgSize)-4); err != nil {
+ return nil, err
+ }
+ msg, err := message.NewEmptyMessage(mh.MsgType)
+ if err != nil {
+ return nil, err
+ }
+ if msg == nil {
+ return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
+ }
+ if err = data.Unmarshal(msg, s.buf[:mh.MsgSize]); err != nil {
+ return nil, err
+ }
+ return msg, nil
+}
+
+//----------------------------------------------------------------------
+// internal methods
+//----------------------------------------------------------------------
+
+// result of read/write operations on sockets.
+type result struct {
+ n int // number of bytes read/written
+ err error // error (or nil)
+}
+
+// Read bytes from a socket into buffer: Returns the number of read
+// bytes and an error code. Only works on open channels ;)
+func (s *Connection) read(ctx context.Context, buf []byte) (int, error) {
+ // check if the channel is open
+ if s.conn == nil {
+ return 0, ErrConnectionNotOpened
+ }
+ // perform read operation
+ ch := make(chan *result)
+ go func() {
+ n, err := s.conn.Read(buf)
+ ch <- &result{n, err}
+ }()
+ for {
+ select {
+ // terminate on request
+ case <-ctx.Done():
+ return 0, ErrConnectionInterrupted
+
+ // handle result of read operation
+ case res := <-ch:
+ return res.n, res.err
+ }
+ }
+}
+
+// Write buffer to socket and returns the number of bytes written and an
+// optional error code.
+func (s *Connection) write(ctx context.Context, buf []byte) (int, error) {
+ // check if we have an open socket to write to.
+ if s.conn == nil {
+ return 0, ErrConnectionNotOpened
+ }
+ // perform write operation
+ ch := make(chan *result)
+ go func() {
+ n, err := s.conn.Write(buf)
+ ch <- &result{n, err}
+ }()
+ for {
+ select {
+ // handle terminate command
+ case <-ctx.Done():
+ return 0, ErrConnectionInterrupted
+
+ // handle result of write operation
+ case res := <-ch:
+ return res.n, res.err
+ }
+ }
+}
+
+//======================================================================
+
+// ConnectionManager to handle client connections on a socket.
+type ConnectionManager struct {
+ listener net.Listener // reference to listener object
+ running bool // server running?
+}
+
+// NewConnectionManager creates a new socket connection manager. Incoming
+// connections from clients are dispatched to a handler channel.
+func NewConnectionManager(
+ ctx context.Context, // execution context
+ path string, // socket file name
+ params map[string]string, // connection parameters
+ hdlr chan *Connection, // handler for incoming connections
+) (cs *ConnectionManager, err error) {
+
+ // instantiate channel server
+ cs = &ConnectionManager{
+ listener: nil,
+ running: false,
+ }
+ // create listener
+ var lc net.ListenConfig
+ if cs.listener, err = lc.Listen(ctx, "unix", path); err != nil {
+ return
+ }
+ // handle additional parameters
+ if params != nil {
+ for key, value := range params {
+ switch key {
+ case "perm": // set permissions on 'unix'
+ if perm, err := strconv.ParseInt(value, 8, 32);
err == nil {
+ if err := os.Chmod(path,
os.FileMode(perm)); err != nil {
+ logger.Printf(
+ logger.ERROR,
+ "MsgChannelServer:
Failed to set permissions %s on %s: %s\n",
+ path, value,
err.Error())
+
+ }
+ } else {
+ logger.Printf(
+ logger.ERROR,
+ "MsgChannelServer: Invalid
permissions '%s'\n",
+ value)
+ }
+ }
+ }
+ }
+ // run go routine to handle channel requests from clients
+ cs.running = true
+ go func() {
+ for cs.running {
+ conn, err := cs.listener.Accept()
+ if err != nil {
+ break
+ }
+ // handle connection
+ c := &Connection{
+ conn: conn,
+ path: path,
+ buf: make([]byte, 65536),
+ }
+ hdlr <- c
+ }
+ if cs.listener != nil {
+ cs.listener.Close()
+ }
+ }()
+ return cs, nil
+}
+
+// Close a network channel server (= stop the server)
+func (s *ConnectionManager) Close() error {
+ s.running = false
+ if s.listener != nil {
+ err := s.listener.Close()
+ s.listener = nil
+ return err
+ }
+ return nil
+}
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 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package service
-
-import (
- "sync"
-
- "gnunet/util"
-
- "github.com/bfix/gospel/concurrent"
-)
-
-// SessionContext is used to set a context for each client connection handled
-// by a service; the session is handled by the 'ServeClient' method of a
-// service implementation.
-type SessionContext struct {
- ID int // session identifier
- wg *sync.WaitGroup // wait group for the session
- sig *concurrent.Signaller // signaller for the session
- pending int // number of pending go-routines
- active bool // is the context active (un-cancelled)?
- onCancel *sync.Mutex // only run one Cancel() at a time
-}
-
-// NewSessionContext instantiates a new session context.
-func NewSessionContext() *SessionContext {
- return &SessionContext{
- ID: util.NextID(),
- wg: new(sync.WaitGroup),
- sig: concurrent.NewSignaller(),
- pending: 0,
- active: true,
- onCancel: new(sync.Mutex),
- }
-}
-
-// Cancel all go-routines associated with this context.
-func (ctx *SessionContext) Cancel() {
- ctx.onCancel.Lock()
- if ctx.active {
- // we are going out-of-business
- ctx.active = false
- // send signal to terminate...
- ctx.sig.Send(true)
- // wait for session go-routines to finish
- ctx.wg.Wait()
- }
- ctx.onCancel.Unlock()
-}
-
-// Add a go-routine to the wait group.
-func (ctx *SessionContext) Add() {
- ctx.wg.Add(1)
- ctx.pending++
-}
-
-// Remove a go-routine from the wait group.
-func (ctx *SessionContext) Remove() {
- ctx.wg.Done()
- ctx.pending--
-}
-
-// Waiting returns the number of waiting go-routines.
-func (ctx *SessionContext) Waiting() int {
- return ctx.pending
-}
-
-// Signaller returns the working instance for the context.
-func (ctx *SessionContext) Signaller() *concurrent.Signaller {
- return ctx.sig
-}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+ "bytes"
+ "encoding/gob"
+ "encoding/hex"
+ "fmt"
+ "gnunet/crypto"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/data"
+)
+
+//----------------------------------------------------------------------
+// Query/Block interfaces for generic DHT handling
+//----------------------------------------------------------------------
+
+// DHT Query interface
+type Query interface {
+
+ // Key returns the DHT key for a block
+ Key() *crypto.HashCode
+
+ // Get retrieves the value of a named query parameter. The value is
+ // unchanged if the key is not in the map or if the value in the map
+ // has an incompatible type.
+ Get(key string, value any) bool
+
+ // Set stores the value of a named query parameter
+ Set(key string, value any)
+
+ // Verify the integrity of a retrieved block (optional). Override in
+ // custom query types to implement block-specific integrity checks
+ // (see GNSQuery for example).
+ Verify(blk Block) error
+
+ // Decrypt block content (optional). Override in custom query types to
+ // implement block-specific encryption (see GNSQuery for example).
+ Decrypt(blk Block) error
+
+ // String returns the human-readable representation of a query
+ String() string
+}
+
+// DHT Block interface
+type Block interface {
+
+ // Data returns the DHT block data (unstructured without type and
+ // expiration information.
+ Data() []byte
+
+ // Return the block type
+ Type() uint16
+
+ // Expire returns the block expiration
+ Expire() util.AbsoluteTime
+
+ // Verify the integrity of a block (optional). Override in custom query
+ // types to implement block-specific integrity checks (see GNSBlock for
+ // example). This verification is usually weaker than the verification
+ // method from a Query (see GNSBlock.Verify for explanation).
+ Verify() error
+
+ // String returns the human-readable representation of a block
+ String() string
+}
+
+// Unwrap (raw) block to a specific block type
+func Unwrap(blk Block, obj interface{}) error {
+ return data.Unmarshal(obj, blk.Data())
+}
+
+//----------------------------------------------------------------------
+// Generic interface implementations without persistent attributes
+//----------------------------------------------------------------------
+
+// GenericQuery is the binary representation of a DHT key
+type GenericQuery struct {
+ // Key for repository queries (local/remote)
+ key *crypto.HashCode
+
+ // query parameters (binary value representation)
+ params map[string][]byte
+}
+
+// Key interface method implementation
+func (q *GenericQuery) Key() *crypto.HashCode {
+ return q.key
+}
+
+// Get retrieves the value of a named query parameter
+func (q *GenericQuery) Get(key string, value any) bool {
+ data, ok := q.params[key]
+ if !ok {
+ return false
+ }
+ dec := gob.NewDecoder(bytes.NewReader(data))
+ return dec.Decode(value) != nil
+}
+
+// Set stores the value of a named query parameter
+func (q *GenericQuery) Set(key string, value any) {
+ wrt := new(bytes.Buffer)
+ enc := gob.NewEncoder(wrt)
+ if enc.Encode(value) == nil {
+ q.params[key] = wrt.Bytes()
+ }
+}
+
+// Verify interface method implementation
+func (q *GenericQuery) Verify(b Block) error {
+ // no verification, no errors ;)
+ return nil
+}
+
+// Decrypt interface method implementation
+func (q *GenericQuery) Decrypt(b Block) error {
+ // no decryption, no errors ;)
+ return nil
+}
+
+// String returns the human-readable representation of a block
+func (q *GenericQuery) String() string {
+ return fmt.Sprintf("GenericQuery{key=%s}",
hex.EncodeToString(q.Key().Bits))
+}
+
+// NewGenericQuery creates a simple Query from hash code.
+func NewGenericQuery(buf []byte) *GenericQuery {
+ return &GenericQuery{
+ key: crypto.NewHashCode(buf),
+ params: make(map[string][]byte),
+ }
+}
+
+//----------------------------------------------------------------------
+
+// GenericBlock is the block in simple binary representation
+type GenericBlock struct {
+ block []byte // block data
+ btype uint16 // block type
+ expire util.AbsoluteTime // expiration date
+}
+
+// Data interface method implementation
+func (b *GenericBlock) Data() []byte {
+ return b.block
+}
+
+// Type returns the block type
+func (b *GenericBlock) Type() uint16 {
+ return b.btype
+}
+
+// Expire returns the block expiration
+func (b *GenericBlock) Expire() util.AbsoluteTime {
+ return b.expire
+}
+
+// String returns the human-readable representation of a block
+func (b *GenericBlock) String() string {
+ return fmt.Sprintf("GenericBlock{type=%d,expires=%s,data=[%d]}",
+ b.btype, b.expire.String(), len(b.block))
+}
+
+// Verify interface method implementation
+func (b *GenericBlock) Verify() error {
+ // no verification, no errors ;)
+ return nil
+}
+
+// NewGenericBlock creates a Block from binary data.
+func NewGenericBlock(buf []byte) *GenericBlock {
+ return &GenericBlock{
+ block: util.Clone(buf),
+ btype: DHT_BLOCK_ANY, // unknown block type
+ expire: util.AbsoluteTimeNever(), // never expires
+ }
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+ "bytes"
+ "testing"
+)
+
+// Test parameter handling for queries
+func TestQueryParams(t *testing.T) {
+ q := NewGenericQuery(nil)
+
+ // set parameters
+ var (
+ btype uint16 = DHT_BLOCK_ANY
+ flags uint32 = 0
+ name string = "Test"
+ data = make([]byte, 8)
+ )
+ q.Set("btype", btype)
+ q.Set("flags", flags)
+ q.Set("name", name)
+ q.Set("data", data)
+
+ // get parameters
+ var (
+ t_btype uint16
+ t_flags uint32
+ t_name string
+ t_data []byte
+ )
+ q.Get("btype", &t_btype)
+ q.Get("flags", &t_flags)
+ q.Get("name", &t_name)
+ q.Get("data", &t_data)
+
+ // check for unchanged data
+ if btype != t_btype {
+ t.Fatal("btype mismatch")
+ }
+ if flags != t_flags {
+ t.Fatal("flags mismatch")
+ }
+ if name != t_name {
+ t.Fatal("name mismatch")
+ }
+ if !bytes.Equal(data, t_data) {
+ t.Fatal("data mismatch")
+ }
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+ "errors"
+ "fmt"
+ "gnunet/crypto"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/data"
+)
+
+// Error messages
+var (
+ ErrBlockNotDecrypted = fmt.Errorf("GNS block not decrypted")
+)
+
+//----------------------------------------------------------------------
+// Query key for GNS lookups
+//----------------------------------------------------------------------
+
+// GNSQuery specifies the context for a basic GNS name lookup of an (atomic)
+// label in a given zone identified by its public key.
+type GNSQuery struct {
+ GenericQuery
+ Zone *crypto.ZoneKey // Public zone key
+ Label string // Atomic label
+ derived *crypto.ZoneKey // Derived zone key from (pkey,label)
+}
+
+// Verify the integrity of the block data from a signature.
+func (q *GNSQuery) Verify(b Block) (err error) {
+ switch blk := b.(type) {
+ case *GNSBlock:
+ // Integrity check performed
+ blk.checked = true
+
+ // verify derived key
+ dkey := blk.DerivedKeySig.ZoneKey
+ dkey2, _ := q.Zone.Derive(q.Label, "gns")
+ if !dkey.Equal(dkey2) {
+ return fmt.Errorf("invalid signature key for GNS Block")
+ }
+ // verify signature
+ var buf []byte
+ if buf, err = data.Marshal(blk.Body); err != nil {
+ return
+ }
+ blk.verified, err = blk.DerivedKeySig.Verify(buf)
+
+ default:
+ err = errors.New("can't verify block type")
+ }
+ return
+}
+
+// Decrypt block data with a key derived from zone key and label.
+func (q *GNSQuery) Decrypt(b Block) (err error) {
+ switch blk := b.(type) {
+ case *GNSBlock:
+ // decrypt GNS payload
+ blk.data, err = q.Zone.Decrypt(blk.Body.Data, q.Label,
blk.Body.Expire)
+ blk.decrypted = true
+ return
+
+ default:
+ err = errors.New("can't decrypt block type")
+ }
+ return
+}
+
+// NewGNSQuery assembles a new Query object for the given zone and label.
+func NewGNSQuery(zkey *crypto.ZoneKey, label string) *GNSQuery {
+ // derive a public key from (pkey,label) and set the repository
+ // key as the SHA512 hash of the binary key representation.
+ // (key blinding)
+ pd, _ := zkey.Derive(label, "gns")
+ gq := crypto.Hash(pd.Bytes()).Bits
+ return &GNSQuery{
+ GenericQuery: *NewGenericQuery(gq),
+ Zone: zkey,
+ Label: label,
+ derived: pd,
+ }
+}
+
+//----------------------------------------------------------------------
+// GNS blocks
+//----------------------------------------------------------------------
+
+// SignedGNSBlockData represents the signed content of a GNS block
+type SignedGNSBlockData struct {
+ Purpose *crypto.SignaturePurpose `` // Size and purpose of
signature (8 bytes)
+ Expire util.AbsoluteTime `` // Expiration time of the
block.
+ Data []byte `size:"*"` // Block data content
+}
+
+// GNSBlock is the result of GNS lookups for a given label in a zone.
+// An encrypted and signed container for GNS resource records that represents
+// the "atomic" data structure associated with a GNS label in a given zone.
+type GNSBlock struct {
+ GenericBlock
+
+ // persistent
+ DerivedKeySig *crypto.ZoneSignature // Derived key used for signing
+ Body *SignedGNSBlockData
+
+ // transient data (not serialized)
+ checked bool // block integrity checked
+ verified bool // block signature verified (internal)
+ decrypted bool // block decrypted (internal)
+ data []byte // decrypted data
+}
+
+// Data block interface implementation
+func (b *GNSBlock) Data() []byte {
+ buf, _ := data.Marshal(b)
+ return buf
+}
+
+// String returns the human-readable representation of a GNSBlock
+func (b *GNSBlock) String() string {
+ return fmt.Sprintf("GNSBlock{Verified=%v,Decrypted=%v,data=[%d]}",
+ b.verified, b.decrypted, len(b.Body.Data))
+}
+
+// NewBlock instantiates an empty GNS block
+func NewBlock() *GNSBlock {
+ return &GNSBlock{
+ DerivedKeySig: nil,
+ Body: &SignedGNSBlockData{
+ Purpose: new(crypto.SignaturePurpose),
+ Expire: *new(util.AbsoluteTime),
+ Data: nil,
+ },
+ checked: false,
+ verified: false,
+ decrypted: false,
+ data: nil,
+ }
+}
+
+// Verify the integrity of the block data from a signature.
+// Only the cryptographic signature is verified; the formal correctness of
+// the association between the block and a GNS label in a GNS zone can't
+// be verified. This is only possible in Query.Verify().
+func (b *GNSBlock) Verify() (err error) {
+ // verify signature
+ var buf []byte
+ if buf, err = data.Marshal(b.Body); err != nil {
+ return
+ }
+ _, err = b.DerivedKeySig.Verify(buf)
+ return
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package blocks
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "gnunet/util"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/bfix/gospel/crypto/ed25519"
+ "github.com/bfix/gospel/data"
+)
+
+//----------------------------------------------------------------------
+// HELLO URLs are used for bootstrapping a node and for adding nodes
+// outside of GNUnet message exchange (e.g. command-line tools)
+//----------------------------------------------------------------------
+
+const helloPrefix = "gnunet://hello/"
+
+// HelloBlock is the DHT-managed block type for HELLO information.
+// It is used to create and parse HELLO URLs.
+// All addresses expire at the same time /this different from HELLO
+// messages (see message.HeeloMsg).
+type HelloBlock struct {
+ PeerID *util.PeerID `` // peer identifier
+ Signature *ed25519.EdSignature `` // signature
+ Expire util.AbsoluteTime `` // Expiration date
+ AddrBin []byte `size:"*"` // raw address data
+
+ // transient attributes
+ addrs []*util.Address // cooked address data
+}
+
+// SetAddresses adds a bulk of addresses for this HELLO block.
+func (h *HelloBlock) SetAddresses(a []*util.Address) {
+ h.addrs = util.Clone(a)
+ h.finalize()
+}
+
+// Addresses returns the list of addresses
+func (h *HelloBlock) Addresses() []*util.Address {
+ return util.Clone(h.addrs)
+}
+
+// ParseHelloURL parses a HELLO URL of the following form:
+// gnunet://hello/<PeerID>/<signature>/<expire>?<addrs>
+// The addresses are encoded.
+func ParseHelloURL(u string) (h *HelloBlock, err error) {
+ // check and trim prefix
+ if !strings.HasPrefix(u, helloPrefix) {
+ err = fmt.Errorf("invalid HELLO-URL prefix: '%s'", u)
+ return
+ }
+ u = u[len(helloPrefix):]
+
+ // split remainder into parts
+ p := strings.Split(u, "/")
+ if len(p) != 3 {
+ err = fmt.Errorf("invalid HELLO-URL: '%s'", u)
+ return
+ }
+
+ // assemble HELLO data
+ h = new(HelloBlock)
+
+ // (1) parse peer public key (peer ID)
+ var buf []byte
+ if buf, err = util.DecodeStringToBinary(p[0], 32); err != nil {
+ return
+ }
+ h.PeerID = util.NewPeerID(buf)
+
+ // (2) parse signature
+ if buf, err = util.DecodeStringToBinary(p[1], 64); err != nil {
+ return
+ }
+ if h.Signature, err = ed25519.NewEdSignatureFromBytes(buf); err != nil {
+ return
+ }
+
+ // (3) split last element into parts
+ q := strings.SplitN(p[2], "?", 2)
+
+ // (4) parse expiration date
+ var exp uint64
+ if exp, err = strconv.ParseUint(q[0], 10, 64); err != nil {
+ return
+ }
+ h.Expire = util.NewAbsoluteTimeEpoch(exp)
+
+ // (5) process addresses.
+ h.addrs = make([]*util.Address, 0)
+ var ua string
+ for _, a := range strings.Split(q[1], "&") {
+ // unescape URL query
+ if ua, err = url.QueryUnescape(a); err != nil {
+ return
+ }
+ // parse address and append it to list
+ var addr *util.Address
+ if addr, err = util.ParseAddress(ua); err != nil {
+ return
+ }
+ h.addrs = append(h.addrs, addr)
+ }
+
+ // (6) generate raw address data so block is complete
+ h.finalize()
+ return
+}
+
+// ParseHelloFromBytes converts a byte array into a HelloBlock instance.
+func ParseHelloFromBytes(buf []byte) (h *HelloBlock, err error) {
+ h = new(HelloBlock)
+ if err = data.Unmarshal(h, buf); err == nil {
+ err = h.finalize()
+ }
+ return
+}
+
+// finalize block data (generate dependent fields)
+func (h *HelloBlock) finalize() (err error) {
+ if h.addrs == nil {
+ err = data.Unmarshal(h.addrs, h.AddrBin)
+ } else if h.AddrBin == nil {
+ wrt := new(bytes.Buffer)
+ for _, a := range h.addrs {
+ wrt.WriteString(a.String())
+ wrt.WriteByte(0)
+ }
+ h.AddrBin = wrt.Bytes()
+ }
+ return
+}
+
+/*
+// Message returns the corresponding HELLO message to be sent to peers.
+func (h *HelloBlock) Message() *message.HelloMsg {
+ msg := message.NewHelloMsg(h.PeerID)
+ for _, a := range h.addrs {
+ msg.AddAddress(message.NewHelloAddress(a, h.Expire))
+ }
+ return msg
+}
+*/
+
+// URL returns the HELLO URL for the data.
+func (h *HelloBlock) URL() string {
+ u := fmt.Sprintf("%s%s/%s/%d?",
+ helloPrefix,
+ h.PeerID.String(),
+ util.EncodeBinaryToString(h.Signature.Bytes()),
+ h.Expire.Epoch(),
+ )
+ for i, a := range h.addrs {
+ if i > 0 {
+ u += "&"
+ }
+ u += url.QueryEscape(a.String())
+ }
+ return u
+}
+
+// Equals returns true if two HELLOs are the same. The expiration
+// timestamp is ignored in the comparision.
+func (h *HelloBlock) Equals(g *HelloBlock) bool {
+ if !h.PeerID.Equals(g.PeerID) ||
+ !util.Equals(h.Signature.Bytes(), g.Signature.Bytes()) ||
+ len(h.addrs) != len(g.addrs) {
+ return false
+ }
+ for i, a := range h.addrs {
+ if !a.Equals(g.addrs[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// Verify the integrity of the HELLO data
+func (h *HelloBlock) Verify() (bool, error) {
+ // assemble signed data and public key
+ sd := h.signedData()
+ pub := ed25519.NewPublicKeyFromBytes(h.PeerID.Key)
+ return pub.EdVerify(sd, h.Signature)
+}
+
+// Sign the HELLO data with private key
+func (h *HelloBlock) Sign(prv *ed25519.PrivateKey) (err error) {
+ // assemble signed data
+ sd := h.signedData()
+ h.Signature, err = prv.EdSign(sd)
+ return
+}
+
+// signedData assembles a data block for sign and verify operations.
+func (h *HelloBlock) signedData() []byte {
+ buf := new(bytes.Buffer)
+ buf.Write(h.PeerID.Key)
+ binary.Write(buf, binary.BigEndian, h.Expire)
+ for _, a := range h.addrs {
+ buf.Write(a.Address)
+ }
+ return buf.Bytes()
+}
diff --git a/src/gnunet/util/fs.go b/src/gnunet/service/dht/blocks/hello_test.go
similarity index 60%
copy from src/gnunet/util/fs.go
copy to src/gnunet/service/dht/blocks/hello_test.go
index 009ef62..089259a 100644
--- a/src/gnunet/util/fs.go
+++ b/src/gnunet/service/dht/blocks/hello_test.go
@@ -1,5 +1,5 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -16,28 +16,29 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
-package util
+package blocks
-import (
- "fmt"
- "os"
+import "testing"
- "github.com/bfix/gospel/logger"
+const (
+ helloURL = "gnunet://hello" +
+ "/7KTBJ90340HF1Q2GB0A57E2XJER4FDHX8HP5GHEB9125VPWPD27G" +
+
+ "/BNMDFN6HJCPWSPNBSEC06MC1K8QN1Z2DHRQSRXDTFR7FTBD4JHN" +
+ "BJ2RJAAEZ31FWG1Q3PMN3PXGZQ3Q7NTNEKQZFA7TE2Y46FM8E20R" +
+ "/1653499308" +
+ "?r5n%2Bip%2Budp%3A1.2.3.4%3A6789" +
+ "&gnunet%2Btcp%3A12.3.4.5"
)
-// EnforceDirExists make sure that the path
-func EnforceDirExists(path string) error {
- logger.Printf(logger.DBG, "[util] Checking directory '%s'...\n", path)
- fi, err := os.Lstat(path)
+func TestHelloURL(t *testing.T) {
+
+ hd, err := ParseHelloURL(helloURL)
if err != nil {
- if os.IsNotExist(err) {
- logger.Printf(logger.DBG, "[util] Creating directory
'%s'...\n", path)
- return os.Mkdir(path, 0770)
- }
- return err
+ t.Fatal(err)
}
- if !fi.IsDir() {
- return fmt.Errorf("Not a directory (%s)", path)
+ u := hd.URL()
+ if u != helloURL {
+ t.Fatal("urls don't match")
}
- return nil
}
diff --git a/src/gnunet/transport/session.go
b/src/gnunet/service/dht/blocks/types.go
similarity index 63%
rename from src/gnunet/transport/session.go
rename to 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 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -16,14 +16,11 @@
//
// SPDX-License-Identifier: AGPL3.0-or-later
-package transport
+package blocks
-// Session states
+// DHT Block types
const (
- KxStateDown = iota // No handshake yet.
- KxStateKeySent // We've sent our session key.
- KxStateKeyReceived // We've received the other peers session key.
- KxStateUp // Key exchange is done.
- KxStateRekeySent // We're rekeying (or had a timeout).
- KxPeerDisconnect // Last state of a KX (when it is being
terminated).
+ DHT_BLOCK_ANY = 0
+ DHT_BLOCK_HELLO = 7 // Type of a block that contains a HELLO for a peer
+ DHT_BLOCK_GNS = 11 // Block for storing record data
)
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+ "bytes"
+ "crypto/sha512"
+ "encoding/binary"
+)
+
+//======================================================================
+// Generic BloomFilter
+//======================================================================
+
+// BloomFilter parameter
+var (
+ bfNumBits = 128
+ bfHash = sha512.New
+)
+
+// BloomFilter is a space-efficient probabilistic datastructure to test if
+// an element is part of a set of elementsis defined as a string of bits
+// always initially empty.
+type BloomFilter struct {
+ data []byte // filter bits
+ salt []byte // salt for hashing
+}
+
+// NewBloomFilter cretes a new filter using the specified salt. An unused
+// salt is set to nil.
+func NewBloomFilter(salt []byte) *BloomFilter {
+ return &BloomFilter{
+ data: make([]byte, (bfNumBits+7)/8),
+ salt: salt,
+ }
+}
+
+// Add entry (binary representation):
+// When adding an element to the Bloom filter bf using BF-SET(bf,e), each
+// integer n of the mapping M(e) is interpreted as a bit offset n mod L
+// within bf and set to 1.
+func (bf *BloomFilter) Add(e []byte) {
+ for _, idx := range bf.indices(e) {
+ bf.data[idx/8] |= (1 << (idx % 7))
+ }
+}
+
+// Contains returns true if the entry is most likely to be included:
+// When testing if an element may be in the Bloom filter bf using
+// BF-TEST(bf,e), each bit offset n mod L within bf MUST have been set to 1.
+// Otherwise, the element is not considered to be in the Bloom filter.
+func (bf *BloomFilter) Contains(e []byte) bool {
+ for _, idx := range bf.indices(e) {
+ if bf.data[idx/8]&(1<<(idx%7)) == 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// indices returns the list of bit indices for antry e:
+// The element e is prepended with a salt (pütional) and hashed using SHA-512.
+// The resulting byte string is interpreted as a list of 16 32-bit integers
+// in network byte order.
+func (bf *BloomFilter) indices(e []byte) []int {
+ // hash the entry (with optional salt prepended)
+ hsh := bfHash()
+ if bf.salt != nil {
+ hsh.Write(bf.salt)
+ }
+ hsh.Write(e)
+ h := hsh.Sum(nil)
+
+ // compute the indices for the entry
+ idx := make([]int, len(h)/2)
+ buf := bytes.NewReader(h)
+ for i := range idx {
+ binary.Read(buf, binary.BigEndian, &idx[i])
+ }
+ return idx
+}
+
+//======================================================================
+// BloomFilter for peer addresses
+//======================================================================
+
+// PeerBloomFilter implements specific Add/Contains functions.
+type PeerBloomFilter struct {
+ BloomFilter
+}
+
+// NewPeerBloomFilter creates a new filter for peer addresses.
+func NewPeerBloomFilter() *PeerBloomFilter {
+ return &PeerBloomFilter{
+ BloomFilter: *NewBloomFilter(nil),
+ }
+}
+
+// Add peer address to the filter.
+func (bf *PeerBloomFilter) Add(p *PeerAddress) {
+ bf.BloomFilter.Add(p.addr[:])
+}
+
+// Contains returns true if the peer address is most likely to be included.
+func (bf *PeerBloomFilter) Contains(p *PeerAddress) bool {
+ return bf.BloomFilter.Contains(p.addr[:])
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+ "encoding/hex"
+ "gnunet/crypto"
+ "gnunet/service"
+ blocks "gnunet/service/dht/blocks"
+ "math/rand"
+ "testing"
+)
+
+// test constants
+const (
+ fsNumBlocks = 5
+)
+
+// TestDHTFileStore generates 'fsNumBlocks' fully-random blocks
+// and stores them under their SHA512 key. It than retrieves
+// each block from storage and checks for matching hash.
+func TestDHTFilesStore(t *testing.T) {
+
+ // create file store
+ fs, err := service.NewFileCache("/var/lib/gnunet/dht/cache", "100")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // allocate keys
+ keys := make([]blocks.Query, 0, fsNumBlocks)
+
+ // First round: save blocks
+ for i := 0; i < fsNumBlocks; i++ {
+ // generate random block
+ size := 20 // 1024 + rand.Intn(62000)
+ buf := make([]byte, size)
+ rand.Read(buf)
+ val := blocks.NewGenericBlock(buf)
+ // generate associated key
+ k := crypto.Hash(buf).Bits
+ key := blocks.NewGenericQuery(k)
+ t.Logf("> %d: %s -- %s", i, hex.EncodeToString(k),
hex.EncodeToString(buf))
+
+ // store block
+ if err := fs.Put(key, val); err != nil {
+ t.Fatal(err)
+ }
+
+ // remember key
+ keys = append(keys, key)
+ }
+
+ // Second round: retrieve blocks and check
+ for i, key := range keys {
+ // get block
+ val, err := fs.Get(key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ buf := val.Data()
+ t.Logf("< %d: %s -- %s", i, hex.EncodeToString(key.Key().Bits),
hex.EncodeToString(buf))
+
+ // re-create key
+ k := crypto.Hash(buf)
+
+ // do the keys match?
+ if !k.Equals(key.Key()) {
+ t.Log(hex.EncodeToString(k.Bits))
+ t.Log(hex.EncodeToString(key.Key().Bits))
+ t.Fatal("key/value mismatch")
+ }
+ }
+}
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 @@
package dht
import (
+ "context"
+ "gnunet/config"
+ "gnunet/core"
"gnunet/message"
"gnunet/service"
- "gnunet/service/gns"
+ "gnunet/service/dht/blocks"
+ "net/http"
)
//======================================================================
@@ -32,16 +36,99 @@ import (
// Put and get blocks into/from a DHT.
//----------------------------------------------------------------------
-// Module handles the permanent storage of blocks under the query key.
+// Module handles the permanent storage of blocks under a query key.
type Module struct {
+ service.ModuleImpl
+
+ store service.DHTStore // reference to the block storage mechanism
+ cache service.DHTStore // transient block cache
+ core *core.Core // reference to core services
+
+ rtable *RoutingTable // routing table
+}
+
+// NewModule returns a new module instance. It initializes the storage
+// mechanism for persistence.
+func NewModule(ctx context.Context, c *core.Core) (m *Module) {
+ // create permanent storage handler
+ store, err := service.NewDHTStore(config.Cfg.DHT.Storage)
+ if err != nil {
+ return nil
+ }
+ // create cache handler
+ cache, err := service.NewDHTStore(config.Cfg.DHT.Cache)
+ if err != nil {
+ return nil
+ }
+ // create routing table
+ rt := NewRoutingTable(NewPeerAddress(c.PeerID()))
+
+ // return module instance
+ m = &Module{
+ ModuleImpl: *service.NewModuleImpl(),
+ store: store,
+ cache: cache,
+ core: c,
+ rtable: rt,
+ }
+ // register as listener for core events
+ listener := m.Run(ctx, m.event, m.Filter())
+ c.Register("dht", listener)
+
+ return
}
-// Get a GNS block from the DHT
-func (nc *Module) Get(ctx *service.SessionContext, query *gns.Query)
(*message.Block, error) {
+//----------------------------------------------------------------------
+
+// Get a block from the DHT
+func (nc *Module) Get(ctx context.Context, query blocks.Query) (block
blocks.Block, err error) {
+
+ // check if we have the requested block in cache or permanent storage.
+ block, err = nc.cache.Get(query)
+ if err == nil {
+ // yes: we are done
+ return
+ }
+ block, err = nc.store.Get(query)
+ if err == nil {
+ // yes: we are done
+ return
+ }
+ // retrieve the block from the DHT
+
return nil, nil
}
-// Put a GNS block into the DHT
-func (nc *Module) Put(ctx *service.SessionContext, block *message.Block) error
{
+// Put a block into the DHT
+func (nc *Module) Put(ctx context.Context, key blocks.Query, block
blocks.Block) error {
return nil
}
+
+//----------------------------------------------------------------------
+
+// Filter returns the event filter for the module
+func (m *Module) Filter() *core.EventFilter {
+ f := core.NewEventFilter()
+ f.AddEvent(core.EV_CONNECT)
+ f.AddEvent(core.EV_DISCONNECT)
+ f.AddMsgType(message.DHT_CLIENT_GET)
+ f.AddMsgType(message.DHT_CLIENT_GET_RESULTS_KNOWN)
+ f.AddMsgType(message.DHT_CLIENT_GET_STOP)
+ f.AddMsgType(message.DHT_CLIENT_PUT)
+ f.AddMsgType(message.DHT_CLIENT_RESULT)
+ return f
+}
+
+// Event handler
+func (nc *Module) event(ctx context.Context, ev *core.Event) {
+
+}
+
+//----------------------------------------------------------------------
+
+// RPC returns the route and handler function for a JSON-RPC request
+func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
+ return "/gns/", func(wrt http.ResponseWriter, req *http.Request) {
+ wrt.Write([]byte(`{"msg": "This is DHT" }`))
+ }
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+ "bytes"
+ "crypto/sha512"
+ "encoding/hex"
+ "gnunet/util"
+ "math/rand"
+ "sync"
+
+ "github.com/bfix/gospel/math"
+)
+
+var (
+ // routing table hash function: defines number of
+ // buckets and size of peer addresses
+ rtHash = sha512.New
+)
+
+// Routing table contants (adjust with changing hash function)
+const (
+ numBuckets = 512 // number of bits of hash function result
+ numK = 20 // number of entries per k-bucket
+ sizeAddr = 64 // size of peer address in bytes
+)
+
+//======================================================================
+//======================================================================
+
+// PeerAddress is the identifier for a peer in the DHT network.
+// It is the SHA-512 hash of the PeerID (public Ed25519 key).
+type PeerAddress struct {
+ addr [sizeAddr]byte
+}
+
+// NewPeerAddress returns the DHT address of a peer.
+func NewPeerAddress(peer *util.PeerID) *PeerAddress {
+ r := new(PeerAddress)
+ h := rtHash()
+ h.Write(peer.Key)
+ copy(r.addr[:], h.Sum(nil))
+ return r
+}
+
+// String returns a human-readble representation of an address.
+func (addr *PeerAddress) String() string {
+ return hex.EncodeToString(addr.addr[:])
+}
+
+// Equals returns true if two peer addresses are the same.
+func (addr *PeerAddress) Equals(p *PeerAddress) bool {
+ return bytes.Equal(addr.addr[:], p.addr[:])
+}
+
+// Distance between two addresses: returns a distance value and a
+// bucket index (smaller index = less distant).
+func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) {
+ var d PeerAddress
+ for i := range d.addr {
+ d.addr[i] = addr.addr[i] ^ p.addr[i]
+ }
+ r := math.NewIntFromBytes(d.addr[:])
+ return r, numBuckets - r.BitLen()
+}
+
+//======================================================================
+// Routing table implementation
+//======================================================================
+
+// RoutingTable holds the (local) routing table for a node.
+// The index of of an address is the number of bits in the
+// distance to the reference address, so smaller index means
+// "nearer" to the reference address.
+type RoutingTable struct {
+ ref *PeerAddress // reference address for distance
+ buckets []*Bucket // list of buckets
+ list map[*PeerAddress]bool // keep list of peers
+ rwlock sync.RWMutex // lock for write operations
+ l2nse float64 // log2 of estimated network size
+}
+
+// NewRoutingTable creates a new routing table for the reference address.
+func NewRoutingTable(ref *PeerAddress) *RoutingTable {
+ rt := new(RoutingTable)
+ rt.ref = ref
+ rt.list = make(map[*PeerAddress]bool)
+ rt.buckets = make([]*Bucket, numBuckets)
+ for i := range rt.buckets {
+ rt.buckets[i] = NewBucket(numK)
+ }
+ return rt
+}
+
+// Add new peer address to routing table.
+// Returns true if the entry was added, false otherwise.
+func (rt *RoutingTable) Add(p *PeerAddress, connected bool) bool {
+ // ensure one write and no readers
+ rt.rwlock.Lock()
+ defer rt.rwlock.Unlock()
+
+ // compute distance (bucket index) and insert address.
+ _, idx := p.Distance(rt.ref)
+ if rt.buckets[idx].Add(p, connected) {
+ rt.list[p] = true
+ return true
+ }
+ // Full bucket: we did not add the address to the routing table.
+ return false
+}
+
+// Remove peer address from routing table.
+// Returns true if the entry was removed, false otherwise.
+func (rt *RoutingTable) Remove(p *PeerAddress) bool {
+ // ensure one write and no readers
+ rt.rwlock.Lock()
+ defer rt.rwlock.Unlock()
+
+ // compute distance (bucket index) and remove entry from bucket
+ _, idx := p.Distance(rt.ref)
+ if rt.buckets[idx].Remove(p) {
+ delete(rt.list, p)
+ return true
+ }
+ return false
+}
+
+//----------------------------------------------------------------------
+// routing functions
+//----------------------------------------------------------------------
+
+// SelectClosestPeer for a given peer address and bloomfilter.
+func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter)
(n *PeerAddress) {
+ // no writer allowed
+ rt.rwlock.RLock()
+ defer rt.rwlock.RUnlock()
+
+ // find closest address
+ var dist *math.Int
+ for _, b := range rt.buckets {
+ if k, d := b.SelectClosestPeer(p, bf); n == nil || (d != nil &&
d.Cmp(dist) < 0) {
+ dist = d
+ n = k
+ }
+ }
+ return
+}
+
+// SelectRandomPeer returns a random address from table (that is not
+// included in the bloomfilter)
+func (rt *RoutingTable) SelectRandomPeer(bf *PeerBloomFilter) *PeerAddress {
+ // no writer allowed
+ rt.rwlock.RLock()
+ defer rt.rwlock.RUnlock()
+
+ // select random entry from list
+ if size := len(rt.list); size > 0 {
+ idx := rand.Intn(size)
+ for k := range rt.list {
+ if idx == 0 {
+ return k
+ }
+ idx--
+ }
+ }
+ return nil
+}
+
+// SelectPeer selects a neighbor depending on the number of hops parameter.
+// If hops < NSE this function MUST return SelectRandomPeer() and
+// SelectClosestpeer() otherwise.
+func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf
*PeerBloomFilter) *PeerAddress {
+ if float64(hops) < rt.l2nse {
+ return rt.SelectRandomPeer(bf)
+ }
+ return rt.SelectClosestPeer(p, bf)
+}
+
+// IsClosestPeer returns true if p is the closest peer for k. Peers with a
+// positive test in the Bloom filter are not considered.
+func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf *PeerBloomFilter)
bool {
+ n := rt.SelectClosestPeer(k, bf)
+ return n.Equals(p)
+}
+
+// ComputeOutDegree computes the number of neighbors that a message should be
forwarded to.
+// The arguments are the desired replication level, the hop count of the
message so far,
+// and the base-2 logarithm of the current network size estimate (L2NSE) as
provided by the
+// underlay. The result is the non-negative number of next hops to select.
+func (rt *RoutingTable) ComputeOutDegree(repl, hop int) int {
+ hf := float64(hop)
+ if hf > 4*rt.l2nse {
+ return 0
+ }
+ if hf > 2*rt.l2nse {
+ return 1
+ }
+ if repl == 0 {
+ repl = 1
+ } else if repl > 16 {
+ repl = 16
+ }
+ rm1 := float64(repl - 1)
+ return 1 + int(rm1/(rt.l2nse+rm1*hf))
+}
+
+//======================================================================
+// Routing table buckets
+//======================================================================
+
+// PeerEntry in a k-Bucket: use routing specific attributes
+// for book-keeping
+type PeerEntry struct {
+ addr *PeerAddress // peer address
+ connected bool // is peer connected?
+}
+
+// Bucket holds peer entries with approx. same distance from node
+type Bucket struct {
+ list []*PeerEntry
+ rwlock sync.RWMutex
+}
+
+// NewBucket creates a new entry list of given size
+func NewBucket(n int) *Bucket {
+ return &Bucket{
+ list: make([]*PeerEntry, 0, n),
+ }
+}
+
+// Add peer address to the bucket if there is free space.
+// Returns true if entry is added, false otherwise.
+func (b *Bucket) Add(p *PeerAddress, connected bool) bool {
+ // only one writer and no readers
+ b.rwlock.Lock()
+ defer b.rwlock.Unlock()
+
+ // check for free space in bucket
+ if len(b.list) < numK {
+ // append entry at the end
+ pe := &PeerEntry{
+ addr: p,
+ connected: connected,
+ }
+ b.list = append(b.list, pe)
+ return true
+ }
+ return false
+}
+
+// Remove peer address from the bucket.
+// Returns true if entry is removed (found), false otherwise.
+func (b *Bucket) Remove(p *PeerAddress) bool {
+ // only one writer and no readers
+ b.rwlock.Lock()
+ defer b.rwlock.Unlock()
+
+ for i, pe := range b.list {
+ if pe.addr.Equals(p) {
+ // found entry: remove it
+ b.list = append(b.list[:i], b.list[i+1:]...)
+ return true
+ }
+ }
+ return false
+}
+
+// SelectClosestPeer returns the entry with minimal distance to the given
+// peer address; entries included in the bloom flter are ignored.
+func (b *Bucket) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n
*PeerAddress, dist *math.Int) {
+ // no writer allowed
+ b.rwlock.RLock()
+ defer b.rwlock.RUnlock()
+
+ for _, pe := range b.list {
+ // skip addresses in bloomfilter
+ if bf.Contains(pe.addr) {
+ continue
+ }
+ // check for shorter distance
+ if d, _ := p.Distance(pe.addr); n == nil || d.Cmp(dist) < 0 {
+ // remember best match
+ dist = d
+ n = pe.addr
+ }
+ }
+ return
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+ "gnunet/config"
+ "gnunet/core"
+ "gnunet/util"
+ "math/rand"
+ "testing"
+)
+
+const (
+ NUMP = 1000 // Total number of peers
+ EPOCHS = 10000 // number of epochs to run
+)
+
+type Entry struct {
+ addr *PeerAddress // address of peer
+ ttl int64 // time to live (in epochs)
+ born int64 // epoch of birth
+ last int64 // last action
+ drop int64 // drop (in epochs)
+ revive int64 // revive dropped (in epochs)
+ online bool // peer connected?
+}
+
+// test data
+var (
+ cfg = &config.NodeConfig{
+ PrivateSeed: "YGoe6XFH3XdvFRl+agx9gIzPTvxA229WFdkazEMdcOs=",
+ Endpoints: []string{
+ "r5n+ip+udp://127.0.0.1:6666",
+ },
+ }
+)
+
+// TestRT connects and disconnects random peers to test the base
+// functionality of the routing table algorithms.
+func TestRT(t *testing.T) {
+ // start deterministic randomizer
+ rand.Seed(19031962)
+
+ // helper functions
+ genRemotePeer := func() *PeerAddress {
+ d := make([]byte, 32)
+ if _, err := rand.Read(d); err != nil {
+ panic(err)
+ }
+ return NewPeerAddress(util.NewPeerID(d))
+ }
+
+ // create routing table and start command handler
+ local, err := core.NewLocalPeer(cfg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rt := NewRoutingTable(NewPeerAddress(local.GetID()))
+
+ // create a task list
+ tasks := make([]*Entry, NUMP)
+ for i := range tasks {
+ tasks[i] = new(Entry)
+ tasks[i].addr = genRemotePeer()
+ tasks[i].born = rand.Int63n(EPOCHS)
+ tasks[i].ttl = 1000 + rand.Int63n(7000)
+ tasks[i].drop = 2000 + rand.Int63n(3000)
+ tasks[i].revive = rand.Int63n(2000)
+ tasks[i].online = false
+ }
+
+ // actions:
+ connected := func(task *Entry, e int64, msg string) {
+ rt.Add(task.addr, true)
+ task.online = true
+ task.last = e
+ t.Logf("[%6d] %s %s\n", e, task.addr, msg)
+ }
+ disconnected := func(task *Entry, e int64, msg string) {
+ rt.Remove(task.addr)
+ task.online = false
+ task.last = e
+ t.Logf("[%6d] %s %s\n", e, task.addr, msg)
+ }
+
+ // run epochs
+ var e int64
+ for e = 0; e < EPOCHS; e++ {
+ for _, task := range tasks {
+ // birth
+ if task.born == e {
+ connected(task, e, "connected")
+ continue
+ }
+ // death
+ if task.born+task.ttl == e {
+ disconnected(task, e, "disconnected")
+ continue
+ }
+ if task.online {
+ // drop out
+ if task.last+task.drop == e {
+ disconnected(task, e, "dropped out")
+ continue
+ }
+ } else {
+ // drop in
+ if task.last+task.drop == e {
+ connected(task, e, "dropped in")
+ continue
+ }
+ }
+ }
+ }
+
+ // execute some routing functions on remaining table
+ k := genRemotePeer()
+ bf := NewPeerBloomFilter()
+ n := rt.SelectClosestPeer(k, bf)
+ t.Logf("Closest: %s -> %s\n", k, n)
+
+ n = rt.SelectRandomPeer(bf)
+ t.Logf("Random: %s\n", n)
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019, 2020 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package dht
+
+import (
+ "context"
+ "fmt"
+ "io"
+
+ "gnunet/core"
+ "gnunet/message"
+ "gnunet/service"
+
+ "github.com/bfix/gospel/logger"
+)
+
+// Error codes
+var (
+ ErrInvalidID = fmt.Errorf("invalid/unassociated ID")
+ ErrBlockExpired = fmt.Errorf("block expired")
+ ErrInvalidResponseType = fmt.Errorf("invald response type")
+)
+
+//----------------------------------------------------------------------
+// "GNUnet R5N DHT" service implementation
+//----------------------------------------------------------------------
+
+// Service implements a DHT service
+type Service struct {
+ Module
+}
+
+// NewService creates a new DHT service instance
+func NewService(ctx context.Context, c *core.Core) service.Service {
+ return &Service{
+ Module: *NewModule(ctx, c),
+ }
+}
+
+// ServeClient processes a client channel.
+func (s *Service) ServeClient(ctx context.Context, id int, mc
*service.Connection) {
+ reqID := 0
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithCancel(ctx)
+
+loop:
+ for {
+ // receive next message from client
+ reqID++
+ logger.Printf(logger.DBG, "[dht:%d:%d] Waiting for client
request...\n", id, reqID)
+ msg, err := mc.Receive(ctx)
+ if err != nil {
+ if err == io.EOF {
+ logger.Printf(logger.INFO, "[dht:%d:%d] Client
channel closed.\n", id, reqID)
+ } else if err == service.ErrConnectionInterrupted {
+ logger.Printf(logger.INFO, "[dht:%d:%d] Service
operation interrupted.\n", id, reqID)
+ } else {
+ logger.Printf(logger.ERROR, "[dht:%d:%d]
Message-receive failed: %s\n", id, reqID, err.Error())
+ }
+ break loop
+ }
+ logger.Printf(logger.INFO, "[dht:%d:%d] Received request:
%v\n", id, reqID, msg)
+
+ // handle message
+ s.HandleMessage(context.WithValue(ctx, "label",
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+ }
+ // close client connection
+ mc.Close()
+
+ // cancel all tasks running for this session/connection
+ logger.Printf(logger.INFO, "[dht:%d] Start closing session...\n", id)
+ cancel()
+}
+
+// HandleMessage handles a DHT request/response message. If the transport
channel
+// is nil, responses are send directly via the transport layer.
+func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back
service.Responder) bool {
+ // assemble log label
+ label := ""
+ if v := ctx.Value("label"); v != nil {
+ label = v.(string)
+ }
+ // process message
+ switch msg.(type) {
+ case *message.DHTClientPutMsg:
+ //----------------------------------------------------------
+ // DHT PUT
+ //----------------------------------------------------------
+
+ case *message.DHTClientGetMsg:
+ //----------------------------------------------------------
+ // DHT GET
+ //----------------------------------------------------------
+
+ case *message.DHTClientGetResultsKnownMsg:
+ //----------------------------------------------------------
+ // DHT GET-RESULTS-KNOWN
+ //----------------------------------------------------------
+
+ case *message.DHTClientGetStopMsg:
+ //----------------------------------------------------------
+ // DHT GET-STOP
+ //----------------------------------------------------------
+
+ case *message.DHTClientResultMsg:
+ //----------------------------------------------------------
+ // DHT RESULT
+ //----------------------------------------------------------
+
+ default:
+ //----------------------------------------------------------
+ // UNKNOWN message type received
+ //----------------------------------------------------------
+ logger.Printf(logger.ERROR, "[dht%s] Unhandled message of type
(%d)\n", label, msg.Header().MsgType)
+ return false
+ }
+ return true
+}
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 {
// resource records in the same block. 'cm' maps the resource type
// to an integer count (how many records of a type are present in the
// GNS block).
- Coexist(cm util.CounterMap) bool
+ Coexist(cm util.Counter[int]) bool
// Records returns a list of RR of the given types associated with
// the custom handler
@@ -103,7 +103,7 @@ type BlockHandler interface {
// BlockHandlerList is a list of block handlers instantiated.
type BlockHandlerList struct {
list map[int]BlockHandler // list of handler instances
- counts util.CounterMap // count number of RRs by type
+ counts util.Counter[int] // count number of RRs by type
}
// NewBlockHandlerList instantiates an a list of active block handlers
@@ -112,7 +112,7 @@ func NewBlockHandlerList(records []*message.ResourceRecord,
labels []string) (*B
// initialize block handler list
hl := &BlockHandlerList{
list: make(map[int]BlockHandler),
- counts: make(util.CounterMap),
+ counts: make(util.Counter[int]),
}
// first pass: build list of shadow records in this block
@@ -260,7 +260,7 @@ func (h *ZoneKeyHandler) AddRecord(rec
*message.ResourceRecord, labels []string)
// Coexist return a flag indicating how a resource record of a given type
// is to be treated (see BlockHandler interface)
-func (h *ZoneKeyHandler) Coexist(cm util.CounterMap) bool {
+func (h *ZoneKeyHandler) Coexist(cm util.Counter[int]) bool {
// only one type (GNS_TYPE_PKEY) is present
return len(cm) == 1 && cm.Num(enums.GNS_TYPE_PKEY) == 1
}
@@ -335,7 +335,7 @@ func (h *Gns2DnsHandler) AddRecord(rec
*message.ResourceRecord, labels []string)
// Coexist return a flag indicating how a resource record of a given type
// is to be treated (see BlockHandler interface)
-func (h *Gns2DnsHandler) Coexist(cm util.CounterMap) bool {
+func (h *Gns2DnsHandler) Coexist(cm util.Counter[int]) bool {
// only one type (GNS_TYPE_GNS2DNS) is present
return len(cm) == 1 && cm.Num(enums.GNS_TYPE_GNS2DNS) > 0
}
@@ -405,7 +405,7 @@ func (h *BoxHandler) AddRecord(rec *message.ResourceRecord,
labels []string) err
// Coexist return a flag indicating how a resource record of a given type
// is to be treated (see BlockHandler interface)
-func (h *BoxHandler) Coexist(cm util.CounterMap) bool {
+func (h *BoxHandler) Coexist(cm util.Counter[int]) bool {
// anything goes...
return true
}
@@ -469,7 +469,7 @@ func (h *LehoHandler) AddRecord(rec
*message.ResourceRecord, labels []string) er
// Coexist return a flag indicating how a resource record of a given type
// is to be treated (see BlockHandler interface)
-func (h *LehoHandler) Coexist(cm util.CounterMap) bool {
+func (h *LehoHandler) Coexist(cm util.Counter[int]) bool {
// requires exactly one LEHO and any number of other records.
return cm.Num(enums.GNS_TYPE_LEHO) == 1
}
@@ -527,7 +527,7 @@ func (h *CnameHandler) AddRecord(rec
*message.ResourceRecord, labels []string) e
// Coexist return a flag indicating how a resource record of a given type
// is to be treated (see BlockHandler interface)
-func (h *CnameHandler) Coexist(cm util.CounterMap) bool {
+func (h *CnameHandler) Coexist(cm util.Counter[int]) bool {
// only a single CNAME allowed
return len(cm) == 1 && cm.Num(enums.GNS_TYPE_DNS_CNAME) == 1
}
@@ -581,7 +581,7 @@ func (h *VpnHandler) AddRecord(rec *message.ResourceRecord,
labels []string) err
// Coexist return a flag indicating how a resource record of a given type
// is to be treated (see BlockHandler interface)
-func (h *VpnHandler) Coexist(cm util.CounterMap) bool {
+func (h *VpnHandler) Coexist(cm util.Counter[int]) bool {
// anything goes
return true
}
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 @@
package gns
import (
+ "context"
"fmt"
"net"
"strings"
@@ -27,7 +28,6 @@ import (
"gnunet/crypto"
"gnunet/enums"
"gnunet/message"
- "gnunet/service"
"gnunet/util"
"github.com/bfix/gospel/logger"
@@ -205,7 +205,7 @@ func QueryDNS(id int, name string, server net.IP, kind
RRTypeList) *message.Reco
// parallel; the first result delivered by any of the servers is returned
// as the result list of matching resource records.
func (gns *Module) ResolveDNS(
- ctx *service.SessionContext,
+ ctx context.Context,
name string,
servers []string,
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 @@
package gns
import (
+ "context"
"fmt"
"net/http"
"strings"
"gnunet/config"
+ "gnunet/core"
"gnunet/crypto"
"gnunet/enums"
"gnunet/message"
"gnunet/service"
+ "gnunet/service/dht/blocks"
"gnunet/service/revocation"
"gnunet/util"
+ "github.com/bfix/gospel/data"
"github.com/bfix/gospel/logger"
)
@@ -44,34 +48,6 @@ var (
ErrGNSRecursionExceeded = fmt.Errorf("recursion depth exceeded")
)
-//----------------------------------------------------------------------
-// Query for simple GNS lookups
-//----------------------------------------------------------------------
-
-// Query specifies the context for a basic GNS name lookup of an (atomic)
-// label in a given zone identified by its public key.
-type Query struct {
- Zone *crypto.ZoneKey // Public zone key
- Label string // Atomic label
- Derived *crypto.ZoneKey // Derived key from (pkey,label)
- Key *crypto.HashCode // Key for repository queries (local/remote)
-}
-
-// NewQuery assembles a new Query object for the given zone and label.
-func NewQuery(zkey *crypto.ZoneKey, label string) *Query {
- // derive a public key from (pkey,label) and set the repository
- // key as the SHA512 hash of the binary key representation.
- // (key blinding)
- pd, _ := zkey.Derive(label, "gns")
- key := crypto.Hash(pd.Bytes())
- return &Query{
- Zone: zkey,
- Label: label,
- Derived: pd,
- Key: key,
- }
-}
-
//----------------------------------------------------------------------
// The GNS module (recursively) resolves GNS names:
// Resolves DNS-like names (e.g. "minecraft.servers.bob.games"; a name is
@@ -112,25 +88,50 @@ func NewQuery(zkey *crypto.ZoneKey, label string) *Query {
// Module handles the resolution of GNS names to RRs bundled in a block.
type Module struct {
+ service.ModuleImpl
+
// Use function references for calls to methods in other modules:
- LookupLocal func(ctx *service.SessionContext, query *Query)
(*message.Block, error)
- StoreLocal func(ctx *service.SessionContext, block
*message.Block) error
- LookupRemote func(ctx *service.SessionContext, query *Query)
(*message.Block, error)
- RevocationQuery func(ctx *service.SessionContext, zkey
*crypto.ZoneKey) (valid bool, err error)
- RevocationRevoke func(ctx *service.SessionContext, rd
*revocation.RevData) (success bool, err error)
+ LookupLocal func(ctx context.Context, query *blocks.GNSQuery)
(*blocks.GNSBlock, error)
+ StoreLocal func(ctx context.Context, query *blocks.GNSQuery,
block *blocks.GNSBlock) error
+ LookupRemote func(ctx context.Context, query blocks.Query)
(blocks.Block, error)
+ RevocationQuery func(ctx context.Context, zkey *crypto.ZoneKey) (valid
bool, err error)
+ RevocationRevoke func(ctx context.Context, rd *revocation.RevData)
(success bool, err error)
}
-// RPC returns the route and handler function for a JSON-RPC request
-func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
- return "/gns/", func(wrt http.ResponseWriter, req *http.Request) {
- wrt.Write([]byte(`{"msg": "This is GNS" }`))
+func NewModule(ctx context.Context, c *core.Core) (m *Module) {
+ m = &Module{
+ ModuleImpl: *service.NewModuleImpl(),
}
+ // register as listener for core events
+ listener := m.Run(ctx, m.event, m.Filter())
+ c.Register("gns", listener)
+
+ return
+}
+
+//----------------------------------------------------------------------
+
+// Filter returns the event filter for the service
+func (m *Module) Filter() *core.EventFilter {
+ f := core.NewEventFilter()
+ f.AddMsgType(message.GNS_LOOKUP)
+ f.AddMsgType(message.GNS_LOOKUP_RESULT)
+ f.AddMsgType(message.GNS_REVERSE_LOOKUP)
+ f.AddMsgType(message.GNS_REVERSE_LOOKUP_RESULT)
+ return f
}
+// Event handler
+func (m *Module) event(ctx context.Context, ev *core.Event) {
+
+}
+
+//----------------------------------------------------------------------
+
// Resolve a GNS name with multiple labels. If pkey is not nil, the name
// is interpreted as "relative to current zone".
func (m *Module) Resolve(
- ctx *service.SessionContext,
+ ctx context.Context,
path string,
zkey *crypto.ZoneKey,
kind RRTypeList,
@@ -142,7 +143,7 @@ func (m *Module) Resolve(
return nil, ErrGNSRecursionExceeded
}
// get the labels in reverse order
- names := util.ReverseStringList(strings.Split(path, "."))
+ names := util.Reverse(strings.Split(path, "."))
logger.Printf(logger.DBG, "[gns] Resolver called for %v\n", names)
// check for relative path
@@ -157,7 +158,7 @@ func (m *Module) Resolve(
// ResolveAbsolute resolves a fully qualified GNS absolute name
// (with multiple labels).
func (m *Module) ResolveAbsolute(
- ctx *service.SessionContext,
+ ctx context.Context,
labels []string,
kind RRTypeList,
mode int,
@@ -184,7 +185,7 @@ func (m *Module) ResolveAbsolute(
// processing simple (PKEY,Label) lookups in sequence and handle intermediate
// GNS record types
func (m *Module) ResolveRelative(
- ctx *service.SessionContext,
+ ctx context.Context,
labels []string,
zkey *crypto.ZoneKey,
kind RRTypeList,
@@ -200,7 +201,7 @@ func (m *Module) ResolveRelative(
logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in
'%s'\n", labels[0], util.EncodeBinaryToString(zkey.Bytes()))
// resolve next level
- var block *message.Block
+ var block *blocks.GNSBlock
if block, err = m.Lookup(ctx, zkey, labels[0], mode); err !=
nil {
// failed to resolve name
return
@@ -221,7 +222,7 @@ func (m *Module) ResolveRelative(
}
// post-process block by inspecting contained resource records
for
// special GNS types
- if records, err = block.Records(); err != nil {
+ if records, err = m.records(block.Body.Data); err != nil {
return
}
// assemble a list of block handlers for this block: if multiple
@@ -238,10 +239,7 @@ func (m *Module) ResolveRelative(
// handle special block cases in priority order:
//--------------------------------------------------------------
- if hdlr := hdlrs.GetHandler(
- enums.GNS_TYPE_PKEY,
- enums.GNS_TYPE_EDKEY,
- ); hdlr != nil {
+ if hdlr := hdlrs.GetHandler(crypto.ZoneTypes...); hdlr != nil {
// (1) zone key record:
inst := hdlr.(*ZoneKeyHandler)
// if labels are pending, set new zone and continue
resolution;
@@ -267,7 +265,7 @@ func (m *Module) ResolveRelative(
}
// ... otherwise we need to handle delegation to DNS:
returns a
// list of found resource records in DNS (filter by
'kind')
- lbls :=
strings.Join(util.ReverseStringList(labels[1:]), ".")
+ lbls := strings.Join(util.Reverse(labels[1:]), ".")
if len(lbls) > 0 {
lbls += "."
}
@@ -361,7 +359,7 @@ func (m *Module) ResolveRelative(
// a PKEY TLD), it is also resolved with GNS. All other names are resolved
// via DNS queries.
func (m *Module) ResolveUnknown(
- ctx *service.SessionContext,
+ ctx context.Context,
name string,
labels []string,
zkey *crypto.ZoneKey,
@@ -372,7 +370,7 @@ func (m *Module) ResolveUnknown(
if strings.HasSuffix(name, ".+") {
// resolve server name relative to current zone
name = strings.TrimSuffix(name, ".+")
- for _, label := range util.ReverseStringList(labels) {
+ for _, label := range util.Reverse(labels) {
name += "." + label
}
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(
// GetZoneKey returns the zone key (or nil) from an absolute GNS path.
func (m *Module) GetZoneKey(path string) *crypto.ZoneKey {
- labels := util.ReverseStringList(strings.Split(path, "."))
+ labels := util.Reverse(strings.Split(path, "."))
if len(labels[0]) == 52 {
if data, err := util.DecodeStringToBinary(labels[0], 32); err
== nil {
if zkey, err := crypto.NewZoneKey(data); err == nil {
@@ -410,13 +408,13 @@ func (m *Module) GetZoneKey(path string) *crypto.ZoneKey {
// Lookup name in GNS.
func (m *Module) Lookup(
- ctx *service.SessionContext,
+ ctx context.Context,
zkey *crypto.ZoneKey,
label string,
- mode int) (block *message.Block, err error) {
+ mode int) (block *blocks.GNSBlock, err error) {
// create query (lookup key)
- query := NewQuery(zkey, label)
+ query := blocks.NewGNSQuery(zkey, label)
// try local lookup first
if block, err = m.LookupLocal(ctx, query); err != nil {
@@ -427,7 +425,8 @@ func (m *Module) Lookup(
if block == nil {
if mode == enums.GNS_LO_DEFAULT {
// get the block from a remote lookup
- if block, err = m.LookupRemote(ctx, query); err != nil
|| block == nil {
+ var blk blocks.Block
+ if blk, err = m.LookupRemote(ctx, query); err != nil ||
blk == nil {
if err != nil {
logger.Printf(logger.ERROR, "[gns]
remote Lookup failed: %s\n", err.Error())
block = nil
@@ -437,8 +436,13 @@ func (m *Module) Lookup(
// lookup fails completely -- no result
return
}
+ // convert to GNSBlock
+ if err = blocks.Unwrap(blk, &block); err != nil {
+ logger.Println(logger.DBG, "[gns] remote
Lookup: GNS unwrap failed")
+ return
+ }
// store RRs from remote locally.
- m.StoreLocal(ctx, block)
+ m.StoreLocal(ctx, query, block)
}
}
return
@@ -456,3 +460,22 @@ func (m *Module) newLEHORecord(name string, expires
util.AbsoluteTime) *message.
rr.Data[len(name)] = 0
return rr
}
+
+// Records returns the list of resource records from binary data.
+func (m *Module) records(buf []byte) ([]*message.ResourceRecord, error) {
+ // parse data into record set
+ rs := message.NewRecordSet()
+ if err := data.Unmarshal(rs, buf); err != nil {
+ return nil, err
+ }
+ return rs.Records, nil
+}
+
+//----------------------------------------------------------------------
+
+// RPC returns the route and handler function for a JSON-RPC request
+func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
+ return "/gns/", func(wrt http.ResponseWriter, req *http.Request) {
+ wrt.Write([]byte(`{"msg": "This is GNS" }`))
+ }
+}
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 @@
package gns
import (
+ "context"
"encoding/hex"
"fmt"
"io"
@@ -28,8 +29,8 @@ import (
"gnunet/enums"
"gnunet/message"
"gnunet/service"
+ "gnunet/service/dht/blocks"
"gnunet/service/revocation"
- "gnunet/transport"
"gnunet/util"
"github.com/bfix/gospel/data"
@@ -64,115 +65,117 @@ func NewService() service.Service {
return inst
}
-// Start the GNS service
-func (s *Service) Start(spec string) error {
- return nil
-}
-
-// Stop the GNS service
-func (s *Service) Stop() error {
- return nil
-}
-
// ServeClient processes a client channel.
-func (s *Service) ServeClient(ctx *service.SessionContext, mc
*transport.MsgChannel) {
+func (s *Service) ServeClient(ctx context.Context, id int, mc
*service.Connection) {
reqID := 0
-loop:
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithCancel(ctx)
+
for {
// receive next message from client
reqID++
- logger.Printf(logger.DBG, "[gns:%d:%d] Waiting for client
request...\n", ctx.ID, reqID)
- msg, err := mc.Receive(ctx.Signaller())
+ logger.Printf(logger.DBG, "[gns:%d:%d] Waiting for client
request...\n", id, reqID)
+ msg, err := mc.Receive(ctx)
if err != nil {
if err == io.EOF {
- logger.Printf(logger.INFO, "[gns:%d:%d] Client
channel closed.\n", ctx.ID, reqID)
- } else if err == transport.ErrChannelInterrupted {
- logger.Printf(logger.INFO, "[gns:%d:%d] Service
operation interrupted.\n", ctx.ID, reqID)
+ logger.Printf(logger.INFO, "[gns:%d:%d] Client
channel closed.\n", id, reqID)
+ } else if err == service.ErrConnectionInterrupted {
+ logger.Printf(logger.INFO, "[gns:%d:%d] Service
operation interrupted.\n", id, reqID)
} else {
- logger.Printf(logger.ERROR, "[gns:%d:%d]
Message-receive failed: %s\n", ctx.ID, reqID, err.Error())
+ logger.Printf(logger.ERROR, "[gns:%d:%d]
Message-receive failed: %s\n", id, reqID, err.Error())
}
- break loop
+ break
}
- logger.Printf(logger.INFO, "[gns:%d:%d] Received request:
%v\n", ctx.ID, reqID, msg)
-
- // perform lookup
- switch m := msg.(type) {
- case *message.LookupMsg:
-
//----------------------------------------------------------
- // GNS_LOOKUP
-
//----------------------------------------------------------
-
- // perform lookup on block (locally and remote)
- go func(id int, m *message.LookupMsg) {
- logger.Printf(logger.INFO, "[gns:%d:%d] Lookup
request received.\n", ctx.ID, id)
- resp := message.NewGNSLookupResultMsg(m.ID)
- ctx.Add()
- defer func() {
- // send response
- if resp != nil {
- if err := mc.Send(resp,
ctx.Signaller()); err != nil {
-
logger.Printf(logger.ERROR, "[gns:%d:%d] Failed to send response: %s\n",
ctx.ID, id, err.Error())
- }
- }
- // go-routine finished
- logger.Printf(logger.DBG, "[gns:%d:%d]
Lookup request finished.\n", ctx.ID, id)
- ctx.Remove()
- }()
-
- label := m.GetName()
- kind := NewRRTypeList(int(m.Type))
- recset, err := s.Resolve(ctx, label, m.Zone,
kind, int(m.Options), 0)
- if err != nil {
- logger.Printf(logger.ERROR,
"[gns:%d:%d] Failed to lookup block: %s\n", ctx.ID, id, err.Error())
- if err ==
transport.ErrChannelInterrupted {
- resp = nil
+ logger.Printf(logger.INFO, "[gns:%d:%d] Received request:
%v\n", id, reqID, msg)
+
+ // handle message
+ s.HandleMessage(context.WithValue(ctx, "label",
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
+ }
+ // close client connection
+ mc.Close()
+
+ // cancel all tasks running for this session/connection
+ logger.Printf(logger.INFO, "[gns:%d] Start closing session...\n", id)
+ cancel()
+}
+
+// Handle a single incoming message
+func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back
service.Responder) bool {
+ // assemble log label
+ label := ""
+ if v := ctx.Value("label"); v != nil {
+ label = v.(string)
+ }
+ // perform lookup
+ switch m := msg.(type) {
+ case *message.LookupMsg:
+ //----------------------------------------------------------
+ // GNS_LOOKUP
+ //----------------------------------------------------------
+
+ // perform lookup on block (locally and remote)
+ go func(m *message.LookupMsg) {
+ logger.Printf(logger.INFO, "[gns%s] Lookup request
received.\n", label)
+ resp := message.NewGNSLookupResultMsg(m.ID)
+ defer func() {
+ // send response
+ if resp != nil {
+ if err := back.Send(ctx, resp); err !=
nil {
+ logger.Printf(logger.ERROR,
"[gns%s] Failed to send response: %s\n", label, err.Error())
}
+ }
+ // go-routine finished
+ logger.Printf(logger.DBG, "[gns%s] Lookup
request finished.\n", label)
+ }()
+
+ label := m.GetName()
+ kind := NewRRTypeList(int(m.Type))
+ recset, err := s.Resolve(ctx, label, m.Zone, kind,
int(m.Options), 0)
+ if err != nil {
+ logger.Printf(logger.ERROR, "[gns%s] Failed to
lookup block: %s\n", label, err.Error())
+ if err == service.ErrConnectionInterrupted {
+ resp = nil
+ }
+ return
+ }
+ // handle records
+ if recset != nil {
+ logger.Printf(logger.DBG, "[gns%s] Received
record set with %d entries\n", label, recset.Count)
+
+ // get records from block
+ if recset.Count == 0 {
+ logger.Printf(logger.WARN, "[gns%s] No
records in block\n", label)
return
}
- // handle records
- if recset != nil {
- logger.Printf(logger.DBG, "[gns:%d:%d]
Received record set with %d entries\n", ctx.ID, id, recset.Count)
-
- // get records from block
- if recset.Count == 0 {
- logger.Printf(logger.WARN,
"[gns:%d:%d] No records in block\n", ctx.ID, id)
- return
- }
- // process records
- for i, rec := range recset.Records {
- logger.Printf(logger.DBG,
"[gns:%d:%d] Record #%d: %v\n", ctx.ID, id, i, rec)
-
- // is this the record type we
are looking for?
- if rec.Type == m.Type ||
int(m.Type) == enums.GNS_TYPE_ANY {
- // add it to the
response message
- resp.AddRecord(rec)
- }
+ // process records
+ for i, rec := range recset.Records {
+ logger.Printf(logger.DBG, "[gns%s]
Record #%d: %v\n", label, i, rec)
+
+ // is this the record type we are
looking for?
+ if rec.Type == m.Type || int(m.Type) ==
enums.GNS_TYPE_ANY {
+ // add it to the response
message
+ resp.AddRecord(rec)
}
}
- }(reqID, m)
-
- default:
-
//----------------------------------------------------------
- // UNKNOWN message type received
-
//----------------------------------------------------------
- logger.Printf(logger.ERROR, "[gns:%d:%d] Unhandled
message of type (%d)\n", ctx.ID, reqID, msg.Header().MsgType)
- break loop
- }
- }
- // close client connection
- mc.Close()
+ }
+ }(m)
- // cancel all tasks running for this session/connection
- logger.Printf(logger.INFO, "[gns:%d] Start closing session... [%d]\n",
ctx.ID, ctx.Waiting())
- ctx.Cancel()
+ default:
+ //----------------------------------------------------------
+ // UNKNOWN message type received
+ //----------------------------------------------------------
+ logger.Printf(logger.ERROR, "[gns%s] Unhandled message of type
(%d)\n", label, msg.Header().MsgType)
+ return false
+ }
+ return true
}
//======================================================================
-// Revocationrelated methods
+// Revocation-related methods
//======================================================================
// QueryKeyRevocation checks if a key has been revoked
-func (s *Service) QueryKeyRevocation(ctx *service.SessionContext, zkey
*crypto.ZoneKey) (valid bool, err error) {
+func (s *Service) QueryKeyRevocation(ctx context.Context, zkey
*crypto.ZoneKey) (valid bool, err error) {
logger.Printf(logger.DBG, "[gns] QueryKeyRev(%s)...\n",
util.EncodeBinaryToString(zkey.Bytes()))
// assemble request
@@ -180,7 +183,7 @@ func (s *Service) QueryKeyRevocation(ctx
*service.SessionContext, zkey *crypto.Z
// get response from Revocation service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Endpoint, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Service.Socket, req); err != nil {
return
}
@@ -195,7 +198,7 @@ func (s *Service) QueryKeyRevocation(ctx
*service.SessionContext, zkey *crypto.Z
}
// RevokeKey revokes a key with given revocation data
-func (s *Service) RevokeKey(ctx *service.SessionContext, rd
*revocation.RevData) (success bool, err error) {
+func (s *Service) RevokeKey(ctx context.Context, rd *revocation.RevData)
(success bool, err error) {
logger.Printf(logger.DBG, "[gns] RevokeKey(%s)...\n",
rd.ZoneKeySig.ID())
// assemble request
@@ -206,7 +209,7 @@ func (s *Service) RevokeKey(ctx *service.SessionContext, rd
*revocation.RevData)
// get response from Revocation service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Endpoint, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Revocation",
config.Cfg.Revocation.Service.Socket, req); err != nil {
return
}
@@ -225,17 +228,17 @@ func (s *Service) RevokeKey(ctx *service.SessionContext,
rd *revocation.RevData)
//======================================================================
// LookupNamecache returns a cached lookup (if available)
-func (s *Service) LookupNamecache(ctx *service.SessionContext, query *Query)
(block *message.Block, err error) {
- logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n",
hex.EncodeToString(query.Key.Bits))
+func (s *Service) LookupNamecache(ctx context.Context, query *blocks.GNSQuery)
(block *blocks.GNSBlock, err error) {
+ logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n",
hex.EncodeToString(query.Key().Bits))
// assemble Namecache request
- req := message.NewNamecacheLookupMsg(query.Key)
+ req := message.NewNamecacheLookupMsg(query.Key())
req.ID = uint32(util.NextID())
block = nil
// get response from Namecache service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Endpoint, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Service.Socket, req); err != nil {
return
}
@@ -250,7 +253,7 @@ func (s *Service) LookupNamecache(ctx
*service.SessionContext, query *Query) (bl
break
}
// check if block was found
- if len(m.EncData) == 0 || util.IsNull(m.EncData) {
+ if len(m.EncData) == 0 || util.IsAll(m.EncData, 0) {
logger.Println(logger.DBG, "[gns] block not found in
namecache")
break
}
@@ -262,21 +265,21 @@ func (s *Service) LookupNamecache(ctx
*service.SessionContext, query *Query) (bl
}
// assemble GNSBlock from message
- block = new(message.Block)
+ block = new(blocks.GNSBlock)
block.DerivedKeySig = m.DerivedKeySig
- sb := new(message.SignedBlockData)
+ sb := new(blocks.SignedGNSBlockData)
sb.Purpose = new(crypto.SignaturePurpose)
sb.Purpose.Purpose = enums.SIG_GNS_RECORD_SIGN
sb.Purpose.Size = uint32(16 + len(m.EncData))
sb.Expire = m.Expire
- sb.EncData = m.EncData
- block.Block = sb
+ sb.Data = m.EncData
+ block.Body = sb
// verify and decrypt block
- if err = block.Verify(query.Zone, query.Label); err != nil {
+ if err = query.Verify(block); err != nil {
break
}
- if err = block.Decrypt(query.Zone, query.Label); err != nil {
+ if err = query.Decrypt(block); err != nil {
break
}
default:
@@ -287,7 +290,7 @@ func (s *Service) LookupNamecache(ctx
*service.SessionContext, query *Query) (bl
}
// StoreNamecache stores a lookup in the local namecache.
-func (s *Service) StoreNamecache(ctx *service.SessionContext, block
*message.Block) (err error) {
+func (s *Service) StoreNamecache(ctx context.Context, query *blocks.GNSQuery,
block *blocks.GNSBlock) (err error) {
logger.Println(logger.DBG, "[gns] StoreNamecache()...")
// assemble Namecache request
@@ -296,7 +299,7 @@ func (s *Service) StoreNamecache(ctx
*service.SessionContext, block *message.Blo
// get response from Namecache service
var resp message.Message
- if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Endpoint, req); err != nil {
+ if resp, err = service.RequestResponse(ctx, "gns", "Namecache",
config.Cfg.Namecache.Service.Socket, req); err != nil {
return
}
@@ -327,13 +330,13 @@ func (s *Service) StoreNamecache(ctx
*service.SessionContext, block *message.Blo
//======================================================================
// LookupDHT gets a GNS block from the DHT for the given query key.
-func (s *Service) LookupDHT(ctx *service.SessionContext, query *Query) (block
*message.Block, err error) {
- logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n",
hex.EncodeToString(query.Key.Bits))
+func (s *Service) LookupDHT(ctx context.Context, query blocks.Query) (block
blocks.Block, err error) {
+ logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n",
hex.EncodeToString(query.Key().Bits))
block = nil
// client-connect to the DHT service
logger.Println(logger.DBG, "[gns] Connecting to DHT service...")
- cl, err := service.NewClient(config.Cfg.DHT.Endpoint)
+ cl, err := service.NewClient(ctx, config.Cfg.DHT.Service.Socket)
if err != nil {
return nil, err
}
@@ -360,7 +363,7 @@ func (s *Service) LookupDHT(ctx *service.SessionContext,
query *Query) (block *m
)
// send DHT GET request and wait for response
- reqGet := message.NewDHTClientGetMsg(query.Key)
+ reqGet := message.NewDHTClientGetMsg(query.Key())
reqGet.ID = uint64(util.NextID())
reqGet.ReplLevel = uint32(enums.DHT_GNS_REPLICATION_LEVEL)
reqGet.Type = uint32(enums.BLOCK_TYPE_GNS_NAMERECORD)
@@ -368,16 +371,16 @@ func (s *Service) LookupDHT(ctx *service.SessionContext,
query *Query) (block *m
if err = interact(reqGet, true); err != nil {
// check for aborted remote lookup: we need to cancel the query
- if err == transport.ErrChannelInterrupted {
+ if err == service.ErrConnectionInterrupted {
logger.Println(logger.WARN, "[gns] remote Lookup
aborted -- cleaning up.")
// send DHT GET_STOP request and terminate
- reqStop := message.NewDHTClientGetStopMsg(query.Key)
+ reqStop := message.NewDHTClientGetStopMsg(query.Key())
reqStop.ID = reqGet.ID
if err = interact(reqStop, false); err != nil {
logger.Printf(logger.ERROR, "[gns] remote
Lookup abort failed: %s\n", err.Error())
}
- return nil, transport.ErrChannelInterrupted
+ return nil, service.ErrConnectionInterrupted
}
}
@@ -407,22 +410,24 @@ func (s *Service) LookupDHT(ctx *service.SessionContext,
query *Query) (block *m
}
// get GNSBlock from message
- block = message.NewBlock()
+ qGNS := query.(*blocks.GNSQuery)
+ block = new(blocks.GNSBlock)
if err = data.Unmarshal(block, m.Data); err != nil {
logger.Printf(logger.ERROR, "[gns] can't read GNS
block: %s\n", err.Error())
break
}
+
// verify and decrypt block
- if err = block.Verify(query.Zone, query.Label); err != nil {
+ if err = qGNS.Verify(block); err != nil {
break
}
- if err = block.Decrypt(query.Zone, query.Label); err != nil {
+ if err = qGNS.Decrypt(block); err != nil {
break
}
// we got a result from DHT that was not in the namecache,
// so store it there now.
- if err = s.StoreNamecache(ctx, block); err != nil {
+ if err = s.StoreNamecache(ctx, qGNS, block.(*blocks.GNSBlock));
err != nil {
logger.Printf(logger.ERROR, "[gns] can't store block in
Namecache: %s\n", err.Error())
}
}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package service
+
+import (
+ "context"
+ "gnunet/core"
+ "net/http"
+)
+
+// Module is an interface for GNUnet service modules (workers).
+type Module interface {
+ // RPC returns the route and handler for JSON-RPC requests
+ RPC() (string, func(http.ResponseWriter, *http.Request))
+
+ // Filter returns the event filter for the module
+ Filter() *core.EventFilter
+}
+
+// EventHandler is a function prototype for event handling
+type EventHandler func(context.Context, *core.Event)
+
+// ModuleImpl is an event-handling type used by Module implementations.
+type ModuleImpl struct {
+ ch chan *core.Event // channel for core events.
+}
+
+// NewModuleImplementation returns a new base module and starts
+func NewModuleImpl() (m *ModuleImpl) {
+ return &ModuleImpl{
+ ch: make(chan *core.Event),
+ }
+}
+
+// Run event handling loop
+func (m *ModuleImpl) Run(ctx context.Context, hdlr EventHandler, filter
*core.EventFilter) (listener *core.Listener) {
+ // listener for registration
+ listener = core.NewListener(m.ch, filter)
+ // run event loop
+ go func() {
+ for {
+ select {
+ // Handle events
+ case event := <-m.ch:
+ hdlr(ctx, event)
+
+ // wait for terminate signal
+ case <-ctx.Done():
+ return
+ }
+ }
+ }()
+ return
+}
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 @@
package namecache
import (
- "gnunet/message"
+ "context"
+ "gnunet/config"
+ "gnunet/core"
"gnunet/service"
- "gnunet/service/gns"
+ "gnunet/service/dht/blocks"
)
//======================================================================
@@ -34,12 +36,29 @@ import (
// Namecache handles the transient storage of GNS blocks under the query key.
type NamecacheModule struct {
+ service.ModuleImpl
+
+ cache service.DHTStore // transient block cache
+}
+
+// NewModule creates a new module instance.
+func NewModule(ctx context.Context, c *core.Core) (m *NamecacheModule) {
+ m = &NamecacheModule{
+ ModuleImpl: *service.NewModuleImpl(),
+ }
+ m.cache, _ = service.NewDHTStore(config.Cfg.Namecache.Storage)
+ return
}
-func (nc *NamecacheModule) Get(ctx *service.SessionContext, query *gns.Query)
(*message.Block, error) {
- return nil, nil
+// Get an entry from the cache if available.
+func (m *NamecacheModule) Get(ctx context.Context, query *blocks.GNSQuery)
(block *blocks.GNSBlock, err error) {
+ var b blocks.Block
+ b, err = m.cache.Get(query)
+ err = blocks.Unwrap(b, block)
+ return
}
-func (nc *NamecacheModule) Put(ctx *service.SessionContext, block
*message.Block) error {
- return nil
+// Put entry into the cache.
+func (m *NamecacheModule) Put(ctx context.Context, query *blocks.GNSQuery,
block *blocks.GNSBlock) error {
+ return m.cache.Put(query, block)
}
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 @@
package revocation
import (
+ "context"
"gnunet/config"
+ "gnunet/core"
"gnunet/crypto"
+ "gnunet/message"
"gnunet/service"
"gnunet/util"
"net/http"
@@ -33,55 +36,71 @@ import (
// "GNUnet Revocation" implementation
//======================================================================
+// The minimum average difficulty acceptable for a set of revocation PoWs
+const MinAvgDifficulty = 23
+
// Module handles the revocation-related calls to other modules.
type Module struct {
- bloomf *data.BloomFilter // bloomfilter for fast revocation check
- kvs util.KeyValueStore // storage for known revocations
+ service.ModuleImpl
+
+ bloomf *data.BloomFilter // bloomfilter for fast revocation check
+ kvs service.KVStore // storage for known revocations
}
-// Init a revocation module
-func (m *Module) Init() error {
- // Initialize access to revocation data storage
- var err error
- if m.kvs, err = util.OpenKVStore(config.Cfg.Revocation.Storage); err !=
nil {
- return err
- }
- // traverse the storage and build bloomfilter for all keys
- m.bloomf = data.NewBloomFilter(1000000, 1e-8)
- keys, err := m.kvs.List()
- if err != nil {
- return err
+// NewModule returns an initialized revocation module
+func NewModule(ctx context.Context, c *core.Core) (m *Module) {
+ // create and init instance
+ m = &Module{
+ ModuleImpl: *service.NewModuleImpl(),
}
- for _, key := range keys {
- buf, err := util.DecodeStringToBinary(key, 32)
- if err != nil {
- return err
+ init := func() (err error) {
+ // Initialize access to revocation data storage
+ if m.kvs, err =
service.NewKVStore(config.Cfg.Revocation.Storage); err != nil {
+ return
}
- m.bloomf.Add(buf)
+ // traverse the storage and build bloomfilter for all keys
+ m.bloomf = data.NewBloomFilter(1000000, 1e-8)
+ var keys []string
+ if keys, err = m.kvs.List(); err != nil {
+ return
+ }
+ for _, key := range keys {
+ m.bloomf.Add([]byte(key))
+ }
+ return
}
- return nil
-}
-
-// NewModule returns an initialized revocation module
-func NewModule() *Module {
- m := new(Module)
- if err := m.Init(); err != nil {
+ if err := init(); err != nil {
logger.Printf(logger.ERROR, "[revocation] Failed to initialize
module: %s\n", err.Error())
return nil
}
+ // register as listener for core events
+ listener := m.Run(ctx, m.event, m.Filter())
+ c.Register("gns", listener)
return m
}
-// RPC returns the route and handler function for a JSON-RPC request
-func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
- return "/revocation/", func(wrt http.ResponseWriter, req *http.Request)
{
- wrt.Write([]byte(`{"msg": "This is REVOCATION" }`))
- }
+//----------------------------------------------------------------------
+
+// Filter returns the event filter for the service
+func (m *Module) Filter() *core.EventFilter {
+ f := core.NewEventFilter()
+ f.AddMsgType(message.REVOCATION_QUERY)
+ f.AddMsgType(message.REVOCATION_QUERY_RESPONSE)
+ f.AddMsgType(message.REVOCATION_REVOKE)
+ f.AddMsgType(message.REVOCATION_REVOKE_RESPONSE)
+ return f
}
+// Event handler
+func (m *Module) event(ctx context.Context, ev *core.Event) {
+
+}
+
+//----------------------------------------------------------------------
+
// Query return true if the pkey is valid (not revoked) and false
// if the pkey has been revoked.
-func (m *Module) Query(ctx *service.SessionContext, zkey *crypto.ZoneKey)
(valid bool, err error) {
+func (m *Module) Query(ctx context.Context, zkey *crypto.ZoneKey) (valid bool,
err error) {
// fast check first: is the key in the bloomfilter?
data := zkey.Bytes()
if !m.bloomf.Contains(data) {
@@ -100,9 +119,9 @@ func (m *Module) Query(ctx *service.SessionContext, zkey
*crypto.ZoneKey) (valid
}
// Revoke a key with given revocation data
-func (m *Module) Revoke(ctx *service.SessionContext, rd *RevData) (success
bool, err error) {
+func (m *Module) Revoke(ctx context.Context, rd *RevData) (success bool, err
error) {
// verify the revocation data
- rc := rd.Verify(true)
+ diff, rc := rd.Verify(true)
switch {
case rc == -1:
logger.Println(logger.WARN, "[revocation] Revoke:
Missing/invalid signature")
@@ -113,10 +132,12 @@ func (m *Module) Revoke(ctx *service.SessionContext, rd
*RevData) (success bool,
case rc == -3:
logger.Println(logger.WARN, "[revocation] Revoke: Wrong PoW
sequence order")
return false, nil
- case rc < 25:
+ }
+ if diff < float64(MinAvgDifficulty) {
logger.Println(logger.WARN, "[revocation] Revoke: Difficulty to
small")
return false, nil
}
+
// store the revocation data
// (1) add it to the bloomfilter
m.bloomf.Add(rd.ZoneKeySig.KeyData)
@@ -129,3 +150,12 @@ func (m *Module) Revoke(ctx *service.SessionContext, rd
*RevData) (success bool,
err = m.kvs.Put(rd.ZoneKeySig.ID(), value)
return true, err
}
+
+//----------------------------------------------------------------------
+
+// RPC returns the route and handler function for a JSON-RPC request
+func (m *Module) RPC() (string, func(http.ResponseWriter, *http.Request)) {
+ return "/revocation/", func(wrt http.ResponseWriter, req *http.Request)
{
+ wrt.Write([]byte(`{"msg": "This is REVOCATION" }`))
+ }
+}
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 (
// Proof-of-Work data
//----------------------------------------------------------------------
+const (
+ // MinDifficulty for revocations -> expires in ~1year
+ MinDifficulty = 23
+)
+
// PoWData is the proof-of-work data
type PoWData struct {
PoW uint64 `order:"big"` // start with this PoW value
@@ -143,6 +148,11 @@ func NewRevDataFromMsg(m *message.RevocationRevokeMsg)
*RevData {
return rd
}
+// Size of a serialized RevData object.
+func (rd *RevData) Size() int {
+ return 16 + 8*len(rd.PoWs) + int(rd.ZoneKeySig.SigSize())
+}
+
// Sign the revocation data
func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err error) {
sigBlock := &SignedRevData{
@@ -160,12 +170,10 @@ func (rd *RevData) Sign(skey *crypto.ZonePrivate) (err
error) {
return
}
-// Verify a revocation object: returns the (smallest) number of leading
-// zero-bits in the PoWs of this revocation; a number > 0, but smaller
-// than the minimum (25) indicates invalid PoWs; a value of -1 indicates
-// a failed signature; -2 indicates an expired revocation and -3 for a
-// "out-of-order" PoW sequence.
-func (rd *RevData) Verify(withSig bool) int {
+// Verify a revocation object and return the average difficulty of the PoWs
+// in this revocation and a verification status (-1=failed signature, -2=
+// expired revocation, -3="out-of-order" PoW sequence).
+func (rd *RevData) Verify(withSig bool) (zbits float64, rc int) {
// (1) check signature
if withSig {
@@ -179,39 +187,36 @@ func (rd *RevData) Verify(withSig bool) int {
}
sigData, err := data.Marshal(sigBlock)
if err != nil {
- return -1
+ return 0., -1
}
valid, err := rd.ZoneKeySig.Verify(sigData)
if err != nil || !valid {
- return -1
+ return 0., -1
}
}
// (2) check PoWs
- var (
- zbits float64 = 0
- last uint64 = 0
- )
+ var last uint64 = 0
for _, pow := range rd.PoWs {
// check sequence order
if pow <= last {
- return -3
+ return 0., -3
}
last = pow
// compute number of leading zero-bits
work := NewPoWData(pow, rd.Timestamp, &rd.ZoneKeySig.ZoneKey)
zbits += float64(512 - work.Compute().BitLen())
}
- zbits /= 32.0
+ zbits /= float64(len(rd.PoWs))
// (3) check expiration
- if zbits > 24.0 {
- ttl := time.Duration(int((zbits-24)*365*24)) * time.Hour
+ if zbits >= 23.0 {
+ ttl := time.Duration(int((zbits-22)*365*24*1.1)) * time.Hour
if util.AbsoluteTimeNow().Add(ttl).Expired() {
- return -2
+ return zbits, -2
}
}
- return int(zbits)
+ return zbits, 0
}
//----------------------------------------------------------------------
@@ -240,6 +245,11 @@ func NewRevDataCalc(zkey *crypto.ZoneKey) *RevDataCalc {
return rd
}
+// Size of a serialized RevData object.
+func (rdc *RevDataCalc) Size() int {
+ return rdc.RevData.Size() + 2*len(rdc.Bits) + 1
+}
+
// Average number of leading zero-bits in current list
func (rdc *RevDataCalc) Average() float64 {
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) {
var (
D =
"6fea32c05af58bfa979553d188605fd57d8bf9cc263b78d5f7478c07b998ed70"
ZKEY =
"000100002ca223e879ecc4bbdeb5da17319281d63b2e3b6955f1c3775c804a98d5f8ddaa"
- DIFF = 7
PROOF = "" +
"0005d66da3598127" +
"0000395d1827c000" +
@@ -142,8 +141,11 @@ func TestRevocationRFC(t *testing.T) {
}
// verify revocation data object
- rc := revData.Verify(true)
- if rc != DIFF {
+ diff, rc := revData.Verify(true)
+ if testing.Verbose() {
+ t.Logf("Average difficulty of PoWs = %f\n", diff)
+ }
+ if rc != 0 {
t.Fatalf("REV_Verify (pkey): %d\n", rc)
}
}
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 @@
package revocation
import (
+ "context"
+ "fmt"
"io"
"gnunet/message"
"gnunet/service"
- "gnunet/transport"
"github.com/bfix/gospel/logger"
)
@@ -44,114 +45,113 @@ func NewService() service.Service {
return inst
}
-// Start the Revocation service
-func (s *Service) Start(spec string) error {
- return nil
-}
-
-// Stop the Revocation service
-func (s *Service) Stop() error {
- return nil
-}
-
// ServeClient processes a client channel.
-func (s *Service) ServeClient(ctx *service.SessionContext, mc
*transport.MsgChannel) {
+func (s *Service) ServeClient(ctx context.Context, id int, mc
*service.Connection) {
reqID := 0
-loop:
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithCancel(ctx)
+
for {
// receive next message from client
reqID++
- logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for
client request...\n", ctx.ID, reqID)
- msg, err := mc.Receive(ctx.Signaller())
+ logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for
client request...\n", id, reqID)
+ msg, err := mc.Receive(ctx)
if err != nil {
if err == io.EOF {
- logger.Printf(logger.INFO, "[revocation:%d:%d]
Client channel closed.\n", ctx.ID, reqID)
- } else if err == transport.ErrChannelInterrupted {
- logger.Printf(logger.INFO, "[revocation:%d:%d]
Service operation interrupted.\n", ctx.ID, reqID)
+ logger.Printf(logger.INFO, "[revocation:%d:%d]
Client channel closed.\n", id, reqID)
+ } else if err == service.ErrConnectionInterrupted {
+ logger.Printf(logger.INFO, "[revocation:%d:%d]
Service operation interrupted.\n", id, reqID)
} else {
- logger.Printf(logger.ERROR, "[revocation:%d:%d]
Message-receive failed: %s\n", ctx.ID, reqID, err.Error())
+ logger.Printf(logger.ERROR, "[revocation:%d:%d]
Message-receive failed: %s\n", id, reqID, err.Error())
}
- break loop
- }
- logger.Printf(logger.INFO, "[revocation:%d:%d] Received
request: %v\n", ctx.ID, reqID, msg)
-
- // handle request
- switch m := msg.(type) {
- case *message.RevocationQueryMsg:
-
//----------------------------------------------------------
- // REVOCATION_QUERY
-
//----------------------------------------------------------
- go func(id int, m *message.RevocationQueryMsg) {
- logger.Printf(logger.INFO, "[revocation:%d:%d]
Query request received.\n", ctx.ID, id)
- var resp *message.RevocationQueryResponseMsg
- ctx.Add()
- defer func() {
- // send response
- if resp != nil {
- if err := mc.Send(resp,
ctx.Signaller()); err != nil {
-
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n",
ctx.ID, id, err.Error())
- }
- }
- // go-routine finished
- logger.Printf(logger.DBG,
"[revocation:%d:%d] Query request finished.\n", ctx.ID, id)
- ctx.Remove()
- }()
-
- valid, err := s.Query(ctx, m.Zone)
- if err != nil {
- logger.Printf(logger.ERROR,
"[revocation:%d:%d] Failed to query revocation status: %s\n", ctx.ID, id,
err.Error())
- if err ==
transport.ErrChannelInterrupted {
- resp = nil
- }
- return
- }
- resp =
message.NewRevocationQueryResponseMsg(valid)
- }(reqID, m)
-
- case *message.RevocationRevokeMsg:
-
//----------------------------------------------------------
- // REVOCATION_REVOKE
-
//----------------------------------------------------------
- go func(id int, m *message.RevocationRevokeMsg) {
- logger.Printf(logger.INFO, "[revocation:%d:%d]
Revoke request received.\n", ctx.ID, id)
- var resp *message.RevocationRevokeResponseMsg
- ctx.Add()
- defer func() {
- // send response
- if resp != nil {
- if err := mc.Send(resp,
ctx.Signaller()); err != nil {
-
logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n",
ctx.ID, id, err.Error())
- }
- }
- // go-routine finished
- logger.Printf(logger.DBG,
"[revocation:%d:%d] Revoke request finished.\n", ctx.ID, id)
- ctx.Remove()
- }()
-
- rd := NewRevDataFromMsg(m)
- valid, err := s.Revoke(ctx, rd)
- if err != nil {
- logger.Printf(logger.ERROR,
"[revocation:%d:%d] Failed to revoke key: %s\n", ctx.ID, id, err.Error())
- if err ==
transport.ErrChannelInterrupted {
- resp = nil
- }
- return
- }
- resp =
message.NewRevocationRevokeResponseMsg(valid)
- }(reqID, m)
-
- default:
-
//----------------------------------------------------------
- // UNKNOWN message type received
-
//----------------------------------------------------------
- logger.Printf(logger.ERROR, "[revocation:%d:%d]
Unhandled message of type (%d)\n", ctx.ID, reqID, msg.Header().MsgType)
- break loop
+ break
}
+ logger.Printf(logger.INFO, "[revocation:%d:%d] Received
request: %v\n", id, reqID, msg)
+
+ // handle message
+ s.HandleMessage(context.WithValue(ctx, "label",
fmt.Sprintf(":%d:%d", id, reqID)), msg, mc)
}
// close client connection
mc.Close()
// cancel all tasks running for this session/connection
- logger.Printf(logger.INFO, "[revocation:%d] Start closing session...
[%d]\n", ctx.ID, ctx.Waiting())
- ctx.Cancel()
+ logger.Printf(logger.INFO, "[revocation:%d] Start closing
session...\n", id)
+ cancel()
+}
+
+// Handle a single incoming message
+func (s *Service) HandleMessage(ctx context.Context, msg message.Message, back
service.Responder) bool {
+ // assemble log label
+ label := ""
+ if v := ctx.Value("label"); v != nil {
+ label = v.(string)
+ }
+ switch m := msg.(type) {
+ case *message.RevocationQueryMsg:
+ //----------------------------------------------------------
+ // REVOCATION_QUERY
+ //----------------------------------------------------------
+ go func(m *message.RevocationQueryMsg) {
+ logger.Printf(logger.INFO, "[revocation%s] Query
request received.\n", label)
+ var resp *message.RevocationQueryResponseMsg
+ defer func() {
+ // send response
+ if resp != nil {
+ if err := back.Send(ctx, resp); err !=
nil {
+ logger.Printf(logger.ERROR,
"[revocation%s] Failed to send response: %s\n", label, err.Error())
+ }
+ }
+ // go-routine finished
+ logger.Printf(logger.DBG, "[revocation%s] Query
request finished.\n", label)
+ }()
+
+ valid, err := s.Query(ctx, m.Zone)
+ if err != nil {
+ logger.Printf(logger.ERROR, "[revocation%s]
Failed to query revocation status: %s\n", label, err.Error())
+ if err == service.ErrConnectionInterrupted {
+ resp = nil
+ }
+ return
+ }
+ resp = message.NewRevocationQueryResponseMsg(valid)
+ }(m)
+
+ case *message.RevocationRevokeMsg:
+ //----------------------------------------------------------
+ // REVOCATION_REVOKE
+ //----------------------------------------------------------
+ go func(m *message.RevocationRevokeMsg) {
+ logger.Printf(logger.INFO, "[revocation%s] Revoke
request received.\n", label)
+ var resp *message.RevocationRevokeResponseMsg
+ defer func() {
+ // send response
+ if resp != nil {
+ if err := back.Send(ctx, resp); err !=
nil {
+ logger.Printf(logger.ERROR,
"[revocation%s] Failed to send response: %s\n", label, err.Error())
+ }
+ }
+ // go-routine finished
+ logger.Printf(logger.DBG, "[revocation%s]
Revoke request finished.\n", label)
+ }()
+
+ rd := NewRevDataFromMsg(m)
+ valid, err := s.Revoke(ctx, rd)
+ if err != nil {
+ logger.Printf(logger.ERROR, "[revocation%s]
Failed to revoke key: %s\n", label, err.Error())
+ if err == service.ErrConnectionInterrupted {
+ resp = nil
+ }
+ return
+ }
+ resp = message.NewRevocationRevokeResponseMsg(valid)
+ }(m)
+
+ default:
+ //----------------------------------------------------------
+ // UNKNOWN message type received
+ //----------------------------------------------------------
+ logger.Printf(logger.ERROR, "[revocation%s] Unhandled message
of type (%d)\n", label, msg.Header().MsgType)
+ return false
+ }
+ return true
}
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 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -19,145 +19,133 @@
package service
import (
+ "context"
+ "errors"
"fmt"
- "net/http"
- "sync"
-
- "gnunet/transport"
+ "gnunet/message"
+ "gnunet/util"
"github.com/bfix/gospel/logger"
)
-// Module is an interface for GNUnet service modules (workers).
-type Module interface {
- // RPC returns the route and handler for JSON-RPC requests
- RPC() (string, func(http.ResponseWriter, *http.Request))
+//----------------------------------------------------------------------
+
+// Responder is a back-channel for messages generated during
+// message processing. The Connection type is a responder
+// and used as such in ServeClient().
+type Responder interface {
+ // Handle outgoing message
+ Send(ctx context.Context, msg message.Message) error
+}
+
+// TransportResponder is used as a responder in message handling for
+// messages received from Transport.
+type TransportResponder struct {
+ Peer *util.PeerID
+ SendFcn func(context.Context, *util.PeerID, message.Message) error
}
-// Service is an interface for GNUnet services. Every service has one channel
-// end-point it listens to for incoming channel requests (network-based
-// channels established by service clients). The end-point is specified in
-// Channel semantics in the specification string.
+// Send a message back to caller.
+func (r *TransportResponder) Send(ctx context.Context, msg message.Message)
error {
+ if r.SendFcn == nil {
+ return errors.New("no send function defined")
+ }
+ return r.SendFcn(ctx, r.Peer, msg)
+}
+
+//----------------------------------------------------------------------
+
+// Service is an interface for GNUnet services
type Service interface {
Module
- // Start a service on the given endpoint
- Start(spec string) error
- // Serve a client session
- ServeClient(ctx *SessionContext, ch *transport.MsgChannel)
- // Stop the service
- Stop() error
+
+ // Serve a client session: A service has a socket it listens to for
+ // incoming connections (sessions) which are used for message exchange
+ // with local GNUnet services or clients.
+ ServeClient(ctx context.Context, id int, mc *Connection)
+
+ // Handle a single incoming message (either locally from a socket
+ // connection or from Transport). Response messages can be send
+ // via a Responder. Returns true if message was processed.
+ HandleMessage(ctx context.Context, msg message.Message, resp Responder)
bool
}
-// Impl is an implementation of generic service functionality.
-type Impl struct {
- impl Service // Specific service implementation
- hdlr chan transport.Channel // Channel from listener
- ctrl chan bool // Control channel
- drop chan int // Channel to drop a session from
pending list
- srvc transport.ChannelServer // multi-user service
- wg *sync.WaitGroup // wait group for go routine
synchronization
- name string // service name
- running bool // service currently running?
- pending map[int]*SessionContext // list of pending sessions
+// SocketHandler handles incoming connections on the local service socket.
+// It delegates calls to ServeClient() and HandleMessage() methods
+// to a custom service 'srv'.
+type SocketHandler struct {
+ srv Service // Specific service implementation
+ hdlr chan *Connection // handler for incoming connections
+ cmgr *ConnectionManager // manager for client connections
+ name string // service name
}
-// NewServiceImpl instantiates a new ServiceImpl object.
-func NewServiceImpl(name string, srv Service) *Impl {
- return &Impl{
- impl: srv,
- hdlr: make(chan transport.Channel),
- ctrl: make(chan bool),
- drop: make(chan int),
- srvc: nil,
- wg: new(sync.WaitGroup),
- name: name,
- running: false,
- pending: make(map[int]*SessionContext),
+// NewSocketHandler instantiates a new socket handler.
+func NewSocketHandler(name string, srv Service) *SocketHandler {
+ return &SocketHandler{
+ srv: srv,
+ hdlr: make(chan *Connection),
+ cmgr: nil,
+ name: name,
}
}
-// Start a service
-func (si *Impl) Start(spec string) (err error) {
+// Start the socket handler by listening on a Unix domain socket specified
+// by its path and additional parameters. Incoming connections from clients
+// are dispatched to 'hdlr'. Stopped socket handlers can be re-started.
+func (h *SocketHandler) Start(ctx context.Context, path string, params
map[string]string) (err error) {
// check if we are already running
- if si.running {
- logger.Printf(logger.ERROR, "Service '%s' already running.\n",
si.name)
+ if h.cmgr != nil {
+ logger.Printf(logger.ERROR, "Service '%s' already running.\n",
h.name)
return fmt.Errorf("service already running")
}
-
- // start channel server
- logger.Printf(logger.INFO, "[%s] Service starting.\n", si.name)
- if si.srvc, err = transport.NewChannelServer(spec, si.hdlr); err != nil
{
+ // start connection manager
+ logger.Printf(logger.INFO, "[%s] Service starting.\n", h.name)
+ if h.cmgr, err = NewConnectionManager(ctx, path, params, h.hdlr); err
!= nil {
return
}
- si.running = true
- // handle clients
- si.wg.Add(1)
+ // handle client connections
go func() {
- defer si.wg.Done()
loop:
- for si.running {
+ for {
select {
- // handle incoming connections
- case in := <-si.hdlr:
- if in == nil {
- logger.Printf(logger.INFO, "[%s]
Listener terminated.\n", si.name)
- break loop
- }
- switch ch := in.(type) {
- case transport.Channel:
- // run a new session with context
- ctx := NewSessionContext()
- sessID := ctx.ID
- si.pending[sessID] = ctx
- logger.Printf(logger.INFO, "[%s]
Session '%d' started.\n", si.name, sessID)
-
- go func() {
- // serve client on the message
channel
- si.impl.ServeClient(ctx,
transport.NewMsgChannel(ch))
- // session is done now.
- logger.Printf(logger.INFO,
"[%s] Session with client '%d' ended.\n", si.name, sessID)
- si.drop <- sessID
- }()
- }
-
- // handle session removal
- case sessID := <-si.drop:
- delete(si.pending, sessID)
-
- // handle cancelation signal on listener.
- case <-si.ctrl:
+ // handle incoming connection
+ case conn := <-h.hdlr:
+ // run a new session with context
+ id := util.NextID()
+ logger.Printf(logger.INFO, "[%s] Session '%d'
started.\n", h.name, id)
+
+ go func() {
+ // serve client on the message channel
+ h.srv.ServeClient(ctx, id, conn)
+ // session is done now.
+ logger.Printf(logger.INFO, "[%s]
Session with client '%d' ended.\n", h.name, id)
+ }()
+
+ // handle termination
+ case <-ctx.Done():
+ logger.Printf(logger.INFO, "[%s] Listener
terminated.\n", h.name)
break loop
}
}
- // terminate pending sessions
- for _, ctx := range si.pending {
- logger.Printf(logger.DBG, "[%s] Session '%d'
closing...\n", si.name, ctx.ID)
- ctx.Cancel()
- }
-
// close-down service
- logger.Printf(logger.INFO, "[%s] Service closing.\n", si.name)
- si.srvc.Close()
- si.running = false
+ logger.Printf(logger.INFO, "[%s] Service closing.\n", h.name)
+ h.cmgr.Close()
}()
-
- return si.impl.Start(spec)
+ return nil
}
-// Stop a service
-func (si *Impl) Stop() error {
- if !si.running {
- logger.Printf(logger.WARN, "Service '%s' not running.\n",
si.name)
+// Stop socket handler.
+func (h *SocketHandler) Stop() error {
+ if h.cmgr == nil {
+ logger.Printf(logger.WARN, "Service '%s' not running.\n",
h.name)
return fmt.Errorf("service not running")
}
- si.running = false
- si.ctrl <- true
- logger.Printf(logger.INFO, "[%s] Service terminating.\n", si.name)
-
- err := si.impl.Stop()
- si.wg.Wait()
- return err
+ logger.Printf(logger.INFO, "[%s] Service terminating.\n", h.name)
+ h.cmgr.Close()
+ h.cmgr = nil
+ return nil
}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package service
+
+import (
+ "context"
+ "database/sql"
+ "encoding/binary"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "gnunet/crypto"
+ "gnunet/service/dht/blocks"
+ "gnunet/util"
+ "io/ioutil"
+ "os"
+ "strconv"
+ "strings"
+
+ redis "github.com/go-redis/redis/v8"
+)
+
+// Error messages related to the key/value-store implementations
+var (
+ ErrStoreInvalidSpec = fmt.Errorf("Invalid Store specification")
+ ErrStoreUnknown = fmt.Errorf("Unknown Store type")
+ ErrStoreNotAvailable = fmt.Errorf("Store not available")
+)
+
+//------------------------------------------------------------
+// Generic storage interface. Can be used for persistent or
+// transient (caching) storage of key/value data.
+// One set of methods (Get/Put) work on DHT queries and blocks,
+// the other set (GetS, PutS) work on key/value strings.
+// Each custom implementation can decide which sets to support.
+//------------------------------------------------------------
+
+// Store is a key/value storage where the type of the key is either
+// a SHA512 hash value or a string and the value is either a DHT
+// block or a string.
+type Store[K, V any] interface {
+ // Put block into storage under given key
+ Put(key K, val V) error
+
+ // Get block with given key from storage
+ Get(key K) (V, error)
+
+ // List all store queries
+ List() ([]K, error)
+}
+
+//------------------------------------------------------------
+// Types for custom store requirements
+//------------------------------------------------------------
+
+// DHTStore for DHT queries and blocks
+type DHTStore Store[blocks.Query, blocks.Block]
+
+// KVStore for key/value string pairs
+type KVStore Store[string, string]
+
+//------------------------------------------------------------
+// NewDHTStore creates a new storage handler with given spec
+// for use with DHT queries and blocks
+func NewDHTStore(spec string) (DHTStore, error) {
+ specs := strings.SplitN(spec, ":", 2)
+ if len(specs) < 2 {
+ return nil, ErrStoreInvalidSpec
+ }
+ switch specs[0] {
+ //------------------------------------------------------------------
+ // File-base storage
+ //------------------------------------------------------------------
+ case "file_store":
+ return NewFileStore(specs[1])
+ case "file_cache":
+ if len(specs) < 3 {
+ return nil, ErrStoreInvalidSpec
+ }
+ return NewFileCache(specs[1], specs[2])
+ }
+ return nil, ErrStoreUnknown
+}
+
+//------------------------------------------------------------
+// NewKVStore creates a new storage handler with given spec
+// for use with key/value string pairs.
+func NewKVStore(spec string) (KVStore, error) {
+ specs := strings.SplitN(spec, ":", 2)
+ if len(specs) < 2 {
+ return nil, ErrStoreInvalidSpec
+ }
+ switch specs[0] {
+ //--------------------------------------------------------------
+ // Redis service
+ //--------------------------------------------------------------
+ case "redis":
+ if len(specs) < 4 {
+ return nil, ErrStoreInvalidSpec
+ }
+ return NewRedisStore(specs[1], specs[2], specs[3])
+
+ //--------------------------------------------------------------
+ // SQL database service
+ //--------------------------------------------------------------
+ case "sql":
+ if len(specs) < 4 {
+ return nil, ErrStoreInvalidSpec
+ }
+ return NewSQLStore(specs[1])
+ }
+ return nil, errors.New("unknown storage mechanism")
+}
+
+//------------------------------------------------------------
+// Filesystem-based storage
+//------------------------------------------------------------
+
+// FileStore implements a filesystem-based storage mechanism for
+// DHT queries and blocks.
+type FileStore struct {
+ path string // storage path
+ cached []*crypto.HashCode // list of cached entries (key)
+ wrPos int // write position in cyclic list
+}
+
+// NewFileStore instantiates a new file storage.
+func NewFileStore(path string) (DHTStore, error) {
+ // create file store
+ return &FileStore{
+ path: path,
+ }, nil
+}
+
+// NewFileCache instantiates a new file-based cache.
+func NewFileCache(path, param string) (DHTStore, error) {
+ // remove old cache content
+ os.RemoveAll(path)
+
+ // get number of cache entries
+ num, err := strconv.ParseUint(param, 10, 32)
+ if err != nil {
+ return nil, err
+ }
+ // create file store
+ return &FileStore{
+ path: path,
+ cached: make([]*crypto.HashCode, num),
+ wrPos: 0,
+ }, nil
+}
+
+// Put block into storage under given key
+func (s *FileStore) Put(query blocks.Query, block blocks.Block) (err error) {
+ // get query parameters for entry
+ var (
+ btype uint16 // block type
+ expire util.AbsoluteTime // block expiration
+ )
+ query.Get("blkType", &btype)
+ query.Get("expire", &expire)
+
+ // are we in caching mode?
+ if s.cached != nil {
+ // release previous block if defined
+ if key := s.cached[s.wrPos]; key != nil {
+ // get path and filename from key
+ path, fname := s.expandPath(key)
+ if err = os.Remove(path + "/" + fname); err != nil {
+ return
+ }
+ // free slot
+ s.cached[s.wrPos] = nil
+ }
+ }
+ // get path and filename from key
+ path, fname := s.expandPath(query.Key())
+ // make sure the path exists
+ if err = os.MkdirAll(path, 0755); err != nil {
+ return
+ }
+ // write to file for storage
+ var fp *os.File
+ if fp, err = os.Create(path + "/" + fname); err == nil {
+ defer fp.Close()
+ // write block data
+ if err = binary.Write(fp, binary.BigEndian, btype); err == nil {
+ if err = binary.Write(fp, binary.BigEndian, expire);
err == nil {
+ _, err = fp.Write(block.Data())
+ }
+ }
+ }
+ // update cache list
+ if s.cached != nil {
+ s.cached[s.wrPos] = query.Key()
+ s.wrPos = (s.wrPos + 1) % len(s.cached)
+ }
+ return
+}
+
+// Get block with given key from storage
+func (s *FileStore) Get(query blocks.Query) (block blocks.Block, err error) {
+ // get requested block type
+ var (
+ btype uint16 = blocks.DHT_BLOCK_ANY
+ blkt uint16 // actual block type
+ expire util.AbsoluteTime // expiration date
+ data []byte // block data
+ )
+ query.Get("blkType", &btype)
+
+ // get path and filename from key
+ path, fname := s.expandPath(query.Key())
+ // read file content (block data)
+ var file *os.File
+ if file, err = os.Open(path + "/" + fname); err != nil {
+ return
+ }
+ // read block data
+ if err = binary.Read(file, binary.BigEndian, &blkt); err == nil {
+ if btype != blocks.DHT_BLOCK_ANY && btype != blkt {
+ // block types not matching
+ return
+ }
+ if err = binary.Read(file, binary.BigEndian, &expire); err ==
nil {
+ if data, err = ioutil.ReadAll(file); err == nil {
+ block = blocks.NewGenericBlock(data)
+ }
+ }
+ }
+ return
+}
+
+// Get a list of all stored block keys (generic query).
+func (s *FileStore) List() ([]blocks.Query, error) {
+ return make([]blocks.Query, 0), nil
+}
+
+// expandPath returns the full path to the file for given key.
+func (s *FileStore) expandPath(key *crypto.HashCode) (string, string) {
+ h := hex.EncodeToString(key.Bits)
+ return fmt.Sprintf("%s/%s/%s", s.path, h[:2], h[2:4]), h[4:]
+}
+
+//------------------------------------------------------------
+// Redis: only use for caching purposes on key/value strings
+//------------------------------------------------------------
+
+// RedisStore uses a (local) Redis server for key/value storage
+type RedisStore struct {
+ client *redis.Client // client connection
+ db int // index to database
+}
+
+// NewRedisStore creates a Redis service client instance.
+func NewRedisStore(addr, passwd, db string) (s KVStore, err error) {
+ kvs := new(RedisStore)
+ if kvs.db, err = strconv.Atoi(db); err != nil {
+ err = ErrStoreInvalidSpec
+ return
+ }
+ kvs.client = redis.NewClient(&redis.Options{
+ Addr: addr,
+ Password: passwd,
+ DB: kvs.db,
+ })
+ if kvs.client == nil {
+ err = ErrStoreNotAvailable
+ }
+ s = kvs
+ return
+}
+
+// Put block into storage under given key
+func (s *RedisStore) Put(key string, value string) (err error) {
+ return s.client.Set(context.TODO(), key, value, 0).Err()
+}
+
+// Get block with given key from storage
+func (s *RedisStore) Get(key string) (value string, err error) {
+ return s.client.Get(context.TODO(), key).Result()
+}
+
+// List all keys in store
+func (s *RedisStore) List() (keys []string, err error) {
+ var (
+ crs uint64
+ segm []string
+ ctx = context.TODO()
+ )
+ keys = make([]string, 0)
+ for {
+ segm, crs, err = s.client.Scan(ctx, crs, "*", 10).Result()
+ if err != nil {
+ return
+ }
+ if crs == 0 {
+ break
+ }
+ keys = append(keys, segm...)
+ }
+ return
+}
+
+//------------------------------------------------------------
+// SQL-based key-value-store
+//------------------------------------------------------------
+
+// SQLStore for generic SQL database handling
+type SQLStore struct {
+ db *util.DbConn
+}
+
+// NewSQLStore creates a new SQL-based key/value store.
+func NewSQLStore(spec string) (s KVStore, err error) {
+ kvs := new(SQLStore)
+
+ // connect to SQL database
+ kvs.db, err = util.DbPool.Connect(spec)
+ if err != nil {
+ return nil, err
+ }
+ // get number of key/value pairs (as a check for existing table)
+ row := kvs.db.QueryRow("select count(*) from store")
+ var num int
+ if row.Scan(&num) != nil {
+ return nil, ErrStoreNotAvailable
+ }
+ return kvs, nil
+
+}
+
+// Put a key/value pair into the store
+func (s *SQLStore) Put(key string, value string) error {
+ _, err := s.db.Exec("insert into store(key,value) values(?,?)", key,
value)
+ return err
+}
+
+// Get a value for a given key from store
+func (s *SQLStore) Get(key string) (value string, err error) {
+ row := s.db.QueryRow("select value from store where key=?", key)
+ err = row.Scan(&value)
+ return
+}
+
+// List all keys in store
+func (s *SQLStore) List() (keys []string, err error) {
+ var (
+ rows *sql.Rows
+ key string
+ )
+ keys = make([]string, 0)
+ rows, err = s.db.Query("select key from store")
+ if err == nil {
+ for rows.Next() {
+ if err = rows.Scan(&key); err != nil {
+ break
+ }
+ keys = append(keys, key)
+ }
+ }
+ return
+}
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 @@
+#!/bin/bash
+
+go 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 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package transport
-
-import (
- "encoding/hex"
- "errors"
- "fmt"
- "path"
- "strings"
-
- "gnunet/message"
- "gnunet/util"
-
- "github.com/bfix/gospel/concurrent"
- "github.com/bfix/gospel/data"
- "github.com/bfix/gospel/logger"
-)
-
-// Error codes
-var (
- ErrChannelNotImplemented = fmt.Errorf("protocol not implemented")
- ErrChannelNotOpened = fmt.Errorf("channel not opened")
- ErrChannelInterrupted = fmt.Errorf("channel interrupted")
-)
-
-////////////////////////////////////////////////////////////////////////
-// CHANNEL
-
-// Channel is an abstraction for exchanging arbitrary data over various
-// transport protocols and mechanisms. They are created by clients via
-// 'NewChannel()' or by services run via 'NewChannelServer()'.
-// A string specifies the end-point of the channel:
-// "unix+/tmp/test.sock" -- for UDS channels
-// "tcp+1.2.3.4:5" -- for TCP channels
-// "udp+1.2.3.4:5" -- for UDP channels
-type Channel interface {
- Open(spec string) error // open channel (for
read/write)
- Close() error // close open channel
- IsOpen() bool // check if channel
is open
- Read([]byte, *concurrent.Signaller) (int, error) // read from channel
- Write([]byte, *concurrent.Signaller) (int, error) // write to channel
-}
-
-// ChannelFactory instantiates specific Channel imülementations.
-type ChannelFactory func() Channel
-
-// Known channel implementations.
-var channelImpl = map[string]ChannelFactory{
- "unix": NewSocketChannel,
- "tcp": NewTCPChannel,
- "udp": NewUDPChannel,
-}
-
-// NewChannel creates a new channel to the specified endpoint.
-// Called by a client to connect to a service.
-func NewChannel(spec string) (Channel, error) {
- parts := strings.Split(spec, "+")
- if fac, ok := channelImpl[parts[0]]; ok {
- inst := fac()
- err := inst.Open(spec)
- return inst, err
- }
- return nil, ErrChannelNotImplemented
-}
-
-////////////////////////////////////////////////////////////////////////
-// CHANNEL SERVER
-
-// ChannelServer creates a listener for the specified endpoint.
-// The specification string has the same format as for Channel with slightly
-// different semantics (for TCP, and ICMP the address specifies is a mask
-// for client addresses accepted for a channel request).
-type ChannelServer interface {
- Open(spec string, hdlr chan<- Channel) error
- Close() error
-}
-
-// ChannelServerFactory instantiates specific ChannelServer imülementations.
-type ChannelServerFactory func() ChannelServer
-
-// Known channel server implementations.
-var channelServerImpl = map[string]ChannelServerFactory{
- "unix": NewSocketChannelServer,
- "tcp": NewTCPChannelServer,
- "udp": NewUDPChannelServer,
-}
-
-// NewChannelServer creates a new channel server instance
-func NewChannelServer(spec string, hdlr chan<- Channel) (cs ChannelServer, err
error) {
- parts := strings.Split(spec, "+")
-
- if fac, ok := channelServerImpl[parts[0]]; ok {
- // check if the basedir for the spec exists...
- if err = util.EnforceDirExists(path.Dir(parts[1])); err != nil {
- return
- }
- // instantiate server implementation
- cs = fac()
- // create the domain socket and listen to it.
- err = cs.Open(spec, hdlr)
- return
- }
- return nil, ErrChannelNotImplemented
-}
-
-////////////////////////////////////////////////////////////////////////
-// MESSAGE CHANNEL
-
-// MsgChannel s a wrapper around a generic channel for GNUnet message exchange.
-type MsgChannel struct {
- ch Channel
- buf []byte
-}
-
-// NewMsgChannel wraps a plain Channel for GNUnet message exchange.
-func NewMsgChannel(ch Channel) *MsgChannel {
- return &MsgChannel{
- ch: ch,
- buf: make([]byte, 65536),
- }
-}
-
-// Close a MsgChannel by closing the wrapped plain Channel.
-func (c *MsgChannel) Close() error {
- return c.ch.Close()
-}
-
-// Send a GNUnet message over a channel.
-func (c *MsgChannel) Send(msg message.Message, sig *concurrent.Signaller)
error {
- // convert message to binary data
- data, err := data.Marshal(msg)
- if err != nil {
- return err
- }
- logger.Printf(logger.DBG, "==> %v\n", msg)
- logger.Printf(logger.DBG, " [%s]\n", hex.EncodeToString(data))
-
- // check message header size and packet size
- mh, err := message.GetMsgHeader(data)
- if err != nil {
- return err
- }
- if len(data) != int(mh.MsgSize) {
- return errors.New("send: message size mismatch")
- }
-
- // send packet
- n, err := c.ch.Write(data, sig)
- if err != nil {
- return err
- }
- if n != len(data) {
- return errors.New("incomplete send")
- }
- return nil
-}
-
-// Receive GNUnet messages over a plain Channel.
-func (c *MsgChannel) Receive(sig *concurrent.Signaller) (message.Message,
error) {
- // get bytes from channel
- get := func(pos, count int) error {
- n, err := c.ch.Read(c.buf[pos:pos+count], sig)
- if err != nil {
- return err
- }
- if n != count {
- return errors.New("not enough bytes on network")
- }
- return nil
- }
-
- if err := get(0, 4); err != nil {
- return nil, err
- }
- mh, err := message.GetMsgHeader(c.buf[:4])
- if err != nil {
- return nil, err
- }
-
- if err := get(4, int(mh.MsgSize)-4); err != nil {
- return nil, err
- }
- msg, err := message.NewEmptyMessage(mh.MsgType)
- if err != nil {
- return nil, err
- }
- if msg == nil {
- return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
- }
- if err = data.Unmarshal(msg, c.buf[:mh.MsgSize]); err != nil {
- return nil, err
- }
- logger.Printf(logger.DBG, "<== %v\n", msg)
- logger.Printf(logger.DBG, " [%s]\n",
hex.EncodeToString(c.buf[:mh.MsgSize]))
- return msg, nil
-}
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 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package transport
-
-import (
- "net"
- "os"
- "strconv"
- "strings"
-
- "github.com/bfix/gospel/concurrent"
- "github.com/bfix/gospel/logger"
-)
-
-// ChannelResult for read/write operations on channels.
-type ChannelResult struct {
- count int // number of bytes read/written
- err error // error (or nil)
-}
-
-// NewChannelResult instanciates a new object with given attributes.
-func NewChannelResult(n int, err error) *ChannelResult {
- return &ChannelResult{
- count: n,
- err: err,
- }
-}
-
-// Values returns the attributes of a result instance (for passing up the
-// call stack).
-func (cr *ChannelResult) Values() (int, error) {
- return cr.count, cr.err
-}
-
-////////////////////////////////////////////////////////////////////////
-// Generic network-based Channel
-
-// NetworkChannel represents a network-based channel
-type NetworkChannel struct {
- network string // network protocol identifier ("tcp", "unix", ...)
- conn net.Conn // associated connection
-}
-
-// NewNetworkChannel creates a new channel for a given network protocol.
-// The channel is in pending state and need to be opened before use.
-func NewNetworkChannel(netw string) Channel {
- return &NetworkChannel{
- network: netw,
- conn: nil,
- }
-}
-
-// Open a network channel based on specification:
-// The specification is a string separated into parts by the '+' delimiter
-// (e.g. "unix+/tmp/gnunet-service-gns-go.sock+perm=0770"). The network
-// identifier (first part) must match the network specification of the
-// underlaying NetworkChannel instance.
-func (c *NetworkChannel) Open(spec string) (err error) {
- parts := strings.Split(spec, "+")
- // check for correct protocol
- if parts[0] != c.network {
- return ErrChannelNotImplemented
- }
- // open connection
- c.conn, err = net.Dial(c.network, parts[1])
- return
-}
-
-// Close a network channel
-func (c *NetworkChannel) Close() error {
- if c.conn != nil {
- rc := c.conn.Close()
- c.conn = nil
- return rc
- }
- return ErrChannelNotOpened
-}
-
-// IsOpen returns true if the channel is opened
-func (c *NetworkChannel) IsOpen() bool {
- return c.conn != nil
-}
-
-// Read bytes from a network channel into buffer: Returns the number of read
-// bytes and an error code. Only works on open channels ;)
-// The read can be aborted by sending 'true' on the cmd interface; the
-// channel is closed after such interruption.
-func (c *NetworkChannel) Read(buf []byte, sig *concurrent.Signaller) (int,
error) {
- // check if the channel is open
- if c.conn == nil {
- return 0, ErrChannelNotOpened
- }
- // perform operation in go-routine
- result := make(chan *ChannelResult)
- go func() {
- result <- NewChannelResult(c.conn.Read(buf))
- }()
-
- listener := sig.Listen()
- defer sig.Drop(listener)
- for {
- select {
- // handle terminate command
- case x := <-listener:
- switch val := x.(type) {
- case bool:
- if val {
- return 0, ErrChannelInterrupted
- }
- }
- // handle result of read operation
- case res := <-result:
- return res.Values()
- }
- }
-}
-
-// Write buffer to a network channel: Returns the number of written bytes and
-// an error code. The write operation can be aborted by sending 'true' on the
-// command channel; the network channel is closed after such interrupt.
-func (c *NetworkChannel) Write(buf []byte, sig *concurrent.Signaller) (int,
error) {
- // check if we have an open channel to write to.
- if c.conn == nil {
- return 0, ErrChannelNotOpened
- }
- // perform operation in go-routine
- result := make(chan *ChannelResult)
- go func() {
- result <- NewChannelResult(c.conn.Write(buf))
- }()
-
- listener := sig.Listen()
- defer sig.Drop(listener)
- for {
- select {
- // handle terminate command
- case x := <-listener:
- switch val := x.(type) {
- case bool:
- if val {
- return 0, ErrChannelInterrupted
- }
- }
- // handle result of read operation
- case res := <-result:
- return res.Values()
- }
- }
-}
-
-////////////////////////////////////////////////////////////////////////
-// Generic network-based ChannelServer
-
-// NetworkChannelServer represents a network-based channel server
-type NetworkChannelServer struct {
- network string // network protocol to listen on
- listener net.Listener // reference to listener object
-}
-
-// NewNetworkChannelServer creates a new network-based channel server
-func NewNetworkChannelServer(netw string) ChannelServer {
- return &NetworkChannelServer{
- network: netw,
- listener: nil,
- }
-}
-
-// Open a network channel server (= start running it) based on the given
-// specification. For every client connection to the server, the associated
-// network channel for the connection is send via the hdlr channel.
-func (s *NetworkChannelServer) Open(spec string, hdlr chan<- Channel) (err
error) {
- parts := strings.Split(spec, "+")
- // check for correct protocol
- if parts[0] != s.network {
- return ErrChannelNotImplemented
- }
- // create listener
- if s.listener, err = net.Listen(s.network, parts[1]); err != nil {
- return
- }
- // handle additional parameters ('key[=value]')
- for _, param := range parts[2:] {
- frag := strings.Split(param, "=")
- switch frag[0] {
- case "perm": // set permissions on 'unix'
- if s.network == "unix" {
- if perm, err := strconv.ParseInt(frag[1], 8,
32); err == nil {
- if err := os.Chmod(parts[1],
os.FileMode(perm)); err != nil {
- logger.Printf(
- logger.ERROR,
- "NetworkChannelServer:
Failed to set permissions: %s\n",
- err.Error())
-
- }
- } else {
- logger.Printf(
- logger.ERROR,
- "NetworkChannelServer: Invalid
permissions '%s'\n",
- frag[1])
- }
- }
- }
- }
- // run go routine to handle channel requests from clients
- go func() {
- for {
- conn, err := s.listener.Accept()
- if err != nil {
- // signal failure and terminate
- hdlr <- nil
- break
- }
- // send channel to handler
- hdlr <- &NetworkChannel{
- network: s.network,
- conn: conn,
- }
- }
- if s.listener != nil {
- s.listener.Close()
- }
- }()
-
- return nil
-}
-
-// Close a network channel server (= stop the server)
-func (s *NetworkChannelServer) Close() error {
- if s.listener != nil {
- err := s.listener.Close()
- s.listener = nil
- return err
- }
- return nil
-}
-
-////////////////////////////////////////////////////////////////////////
-// helper functions to instantiate network channels and servers for
-// common network protocols
-
-// NewSocketChannel implements a Unix Domain Socket connection
-func NewSocketChannel() Channel {
- return NewNetworkChannel("unix")
-}
-
-// NewTCPChannel implements a: TCP connection
-func NewTCPChannel() Channel {
- return NewNetworkChannel("tcp")
-}
-
-// NewUDPChannel implements an UDP connection
-func NewUDPChannel() Channel {
- return NewNetworkChannel("udp")
-}
-
-// NewSocketChannelServer implements an Unix Domain Socket listener
-func NewSocketChannelServer() ChannelServer {
- return NewNetworkChannelServer("unix")
-}
-
-// NewTCPChannelServer implements a TCP listener
-func NewTCPChannelServer() ChannelServer {
- return NewNetworkChannelServer("tcp")
-}
-
-// NewUDPChannelServer implements an UDP listener
-func NewUDPChannelServer() ChannelServer {
- return NewNetworkChannelServer("udp")
-}
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 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package transport
-
-import (
- "bytes"
- "fmt"
- "testing"
- "time"
-
- "github.com/bfix/gospel/concurrent"
-)
-
-// TODO: These test cases fail from time to time for no obvious reason.
-// This needs to be investigated.
-
-const (
- SockAddr = "/tmp/gnunet-go-test.sock"
- TCPAddrClient = "gnunet.org:80"
- TCPAddrServer = "127.0.0.1:9876"
-)
-
-type TestChannelServer struct {
- hdlr chan Channel
- srvc ChannelServer
- running bool
-}
-
-func NewTestChannelServer() *TestChannelServer {
- return &TestChannelServer{
- hdlr: make(chan Channel),
- srvc: nil,
- running: false,
- }
-}
-
-func (s *TestChannelServer) handle(ch Channel, sig *concurrent.Signaller) {
- buf := make([]byte, 4096)
- for {
- n, err := ch.Read(buf, sig)
- if err != nil {
- break
- }
- _, err = ch.Write(buf[:n], sig)
- if err != nil {
- break
- }
- }
- ch.Close()
-}
-
-func (s *TestChannelServer) Start(spec string) (err error) {
- // check if we are already running
- if s.running {
- return fmt.Errorf("Server already running")
- }
-
- // start channel server
- if s.srvc, err = NewChannelServer(spec, s.hdlr); err != nil {
- return
- }
- s.running = true
-
- // handle clients
- sig := concurrent.NewSignaller()
- go func() {
- for s.running {
- in := <-s.hdlr
- if in == nil {
- break
- }
- switch x := in.(type) {
- case Channel:
- go s.handle(x, sig)
- }
- }
- s.srvc.Close()
- s.running = false
- }()
- return nil
-}
-
-func (s *TestChannelServer) Stop() {
- s.running = false
-}
-
-func TestChannelServerTCPSingle(t *testing.T) {
- time.Sleep(time.Second)
- s := NewTestChannelServer()
- err := s.Start("tcp+" + TCPAddrServer)
- defer s.Stop()
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestChannelServerTCPTwice(t *testing.T) {
- time.Sleep(time.Second)
- s1 := NewTestChannelServer()
- err := s1.Start("tcp+" + TCPAddrServer)
- defer s1.Stop()
- if err != nil {
- t.Fatal(err)
- }
- time.Sleep(time.Second)
- s2 := NewTestChannelServer()
- err = s2.Start("tcp+" + TCPAddrServer)
- defer s2.Stop()
- if err == nil {
- t.Fatal("SocketServer started twice!!")
- }
-}
-
-func TestChannelClientTCP(t *testing.T) {
- time.Sleep(time.Second)
- ch, err := NewChannel("tcp+" + TCPAddrClient)
- if err != nil {
- t.Fatal(err)
- }
- defer ch.Close()
-
- sig := concurrent.NewSignaller()
- msg := []byte("GET /\n\n")
- n, err := ch.Write(msg, sig)
- if err != nil {
- t.Fatal(err)
- }
- if n != len(msg) {
- t.Fatal("Send size mismatch")
- }
- buf := make([]byte, 4096)
- n = 0
- start := time.Now().Unix()
- for n == 0 && (time.Now().Unix()-start) < 3 {
- if n, err = ch.Read(buf, sig); err != nil {
- t.Fatal(err)
- }
- }
- t.Logf("'%s' [%d]\n", string(buf[:n]), n)
-}
-
-func TestChannelClientServerTCP(t *testing.T) {
- time.Sleep(time.Second)
- s := NewTestChannelServer()
- err := s.Start("tcp+" + TCPAddrServer)
- defer s.Stop()
- if err != nil {
- t.Fatal(err)
- }
-
- ch, err := NewChannel("tcp+" + TCPAddrServer)
- if err != nil {
- t.Fatal(err)
- }
- sig := concurrent.NewSignaller()
- msg := []byte("GET /\n\n")
- n, err := ch.Write(msg, sig)
- if err != nil {
- t.Fatal(err)
- }
- if n != len(msg) {
- t.Fatal("Send size mismatch")
- }
- buf := make([]byte, 4096)
- n = 0
- start := time.Now().Unix()
- for n == 0 && (time.Now().Unix()-start) < 3 {
- if n, err = ch.Read(buf, sig); err != nil {
- t.Fatal(err)
- }
- }
- if err = ch.Close(); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(buf[:n], msg) {
- t.Fatal("message send/receive mismatch")
- }
-}
-
-func TestChannelClientServerSock(t *testing.T) {
- time.Sleep(time.Second)
- s := NewTestChannelServer()
- if err := s.Start("unix+" + SockAddr); err != nil {
- t.Fatal(err)
- }
-
- ch, err := NewChannel("unix+" + SockAddr)
- if err != nil {
- t.Fatal(err)
- }
- sig := concurrent.NewSignaller()
- msg := []byte("This is just a test -- please ignore...")
- n, err := ch.Write(msg, sig)
- if err != nil {
- t.Fatal(err)
- }
- if n != len(msg) {
- t.Fatal("Send size mismatch")
- }
- buf := make([]byte, 4096)
- n = 0
- start := time.Now().Unix()
- for n == 0 && (time.Now().Unix()-start) < 3 {
- if n, err = ch.Read(buf, sig); err != nil {
- t.Fatal(err)
- }
- }
- if err = ch.Close(); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(buf[:n], msg) {
- t.Fatal("message send/receive mismatch")
- }
-
- s.Stop()
-}
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 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -19,59 +19,97 @@
package transport
import (
- "gnunet/core"
+ "context"
+ "errors"
"gnunet/message"
+ "net"
+)
- "github.com/bfix/gospel/concurrent"
+// Error codes
+var (
+ ErrConnectionNotOpened = errors.New("connection not opened")
+ ErrConnectionInterrupted = errors.New("connection interrupted")
)
-// Connection for communicating peers
+//----------------------------------------------------------------------
+
+// Connection is a net.Conn for GNUnet message exchange (send/receive)
type Connection struct {
- from, to *core.Peer
- ch *MsgChannel
- bandwidth uint32
- state int
- shared []byte
+ conn net.Conn // associated connection
+ buf []byte // read/write buffer
}
-// NewConnection instanciates a new connection between peers communicating
-// over a message channel (Connections are authenticated and secured).
-func NewConnection(ch *MsgChannel, from, to *core.Peer) *Connection {
+// NewConnection creates a new connection from an existing net.Conn
+// This is usually used by clients to connect to a service.
+func NewConnection(ctx context.Context, conn net.Conn) *Connection {
return &Connection{
- from: from,
- to: to,
- state: 1,
- ch: ch,
+ conn: conn,
+ buf: make([]byte, 65536),
}
}
-// SharedSecret computes the shared secret the two endpoints of a connection.
-func (c *Connection) SharedSecret(secret []byte) {
- c.shared = make([]byte, len(secret))
- copy(c.shared, secret)
+// Close connection
+func (s *Connection) Close() error {
+ if s.conn != nil {
+ rc := s.conn.Close()
+ s.conn = nil
+ return rc
+ }
+ return ErrConnectionNotOpened
}
-// GetState returns the current state of the connection.
-func (c *Connection) GetState() int {
- return c.state
+// Send a GNUnet message over connection
+func (s *Connection) Send(ctx context.Context, msg message.Message) error {
+ return WriteMessage(ctx, s.conn, msg)
}
-// SetBandwidth to control transfer rates on the connection
-func (c *Connection) SetBandwidth(bw uint32) {
- c.bandwidth = bw
+// Receive GNUnet messages from socket.
+func (s *Connection) Receive(ctx context.Context) (message.Message, error) {
+ return ReadMessage(ctx, s.conn, s.buf)
}
-// Close connection between two peers.
-func (c *Connection) Close() error {
- return c.ch.Close()
+//----------------------------------------------------------------------
+
+// ConnectionManager handles client connections on a net.Listener
+type ConnectionManager struct {
+ listener net.Listener // reference to listener object
}
-// Send a message on the connection
-func (c *Connection) Send(msg message.Message, sig *concurrent.Signaller)
error {
- return c.ch.Send(msg, sig)
+// NewConnectionManager creates a new net.Listener connection manager.
+// Incoming connections from clients are dispatched to a handler channel.
+func NewConnectionManager(ctx context.Context, listener net.Listener, hdlr
chan *Connection) (cs *ConnectionManager, err error) {
+ // instantiate connection manager
+ cs = &ConnectionManager{
+ listener: listener,
+ }
+ // run watch dog for termination
+ go func() {
+ <-ctx.Done()
+ cs.listener.Close()
+ }()
+ // run go routine to handle channel requests from clients
+ go func() {
+ for {
+ conn, err := cs.listener.Accept()
+ if err != nil {
+ return
+ }
+ // handle connection
+ c := &Connection{
+ conn: conn,
+ buf: make([]byte, 65536),
+ }
+ hdlr <- c
+ }
+ }()
+ return cs, nil
}
-// Receive a message on the connection
-func (c *Connection) Receive(sig *concurrent.Signaller) (message.Message,
error) {
- return c.ch.Receive(sig)
+// Close a connection manager (= stop the server)
+func (s *ConnectionManager) Close() (err error) {
+ if s.listener != nil {
+ err = s.listener.Close()
+ s.listener = nil
+ }
+ return
}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package transport
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "gnunet/message"
+ "gnunet/util"
+ "net"
+)
+
+var (
+ ErrEndpNotAvailable = errors.New("no endpoint for address
available")
+ ErrEndpProtocolMismatch = errors.New("transport protocol mismatch")
+)
+
+// Endpoint represents a local endpoint that can send and receive messages.
+// Implementations need to manage the relations between peer IDs and
+// remote endpoints for TCP and UDP traffic.
+type Endpoint interface {
+ // Run the endpoint and send received messages to channel
+ Run(context.Context, chan *TransportMessage) error
+
+ // Send message on endpoint
+ Send(context.Context, net.Addr, *TransportMessage) error
+
+ // Address returns the listening address for the endpoint
+ Address() net.Addr
+
+ // CanSendTo returns true if the endpoint can sent to address
+ CanSendTo(net.Addr) bool
+
+ // Return endpoint identifier
+ ID() int
+}
+
+//----------------------------------------------------------------------
+
+// NewEndpoint returns a suitable endpoint for the address.
+func NewEndpoint(addr net.Addr) (ep Endpoint, err error) {
+ switch epMode(addr.Network()) {
+ case "packet":
+ ep, err = newPacketEndpoint(addr)
+ case "stream":
+ ep, err = newStreamEndpoint(addr)
+ default:
+ err = ErrEndpNotAvailable
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Packet-oriented endpoint
+//----------------------------------------------------------------------
+
+// PacketEndpoint for packet-oriented network protocols
+type PaketEndpoint struct {
+ id int // endpoint identifier
+ addr net.Addr // endpoint address
+ conn net.PacketConn // packet connection
+ buf []byte // buffer for read/write operations
+}
+
+// Run packet endpoint: send incoming messages to the handler.
+func (ep *PaketEndpoint) Run(ctx context.Context, hdlr chan *TransportMessage)
(err error) {
+ // create listener
+ var lc net.ListenConfig
+ if ep.conn, err = lc.ListenPacket(ctx, ep.addr.Network(),
ep.addr.String()); err != nil {
+ return
+ }
+
+ // run watch dog for termination
+ go func() {
+ <-ctx.Done()
+ ep.conn.Close()
+ }()
+ // run go routine to handle messages from clients
+ go func() {
+ for {
+ // read next message from packet
+ n, _, err := ep.conn.ReadFrom(ep.buf)
+ if err != nil {
+ break
+ }
+ rdr := bytes.NewBuffer(util.Clone(ep.buf[:n]))
+ msg, err := ReadMessageDirect(rdr, ep.buf)
+ if err != nil {
+ break
+ }
+ // check for transport message
+ if msg.Header().MsgType == message.DUMMY {
+ // set transient attributes
+ tm := msg.(*TransportMessage)
+ tm.endp = ep.id
+ tm.conn = 0
+ // send to handler
+ go func() {
+ hdlr <- tm
+ }()
+ }
+ }
+ // connection ended.
+ ep.conn.Close()
+ }()
+ return
+}
+
+// Send message to address from endpoint
+func (ep *PaketEndpoint) Send(ctx context.Context, addr net.Addr, msg
*TransportMessage) (err error) {
+ var a *net.UDPAddr
+ a, err = net.ResolveUDPAddr(addr.Network(), addr.String())
+ var buf []byte
+ if buf, err = msg.Bytes(); err != nil {
+ return
+ }
+ _, err = ep.conn.WriteTo(buf, a)
+ return
+}
+
+// Address returms the
+func (ep *PaketEndpoint) Address() net.Addr {
+ if ep.conn != nil {
+ return ep.conn.LocalAddr()
+ }
+ return ep.addr
+}
+
+// CanSendTo returns true if the endpoint can sent to address
+func (ep *PaketEndpoint) CanSendTo(addr net.Addr) bool {
+ return epMode(addr.Network()) == "packet"
+}
+
+// ID returns the endpoint identifier
+func (ep *PaketEndpoint) ID() int {
+ return ep.id
+}
+
+func newPacketEndpoint(addr net.Addr) (ep *PaketEndpoint, err error) {
+ // check for matching protocol
+ if epMode(addr.Network()) != "packet" {
+ err = ErrEndpProtocolMismatch
+ return
+ }
+ // create endpoint
+ ep = &PaketEndpoint{
+ id: util.NextID(),
+ addr: addr,
+ buf: make([]byte, 65536),
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Stream-oriented endpoint
+//----------------------------------------------------------------------
+
+// StreamEndpoint for stream-oriented network protocols
+type StreamEndpoint struct {
+ id int // endpoint identifier
+ addr net.Addr // listening address
+ listener net.Listener // listener instance
+ conns *util.Map[int, net.Conn] // active connections
+ buf []byte // read/write buffer
+}
+
+// Run packet endpoint: send incoming messages to the handler.
+func (ep *StreamEndpoint) Run(ctx context.Context, hdlr chan
*TransportMessage) (err error) {
+ // create listener
+ var lc net.ListenConfig
+ if ep.listener, err = lc.Listen(ctx, ep.addr.Network(),
ep.addr.String()); err != nil {
+ return
+ }
+ // run watch dog for termination
+ go func() {
+ <-ctx.Done()
+ ep.listener.Close()
+ }()
+ // run go routine to handle messages from clients
+ go func() {
+ for {
+ // get next client connection
+ conn, err := ep.listener.Accept()
+ if err != nil {
+ return
+ }
+ session := util.NextID()
+ ep.conns.Put(session, conn)
+ go func() {
+ for {
+ // read next message from connection
+ msg, err := ReadMessage(ctx, conn,
ep.buf)
+ if err != nil {
+ break
+ }
+ // check for transport message
+ if msg.Header().MsgType ==
message.DUMMY {
+ // set transient attributes
+ tm := msg.(*TransportMessage)
+ tm.endp = ep.id
+ tm.conn = session
+ // send to handler
+ go func() {
+ hdlr <- tm
+ }()
+ }
+ }
+ // connection ended.
+ conn.Close()
+ ep.conns.Delete(session)
+ }()
+ }
+ }()
+ return
+}
+
+// Send message to address from endpoint
+func (ep *StreamEndpoint) Send(ctx context.Context, addr net.Addr, msg
*TransportMessage) error {
+ return nil
+}
+
+// Address returns the actual listening endpoint address
+func (ep *StreamEndpoint) Address() net.Addr {
+ if ep.listener != nil {
+ return ep.listener.Addr()
+ }
+ return ep.addr
+}
+
+// CanSendTo returns true if the endpoint can sent to address
+func (ep *StreamEndpoint) CanSendTo(addr net.Addr) bool {
+ return epMode(addr.Network()) == "stream"
+}
+
+// ID returns the endpoint identifier
+func (ep *StreamEndpoint) ID() int {
+ return ep.id
+}
+
+func newStreamEndpoint(addr net.Addr) (ep *StreamEndpoint, err error) {
+ // check for matching protocol
+ if epMode(addr.Network()) != "stream" {
+ err = ErrEndpProtocolMismatch
+ return
+ }
+ // create endpoint
+ ep = &StreamEndpoint{
+ id: util.NextID(),
+ addr: addr,
+ conns: util.NewMap[int, net.Conn](),
+ buf: make([]byte, 65536),
+ }
+ return
+}
+
+// epMode returns the endpoint mode (packet or stream) for a given network
+func epMode(netw string) string {
+ switch netw {
+ case "udp", "udp4", "udp6", "r5n+ip+udp":
+ return "packet"
+ case "tcp", "unix":
+ return "stream"
+ }
+ return ""
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2019-2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package transport
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "gnunet/message"
+ "io"
+
+ "github.com/bfix/gospel/data"
+)
+
+// WriteMessageDirect writes directly to io.Writer
+func WriteMessageDirect(wrt io.Writer, msg message.Message) error {
+ dwc := &directWriteCloser{wrt}
+ return WriteMessage(context.Background(), dwc, msg)
+}
+
+// WriteMessage to io.WriteCloser
+func WriteMessage(ctx context.Context, wrt io.WriteCloser, msg
message.Message) (err error) {
+ // convert message to binary data
+ var buf []byte
+ if buf, err = data.Marshal(msg); err != nil {
+ return err
+ }
+ // check message header size and packet size
+ mh, err := message.GetMsgHeader(buf)
+ if err != nil {
+ return err
+ }
+ if len(buf) != int(mh.MsgSize) {
+ return errors.New("WriteMessage: message size mismatch")
+ }
+ // watch dog for write operation
+ go func() {
+ for {
+ select {
+ case <-ctx.Done():
+ wrt.Close()
+ }
+ }
+ }()
+ // perform write operation
+ var n int
+ if n, err = wrt.Write(buf); err != nil {
+ return
+ }
+ if n != len(buf) {
+ err = fmt.Errorf("WriteMessage incomplete (%d of %d)", n,
len(buf))
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+
+// ReadMessageDirect reads directly from io.Reader
+func ReadMessageDirect(rdr io.Reader, buf []byte) (msg message.Message, err
error) {
+ drc := &directReadCloser{
+ rdr: rdr,
+ }
+ return ReadMessage(context.Background(), drc, buf)
+}
+
+// ReadMessage from io.ReadCloser
+func ReadMessage(ctx context.Context, rdr io.ReadCloser, buf []byte) (msg
message.Message, err error) {
+ // watch dog for write operation
+ go func() {
+ for {
+ select {
+ case <-ctx.Done():
+ rdr.Close()
+ }
+ }
+ }()
+ // get bytes from reader
+ if buf == nil {
+ buf = make([]byte, 65536)
+ }
+ get := func(pos, count int) error {
+ n, err := rdr.Read(buf[pos : pos+count])
+ if err == nil && n != count {
+ err = fmt.Errorf("not enough bytes on reader (%d of
%d)", n, count)
+ }
+ return err
+ }
+ // read header first
+ if err := get(0, 4); err != nil {
+ return nil, err
+ }
+ var mh *message.Header
+ if mh, err = message.GetMsgHeader(buf[:4]); err != nil {
+ return nil, err
+ }
+ // get rest of message
+ if err = get(4, int(mh.MsgSize)-4); err != nil {
+ return nil, err
+ }
+ // handle transport message case
+ if mh.MsgType == message.DUMMY {
+ msg = NewTransportMessage(nil, nil)
+ } else if msg, err = message.NewEmptyMessage(mh.MsgType); err != nil {
+ return nil, err
+ }
+ if msg == nil {
+ return nil, fmt.Errorf("message{%d} is nil", mh.MsgType)
+ }
+ if err = data.Unmarshal(msg, buf[:mh.MsgSize]); err != nil {
+ return nil, err
+ }
+ return msg, nil
+}
+
+//----------------------------------------------------------------------
+// helper for wrapped ReadCloser/WriteCloser (close is nop)
+//----------------------------------------------------------------------
+
+type directReadCloser struct {
+ rdr io.Reader
+}
+
+func (drc *directReadCloser) Read(buf []byte) (int, error) {
+ return drc.rdr.Read(buf)
+}
+
+func (drc *directReadCloser) Close() error {
+ return nil
+}
+
+type directWriteCloser struct {
+ wrt io.Writer
+}
+
+func (dwc *directWriteCloser) Write(buf []byte) (int, error) {
+ return dwc.wrt.Write(buf)
+}
+
+func (dwc *directWriteCloser) Close() error {
+ return nil
+}
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 @@
+// This file is part of gnunet-go, a GNUnet-implementation in Golang.
+// Copyright (C) 2022 Bernd Fix >Y<
+//
+// gnunet-go is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// gnunet-go is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// SPDX-License-Identifier: AGPL3.0-or-later
+
+package transport
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "gnunet/message"
+ "gnunet/util"
+ "net"
+)
+
+// Trnsport layer error codes
+var (
+ ErrTransNoEndpoint = errors.New("no matching endpoint found")
+)
+
+//======================================================================
+// Network-oriented transport implementation
+//======================================================================
+
+// TransportMessage is the unit processed by the transport mechanism.
+// Peer refers to the remote endpoint (sender/receiver) and
+// Msg is the exchanged GNUnet message. The packet itself satisfies the
+// message.Message interface.
+type TransportMessage struct {
+ Hdr *message.Header `` // message header
+ Peer *util.PeerID `` // remote peer
+ Payload []byte `size:"*"` // GNUnet message
+
+ // package-local attributes (transient)
+ msg message.Message
+ endp int // id of endpoint (incoming message)
+ conn int // id of connection (optional, incoming message)
+}
+
+func (msg *TransportMessage) Header() *message.Header {
+ return msg.Hdr
+}
+
+func (msg *TransportMessage) Message() (m message.Message, err error) {
+ if m = msg.msg; m == nil {
+ rdr := bytes.NewBuffer(msg.Payload)
+ m, err = ReadMessageDirect(rdr, nil)
+ }
+ return
+}
+
+// Bytes returns the binary representation of a transport message
+func (msg *TransportMessage) Bytes() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ err := WriteMessageDirect(buf, msg)
+ return buf.Bytes(), err
+}
+
+// String returns the message in human-readable form
+func (msg *TransportMessage) String() string {
+ return "TransportMessage{...}"
+}
+
+// NewTransportMessage creates a message suitable for transfer
+func NewTransportMessage(peer *util.PeerID, payload []byte) (tm
*TransportMessage) {
+ if peer == nil {
+ peer = util.NewPeerID(nil)
+ }
+ msize := 0
+ if payload != nil {
+ msize = len(payload)
+ }
+ tm = &TransportMessage{
+ Hdr: &message.Header{
+ MsgSize: uint16(36 + msize),
+ MsgType: message.DUMMY,
+ },
+ Peer: peer,
+ Payload: payload,
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+
+// Transport enables network-oriented (like IP, UDP, TCP or UDS)
+// message exchange on multiple endpoints.
+type Transport struct {
+ incoming chan *TransportMessage // messages as received from the
network
+ endpoints map[int]Endpoint // list of available endpoints
+}
+
+// NewTransport creates and runs a new transport layer implementation.
+func NewTransport(ctx context.Context, ch chan *TransportMessage) (t
*Transport) {
+ // create transport instance
+ return &Transport{
+ incoming: ch,
+ endpoints: make(map[int]Endpoint),
+ }
+}
+
+// Send a message over suitable endpoint
+func (t *Transport) Send(ctx context.Context, addr net.Addr, msg
*TransportMessage) (err error) {
+ for _, ep := range t.endpoints {
+ if ep.CanSendTo(addr) {
+ err = ep.Send(ctx, addr, msg)
+ break
+ }
+ }
+ return
+}
+
+//----------------------------------------------------------------------
+// Endpoint handling
+//----------------------------------------------------------------------
+
+// AddEndpoint instantiates and run a new endpoint handler for the
+// given address (must map to a network interface).
+func (t *Transport) AddEndpoint(ctx context.Context, addr net.Addr) (a
net.Addr, err error) {
+ // register endpoint
+ var ep Endpoint
+ if ep, err = NewEndpoint(addr); err != nil {
+ return
+ }
+ t.endpoints[ep.ID()] = ep
+ ep.Run(ctx, t.incoming)
+ return ep.Address(), nil
+}
+
+// Endpoints returns a list of listening addresses managed by transport.
+func (t *Transport) Endpoints() (list []net.Addr) {
+ list = make([]net.Addr, 0)
+ for _, ep := range t.endpoints {
+ list = append(list, ep.Address())
+ }
+ return
+}
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 @@
package util
import (
- "encoding/hex"
+ "bytes"
"fmt"
"net"
+ "strings"
)
// Address specifies how a peer is reachable on the network.
type Address struct {
- Transport string // transport protocol
- Options uint32 `order:"big"` // address options
- Address []byte `size:"*"` // address data (protocol-dependent)
+ Netw string `` // network protocol
+ Options uint32 `order:"big"` // address options
+ Expires AbsoluteTime `` // expiration date for address
+ Address []byte `size:"*"` // address data (protocol-dependent)
}
// NewAddress returns a new Address for the given transport and specs
func NewAddress(transport string, addr []byte) *Address {
- a := &Address{
- Transport: transport,
- Options: 0,
- Address: make([]byte, len(addr)),
+ return &Address{
+ Netw: transport,
+ Options: 0,
+ Address: Clone(addr),
+ Expires: AbsoluteTimeNever(),
}
- copy(a.Address, addr)
- return a
}
+func NewAddressWrap(addr net.Addr) *Address {
+ return &Address{
+ Netw: addr.Network(),
+ Options: 0,
+ Address: []byte(addr.String()),
+ Expires: AbsoluteTimeNever(),
+ }
+}
+
+// ParseAddress translates a GNUnet address string like
+// "r5n+ip+udp://1.2.3.4:6789" or "gnunet+tcp://12.3.4.5/".
+// It can also handle standard strings like "udp:127.0.0.1:6735".
+func ParseAddress(s string) (addr *Address, err error) {
+ p := strings.SplitN(s, ":", 2)
+ if len(p) != 2 {
+ err = fmt.Errorf("invalid address format: '%s'", s)
+ return
+ }
+ addr = NewAddress(p[0], []byte(strings.Trim(p[1], "/")))
+ return
+}
+
+// Equals return true if two addresses match.
+func (a *Address) Equals(b *Address) bool {
+ return a.Netw == b.Netw &&
+ a.Options == b.Options &&
+ bytes.Equal(a.Address, b.Address)
+}
+
+// StringAll returns a human-readable representation of an address.
+func (a *Address) StringAll() string {
+ return a.Netw + "://" + string(a.Address)
+}
+
+// implement net.Addr interface methods:
+
// String returns a human-readable representation of an address.
func (a *Address) String() string {
- return fmt.Sprintf("Address{%s}", AddressString(a.Transport, a.Address))
+ return string(a.Address)
+}
+
+// Network returns the protocol specifier.
+func (a *Address) Network() string {
+ return a.Netw
}
//----------------------------------------------------------------------
// AddressString returns a string representaion of an address.
-func AddressString(transport string, addr []byte) string {
- if transport == "tcp" || transport == "udp" {
- alen := len(addr)
- port := uint(addr[alen-2])*256 + uint(addr[alen-1])
- return fmt.Sprintf("%s:%s:%d", transport,
net.IP(addr[:alen-2]).String(), port)
- }
- return fmt.Sprintf("%s:%s", transport, hex.EncodeToString(addr))
+func AddressString(network string, addr []byte) string {
+ return network + "://" + string(addr)
}
//----------------------------------------------------------------------
@@ -76,3 +113,71 @@ func NewIPAddress(host []byte, port uint16) *IPAddress {
copy(ip.Host, host)
return ip
}
+
+//----------------------------------------------------------------------
+
+// PeerAddrList is a list of addresses per peer ID.
+type PeerAddrList struct {
+ list *Map[string, []*Address]
+}
+
+// NewPeerAddrList returns a new and empty address list.
+func NewPeerAddrList() *PeerAddrList {
+ return &PeerAddrList{
+ list: NewMap[string, []*Address](),
+ }
+}
+
+// Add address for peer. The returned mode is 0=not added, 1=new peer,
+// 2=new address
+func (a *PeerAddrList) Add(id string, addr *Address) (mode int) {
+ // check for expired address.
+ mode = 0
+ if !addr.Expires.Expired() {
+ // run add operation
+ a.list.Process(func() error {
+ list, ok := a.list.Get(id)
+ if !ok {
+ list = make([]*Address, 0)
+ mode = 1
+ } else {
+ for _, a := range list {
+ if a.Equals(addr) {
+ return nil
+ }
+ }
+ mode = 2
+ }
+ list = append(list, addr)
+ a.list.Put(id, list)
+ return nil
+ })
+ }
+ return
+}
+
+// Get address for peer
+func (a *PeerAddrList) Get(id string, transport string) *Address {
+ list, ok := a.list.Get(id)
+ if ok {
+ for _, addr := range list {
+ // check for expired address.
+ if addr.Expires.Expired() {
+ // skip expired
+ continue
+ }
+ // check for matching protocol
+ if len(transport) > 0 && transport != addr.Netw {
+ // skip other transports
+ continue
+ }
+ return addr
+ }
+ }
+ return nil
+}
+
+// Delete a list entry by key.
+func (a *PeerAddrList) Delete(id string) {
+ a.list.Delete(id)
+}
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 (
// Error variables
var (
- ErrUtilArrayTooSmall = fmt.Errorf("Array to small")
+ ErrUtilArrayTooSmall = fmt.Errorf("array to small")
)
//----------------------------------------------------------------------
-// Byte array helpers
+// generic array helpers
//----------------------------------------------------------------------
// Clone creates a new array of same content as the argument.
-func Clone(d []byte) []byte {
- r := make([]byte, len(d))
+func Clone[T []E, E any](d T) T {
+ r := make(T, len(d))
copy(r, d)
return r
}
-// Reverse the content of a byte array
-func Reverse(b []byte) []byte {
+// Equals returns true if two arrays match.
+func Equals[T []E, E comparable](a, b T) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, e := range a {
+ if e != b[i] {
+ return false
+ }
+ }
+ return true
+}
+
+// Reverse the content of an array
+func Reverse[T []E, E any](b T) T {
bl := len(b)
- r := make([]byte, bl)
+ r := make(T, bl)
for i := 0; i < bl; i++ {
r[bl-i-1] = b[i]
}
return r
}
-// IsNull returns true if all bytes in an array are set to 0.
-func IsNull(b []byte) bool {
+// IsAll returns true if all elements in an array are set to null.
+func IsAll[T []E, E comparable](b T, null E) bool {
for _, v := range b {
- if v != 0 {
+ if v != null {
return false
}
}
return true
}
-// CopyBlock copies 'in' to 'out' so that 'out' is filled completely.
+// Fill an array with a value
+func Fill[T []E, E any](b T, val E) {
+ for i := range b {
+ b[i] = val
+ }
+}
+
+//----------------------------------------------------------------------
+// byte array helpers
+//----------------------------------------------------------------------
+
+// CopyAlignedBlock copies 'in' to 'out' so that 'out' is filled completely.
// - If 'in' is larger than 'out', it is left-truncated before copy
// - If 'in' is smaller than 'out', it is left-padded with 0 before copy
-func CopyBlock(out, in []byte) {
+func CopyAlignedBlock(out, in []byte) {
count := len(in)
size := len(out)
from, to := 0, 0
@@ -76,27 +100,10 @@ func CopyBlock(out, in []byte) {
copy(out[to:], in[from:])
}
-// Fill an array with a value
-func Fill(b []byte, val byte) {
- for i := 0; i < len(b); i++ {
- b[i] = val
- }
-}
-
//----------------------------------------------------------------------
// String list helpers
//----------------------------------------------------------------------
-// ReverseStringList reverse an array of strings
-func ReverseStringList(s []string) []string {
- sl := len(s)
- r := make([]string, sl)
- for i := 0; i < sl; i++ {
- r[sl-i-1] = s[i]
- }
- return r
-}
-
// StringList converts a binary representation of a string list. Each string
// is '\0'-terminated. The whole byte array is parsed; if the final string is
// 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 @@
package util
import (
+ "context"
"database/sql"
"fmt"
"os"
@@ -34,7 +35,96 @@ var (
ErrSQLNoDatabase = fmt.Errorf("Database not found")
)
-// ConnectSQLDatabase connects to an SQL database (various types and flavors):
+//----------------------------------------------------------------------
+// Connection to a database instance. There can be multiple connections
+// on the same instance, managed by the database pool.
+//----------------------------------------------------------------------
+
+// DbConn is a database connection suitable for executing SQL commands.
+type DbConn struct {
+ conn *sql.Conn // connection to database instance
+ pool *dbPool // reference to managng pool
+ key string // database identifier (connect string)
+}
+
+// Close database connection.
+func (db *DbConn) Close() (err error) {
+ if err = db.conn.Close(); err != nil {
+ return
+ }
+ err = db.pool.remove(db.key)
+ return
+}
+
+// QueryRow returns a single record for a query
+func (db *DbConn) QueryRow(query string, args ...any) *sql.Row {
+ return db.conn.QueryRowContext(db.pool.ctx, query, args...)
+}
+
+// Query returns all matching records for a query
+func (db *DbConn) Query(query string, args ...any) (*sql.Rows, error) {
+ return db.conn.QueryContext(db.pool.ctx, query, args...)
+}
+
+// Exec a SQL statement
+func (db *DbConn) Exec(query string, args ...any) (sql.Result, error) {
+ return db.conn.ExecContext(db.pool.ctx, query, args...)
+}
+
+// TODO: add more SQL methods
+
+//----------------------------------------------------------------------
+// DbPool holds all database instances used: Connecting with the same
+// connect string returns the same instance.
+//----------------------------------------------------------------------
+
+// global instance for the database pool (singleton)
+var (
+ DbPool *dbPool
+)
+
+// DbPoolEntry holds information about a database instance.
+type DbPoolEntry struct {
+ db *sql.DB // reference to the database engine
+ refs int // number of open connections (reference count)
+ connect string // SQL connect string
+}
+
+// package initialization
+func init() {
+ // construct database pool
+ DbPool = new(dbPool)
+ DbPool.insts = NewMap[string, *DbPoolEntry]()
+ DbPool.ctx, DbPool.cancel = context.WithCancel(context.Background())
+}
+
+// dbPool keeps a mapping between connect string and database instance
+type dbPool struct {
+ ctx context.Context // connection context
+ cancel context.CancelFunc // cancel function
+ insts *Map[string, *DbPoolEntry] // map of database instances
+}
+
+// remove a database instance from the pool based on its connect string.
+func (p *dbPool) remove(key string) error {
+ return p.insts.Process(func() (err error) {
+ // get pool entry
+ pe, ok := p.insts.Get(key)
+ if !ok {
+ return nil
+ }
+ // decrement ref count
+ pe.refs--
+ if pe.refs == 0 {
+ // no more refs: close database
+ err = pe.db.Close()
+ p.insts.Delete(key)
+ }
+ return
+ })
+}
+
+// Connect to a SQL database (various types and flavors):
// The 'spec' option defines the arguments required to connect to a database;
// the meaning and format of the arguments depends on the specific SQL
database.
// The arguments are seperated by the '+' character; the first (and mandatory)
@@ -46,27 +136,50 @@ var (
// * 'mysql': A MySQL-compatible database; the second argument specifies the
// information required to log into the database (e.g.
// "[user[:passwd]@][proto[(addr)]]/dbname[?param1=value1&...]").
-func ConnectSQLDatabase(spec string) (db *sql.DB, err error) {
- // split spec string into segments
- specs := strings.Split(spec, ":")
- if len(specs) < 2 {
- return nil, ErrSQLInvalidDatabaseSpec
- }
- switch specs[0] {
- case "sqlite3":
- // check if the database file exists
- var fi os.FileInfo
- if fi, err = os.Stat(specs[1]); err != nil {
- return nil, ErrSQLNoDatabase
- }
- if fi.IsDir() {
- return nil, ErrSQLNoDatabase
+func (p *dbPool) Connect(spec string) (db *DbConn, err error) {
+ err = p.insts.Process(func() error {
+ // check if we have a connection to this database.
+ inst, ok := p.insts.Get(spec)
+ if !ok {
+ inst = new(DbPoolEntry)
+ inst.refs = 0
+ inst.connect = spec
+
+ // No: create new database instance.
+ // split spec string into segments
+ specs := strings.Split(spec, ":")
+ if len(specs) < 2 {
+ return ErrSQLInvalidDatabaseSpec
+ }
+ // create database object
+ switch specs[0] {
+ case "sqlite3":
+ // check if the database file exists
+ var fi os.FileInfo
+ if fi, err = os.Stat(specs[1]); err != nil {
+ return ErrSQLNoDatabase
+ }
+ if fi.IsDir() {
+ return ErrSQLNoDatabase
+ }
+ // open the database file
+ inst.db, err = sql.Open("sqlite3", specs[1])
+ case "mysql":
+ // just connect to the database
+ inst.db, err = sql.Open("mysql", specs[1])
+ default:
+ return ErrSQLInvalidDatabaseSpec
+ }
+ // save database in pool
+ p.insts.Put(spec, inst)
+ ok = true
}
- // open the database file
- return sql.Open("sqlite3", specs[1])
- case "mysql":
- // just connect to the database
- return sql.Open("mysql", specs[1])
- }
- return nil, ErrSQLInvalidDatabaseSpec
+ // increment reference count
+ inst.refs++
+ // return a new connection to the database.
+ db = new(DbConn)
+ db.conn, err = inst.db.Conn(p.ctx)
+ return err
+ })
+ return
}
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 (
"github.com/bfix/gospel/logger"
)
-// EnforceDirExists make sure that the path
+// EnforceDirExists make sure that the path is created
func EnforceDirExists(path string) error {
logger.Printf(logger.DBG, "[util] Checking directory '%s'...\n", path)
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 @@
-// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
-//
-// gnunet-go is free software: you can redistribute it and/or modify it
-// under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License,
-// or (at your option) any later version.
-//
-// gnunet-go is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-//
-// SPDX-License-Identifier: AGPL3.0-or-later
-
-package util
-
-import (
- "context"
- "database/sql"
- "fmt"
- "strconv"
- "strings"
-
- redis "github.com/go-redis/redis/v8"
-)
-
-// Error messages related to the key/value-store implementations
-var (
- ErrKVSInvalidSpec = fmt.Errorf("Invalid KVStore specification")
- ErrKVSNotAvailable = fmt.Errorf("KVStore not available")
-)
-
-// KeyValueStore interface for implementations that store and retrieve
-// key/value pairs. Keys and values are strings.
-type KeyValueStore interface {
- Put(key string, value string) error // put a key/value pair into store
- Get(key string) (string, error) // retrieve a value for a key from
store
- List() ([]string, error) // get all keys from the store
-}
-
-// OpenKVStore opens a key/value store for further put/get operations.
-// The 'spec' option specifies the arguments required to connect to a specific
-// persistence mechanism. The arguments in the 'spec' string are separated by
-// the '+' character.
-// The first argument specifies the type of key/value store to be used; the
-// meaning and format of the following arguments depend on this type.
-//
-// Key/Value Store types defined:
-// * 'redis': Use a Redis server for persistance; the specification is
-// "redis+addr+[passwd]+db". 'db' must be an integer value.
-// * 'mysql': MySQL-compatible database (see 'database.go' for details)
-// * 'sqlite3': SQLite3-compatible database (see 'database.go' for details)
-func OpenKVStore(spec string) (KeyValueStore, error) {
- // check specification string
- specs := strings.Split(spec, "+")
- if len(specs) < 2 {
- return nil, ErrKVSInvalidSpec
- }
- switch specs[0] {
- case "redis":
- //--------------------------------------------------------------
- // NoSQL-based persistance
- //--------------------------------------------------------------
- if len(specs) < 4 {
- return nil, ErrKVSInvalidSpec
- }
- db, err := strconv.Atoi(specs[3])
- if err != nil {
- return nil, ErrKVSInvalidSpec
- }
- kvs := new(KvsRedis)
- kvs.db = db
- kvs.client = redis.NewClient(&redis.Options{
- Addr: specs[1],
- Password: specs[2],
- DB: db,
- })
- if kvs.client == nil {
- err = ErrKVSNotAvailable
- }
- return kvs, err
-
- case "sqlite3", "mysql":
- //--------------------------------------------------------------
- // SQL-based persistance
- //--------------------------------------------------------------
- kvs := new(KvsSQL)
- var err error
-
- // connect to SQL database
- kvs.db, err = ConnectSQLDatabase(spec)
- if err != nil {
- return nil, err
- }
- // get number of key/value pairs (as a check for existing table)
- row := kvs.db.QueryRow("select count(*) from store")
- var num int
- if row.Scan(&num) != nil {
- return nil, ErrKVSNotAvailable
- }
- return kvs, nil
- }
- return nil, ErrKVSInvalidSpec
-}
-
-//======================================================================
-// NoSQL-based key-value-stores
-//======================================================================
-
-// KvsRedis represents a redis-based key/value store
-type KvsRedis struct {
- client *redis.Client // client connection
- db int // index to database
-}
-
-// Put a key/value pair into the store
-func (kvs *KvsRedis) Put(key string, value string) error {
- return kvs.client.Set(context.TODO(), key, value, 0).Err()
-}
-
-// Get a value for a given key from store
-func (kvs *KvsRedis) Get(key string) (value string, err error) {
- return kvs.client.Get(context.TODO(), key).Result()
-}
-
-// List all keys in store
-func (kvs *KvsRedis) List() (keys []string, err error) {
- var (
- crs uint64
- segm []string
- ctx = context.TODO()
- )
- for {
- segm, crs, err = kvs.client.Scan(ctx, crs, "*", 10).Result()
- if err != nil {
- return nil, err
- }
- if crs == 0 {
- break
- }
- keys = append(keys, segm...)
- }
- return
-}
-
-//======================================================================
-// SQL-based key-value-store
-//======================================================================
-
-// KvsSQL represents a SQL-based key/value store
-type KvsSQL struct {
- db *sql.DB
-}
-
-// Put a key/value pair into the store
-func (kvs *KvsSQL) Put(key string, value string) error {
- _, err := kvs.db.Exec("insert into store(key,value) values(?,?)", key,
value)
- return err
-}
-
-// Get a value for a given key from store
-func (kvs *KvsSQL) Get(key string) (value string, err error) {
- row := kvs.db.QueryRow("select value from store where key=?", key)
- err = row.Scan(&value)
- return
-}
-
-// List all keys in store
-func (kvs *KvsSQL) List() (keys []string, err error) {
- var (
- rows *sql.Rows
- key string
- )
- rows, err = kvs.db.Query("select key from store")
- if err == nil {
- for rows.Next() {
- if err = rows.Scan(&key); err != nil {
- break
- }
- keys = append(keys, key)
- }
- }
- return
-}
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
import (
"strings"
+ "sync"
)
-// CounterMap is a metric with single key
-type CounterMap map[interface{}]int
+//----------------------------------------------------------------------
+// Count occurence of multiple instance at the same time.
+//----------------------------------------------------------------------
+
+// Counter is a metric with single key
+type Counter[T comparable] map[T]int
// Add one to themetric for a given key and return current value
-func (cm CounterMap) Add(i interface{}) int {
+func (cm Counter[T]) Add(i T) int {
count, ok := cm[i]
if !ok {
count = 1
@@ -38,7 +43,7 @@ func (cm CounterMap) Add(i interface{}) int {
}
// Num returns the metric for a given key
-func (cm CounterMap) Num(i interface{}) int {
+func (cm Counter[T]) Num(i T) int {
count, ok := cm[i]
if !ok {
count = 0
@@ -46,6 +51,68 @@ func (cm CounterMap) Num(i interface{}) int {
return count
}
+//----------------------------------------------------------------------
+// Thread-safe map implementation
+//----------------------------------------------------------------------
+
+// Map keys to values
+type Map[K comparable, V any] struct {
+ list map[K]V
+ mtx sync.RWMutex
+ inProcess bool
+}
+
+// NewMap allocates a new mapping.
+func NewMap[K comparable, V any]() *Map[K, V] {
+ return &Map[K, V]{
+ list: make(map[K]V),
+ inProcess: false,
+ }
+}
+
+// Process a function in the locked map context. Calls
+// to other map functions in 'f' will use additional locks.
+func (m *Map[K, V]) Process(f func() error) error {
+ m.mtx.Lock()
+ defer m.mtx.Unlock()
+ m.inProcess = true
+ err := f()
+ m.inProcess = false
+ return err
+}
+
+// Put value into map under given key.
+func (m *Map[K, V]) Put(key K, value V) {
+ if !m.inProcess {
+ m.mtx.Lock()
+ defer m.mtx.Unlock()
+ }
+ m.list[key] = value
+}
+
+// Get value with iven key from map.
+func (m *Map[K, V]) Get(key K) (value V, ok bool) {
+ if !m.inProcess {
+ m.mtx.RLock()
+ defer m.mtx.RUnlock()
+ }
+ value, ok = m.list[key]
+ return
+}
+
+// Delete key/value pair from map.
+func (m *Map[K, V]) Delete(key K) {
+ if !m.inProcess {
+ m.mtx.Lock()
+ defer m.mtx.Unlock()
+ }
+ delete(m.list, key)
+}
+
+//----------------------------------------------------------------------
+// additional helpers
+//----------------------------------------------------------------------
+
// StripPathRight returns a dot-separated path without
// its last (right-most) element.
func 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 @@
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
-// Copyright (C) 2019, 2020 Bernd Fix >Y<
+// Copyright (C) 2019-2022 Bernd Fix >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
@@ -18,6 +18,8 @@
package util
+import "bytes"
+
// PeerID is the 32-byte binary representation od a Ed25519 key
type PeerID struct {
Key []byte `size:"32"`
@@ -33,7 +35,7 @@ func NewPeerID(data []byte) *PeerID {
data = data[:32]
} else if size < 32 {
buf := make([]byte, 32)
- CopyBlock(buf, data)
+ CopyAlignedBlock(buf, data)
data = buf
}
}
@@ -42,6 +44,11 @@ func NewPeerID(data []byte) *PeerID {
}
}
+// Equals returns true if two peer IDs match.
+func (p *PeerID) Equals(q *PeerID) bool {
+ return bytes.Equal(p.Key, q.Key)
+}
+
// String returns a human-readable representation of a peer id.
func (p *PeerID) String() string {
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 {
}
}
+// NewAbsoluteTimeEpoch set the point in time to the given time value
+func NewAbsoluteTimeEpoch(secs uint64) AbsoluteTime {
+ return AbsoluteTime{
+ Val: uint64(secs * 1000000),
+ }
+}
+
// AbsoluteTimeNow returns the current point in time.
func AbsoluteTimeNow() AbsoluteTime {
return NewAbsoluteTime(time.Now())
@@ -53,6 +60,11 @@ func AbsoluteTimeNever() AbsoluteTime {
return AbsoluteTime{math.MaxUint64}
}
+// Epoch returns the seconds since Unix epoch.
+func (t AbsoluteTime) Epoch() uint64 {
+ return t.Val / 1000000
+}
+
// String returns a human-readable notation of an absolute time.
func (t AbsoluteTime) String() string {
if t.Val == math.MaxUint64 {
@@ -133,3 +145,8 @@ func (t RelativeTime) String() string {
}
return time.Duration(t.Val * 1000).String()
}
+
+// Add two durations
+func (t RelativeTime) Add(t2 RelativeTime) {
+ t.Val += t2.Val
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnunet-go] branch master updated: Reworked gnunet/transport and gnunet/service.,
gnunet <=