Whisper: Remove C and Go wrappers and Nimbus as a library

Remove the C and Go example wrappers that call Nimbus as a library, by removing
the entire `wrappers/` directory.  They are removed because they only wrap
Whisper protocol support, which has been removed as it is obsolete.

The only thing wrapped were Whisper functions, even though there were separate
`go_wrapper_example` and `go_wrapper_whisper_example` programs.  The wrappers
don't build without Whisper in Nimbus, and without it, there isn't enough left
for them to be useful examples.

Also remove support for building the whole of Nimbus as a library, because
there is nothing left using it.  These targets are gone from the Makefile:

- `wrappers`
- `wrappers-static`
- `libnimbus.so`
- `libnimbus.a`

The code isn't really gone, because it remains available in Git history.  It
may be useful someday, so a comment has been left in the Makefile for future
generations:

> This note is kept so that anyone wanting to build Nimbus as a library or call
> from C or Go will know it has been done before.  The previous working version
> can be found in Git history.  Look for the `nimbus-eth1` commit that adds
> this comment and removes `wrappers/*`.

Also worth a note, the library support was not tested on Windows, and the
shared library support was only tested on Linux.

Signed-off-by: Jamie Lokier <jamie@shareable.org>
This commit is contained in:
Jamie Lokier 2021-06-01 17:45:11 +01:00
parent cf36bdb801
commit ecb0654da7
No known key found for this signature in database
GPG Key ID: CBC25C68435C30A2
8 changed files with 12 additions and 1024 deletions

View File

@ -34,8 +34,6 @@ TOOLS_CSV := $(subst $(SPACE),$(COMMA),$(TOOLS))
clean \
libnimbus.so \
libnimbus.a \
wrappers \
wrappers-static \
libbacktrace
ifeq ($(NIM_PARAMS),)
@ -135,52 +133,21 @@ test-reproducibility:
# usual cleaning
clean: | clean-common
rm -rf build/{nimbus,$(TOOLS_CSV),all_tests,test_rpc,*_wrapper_test}
rm -rf build/{nimbus,$(TOOLS_CSV),all_tests,test_rpc}
ifneq ($(USE_LIBBACKTRACE), 0)
+ $(MAKE) -C vendor/nim-libbacktrace clean $(HANDLE_OUTPUT)
endif
libnimbus.so: | build deps
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim c --app:lib --noMain --nimcache:nimcache/libnimbus $(NIM_PARAMS) -o:build/$@.0 wrappers/libnimbus.nim && \
rm -f build/$@ && \
ln -s $@.0 build/$@
# libraries for dynamic linking of non-Nim objects
EXTRA_LIBS_DYNAMIC := -L"$(CURDIR)/build" -lnimbus -lm
wrappers: | build deps libnimbus.so
echo -e $(BUILD_MSG) "build/C_wrapper_example" && \
$(CC) wrappers/wrapper_example.c -Wl,-rpath,'$$ORIGIN' $(EXTRA_LIBS_DYNAMIC) -g -o build/C_wrapper_example
echo -e $(BUILD_MSG) "build/go_wrapper_example" && \
go build -ldflags "-linkmode external -extldflags '$(EXTRA_LIBS_DYNAMIC)'" -o build/go_wrapper_example wrappers/wrapper_example.go wrappers/cfuncs.go
echo -e $(BUILD_MSG) "build/go_wrapper_whisper_example" && \
go build -ldflags "-linkmode external -extldflags '$(EXTRA_LIBS_DYNAMIC)'" -o build/go_wrapper_whisper_example wrappers/wrapper_whisper_example.go wrappers/cfuncs.go
libnimbus.a: | build deps
echo -e $(BUILD_MSG) "build/$@" && \
rm -f build/$@ && \
$(ENV_SCRIPT) nim c --app:staticlib --noMain --nimcache:nimcache/libnimbus_static $(NIM_PARAMS) -o:build/$@ wrappers/libnimbus.nim && \
[[ -e "$@" ]] && mv "$@" build/ # workaround for https://github.com/nim-lang/Nim/issues/12745
# These libraries are for statically linking non-Nim objects to libnimbus.a
# (where "vendor/nim-libbacktrace/libbacktrace.nim" doesn't get to set its LDFLAGS)
EXTRA_LIBS_STATIC := -L"$(CURDIR)/build" -lnimbus -L"$(CURDIR)/vendor/nim-libbacktrace/install/usr/lib" -lbacktracenim -lbacktrace -lm -ldl -lpcre
ifeq ($(shell uname), Darwin)
USE_VENDORED_LIBUNWIND := 1
endif # macOS
ifeq ($(OS), Windows_NT)
USE_VENDORED_LIBUNWIND := 1
endif # Windows
ifeq ($(USE_VENDORED_LIBUNWIND), 1)
EXTRA_LIBS_STATIC := $(EXTRA_LIBS_STATIC) -lunwind
endif # USE_VENDORED_LIBUNWIND
wrappers-static: | build deps libnimbus.a
echo -e $(BUILD_MSG) "build/C_wrapper_example_static" && \
$(CC) wrappers/wrapper_example.c -static -pthread $(EXTRA_LIBS_STATIC) -g -o build/C_wrapper_example_static
echo -e $(BUILD_MSG) "build/go_wrapper_example_static" && \
go build -ldflags "-linkmode external -extldflags '-static $(EXTRA_LIBS_STATIC)'" -o build/go_wrapper_example_static wrappers/wrapper_example.go wrappers/cfuncs.go
echo -e $(BUILD_MSG) "build/go_wrapper_whisper_example_static" && \
go build -ldflags "-linkmode external -extldflags '-static $(EXTRA_LIBS_STATIC)'" -o build/go_wrapper_whisper_example_static wrappers/wrapper_whisper_example.go wrappers/cfuncs.go
# Note about building Nimbus as a library:
#
# There were `wrappers`, `wrappers-static`, `libnimbus.so` and `libnimbus.a`
# target scripts here, and C and Go examples for calling the Nimbus library in
# directory `wrappers/`. They have been removed because they only wrapped
# Whisper protocol support, which has been removed as it is obsolete.
#
# This note is kept so that anyone wanting to build Nimbus as a library or call
# from C or Go will know it has been done before. The previous working version
# can be found in Git history. Look for the `nimbus-eth1` commit that adds
# this comment and removes `wrappers/*`.
endif # "variables.mk" was not included

View File

@ -1,71 +0,0 @@
This folder contains an experimental C wrapper for using parts of the Nimbus
code from C/Go in the Status console client:
https://github.com/status-im/status-console-client/
It serves mainly as a proof-of-concept for now - there are several unresolved
issues surrounding threading, inter-language communication, callbacks etc.
To build the wrappers and the example programs, run from the top level directory:
```bash
make wrappers
```
Now you can run the example programs:
```bash
build/C_wrapper_example
build/go_wrapper_example
build/go_wrapper_whisper_example
```
To build statically linked versions:
```bash
make wrappers-static
```
You can also build the wrappers with the Nix build file (Need to
[install](https://nixos.org/nix/download.html) Nix first):
```bash
nix-build -A wrappers
```
# Notes on usage
Before any of the API calls are done, `NimMain()` needs to be run.
After this any of the asymmetric or symmetric keys API calls can be done.
Before any actual Whisper calls are done, `nimbus_start()` needs to be called.
This will setup the Whisper node, start discovery and connect to the Status
fleet.
After this call you can use the Whisper related calls such as
`nimbus_subscribe_filter` and `nimbus_post`.
`nimbus_subscribe_filter` allows you to pass a pointer to user data. This will
just pass the pointer to the, by you provided, callback. You can use this to
have a certain context in the callback.
Be aware that when using this with cgo, you will have a problem when passing a
Go pointer that contains other Go pointers, as this is no longer allowed.
See also: https://golang.org/cmd/cgo/#hdr-Passing_pointers
This can be resolved by refering to it indirectly, e.g. using a map in Go
containing the pointers. See here for an example solution:
https://github.com/mattn/go-pointer/blob/master/pointer.go
# Caveat
Two items to take into consideration:
1. All API calls must be called from the same thread where the initial
`NimMain()` was called from. While there are
[ways](https://nim-lang.org/docs/backends.html#memory-management-thread-coordination)
of thread coordination, this will not work for calls involving `async` work
with Chronos. Thus to be extra clear & consistent, the above mentioned ways are
not used at all.
2. For most of the API calls it is assumed that when passing a pointer as
parameter, that the data that this pointer refers to remains valid during the
duration of the call. If this is not the case, issues might occur as copying
might happen to late.

View File

@ -1,14 +0,0 @@
package main
/*
#include "libnimbus.h"
// receiveHandler gateway function
void receiveHandler_cgo(received_message * msg, void* udata)
{
void receiveHandler(received_message* msg, void* udata);
receiveHandler(msg, udata);
}
*/
import "C"

View File

@ -1,132 +0,0 @@
#ifndef __LIBNIMBUS_H__
#define __LIBNIMBUS_H__
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
const uint8_t* decoded; /* Decoded payload */
size_t decodedLen; /* Decoded payload length */
const uint8_t* source; /* 64 bytes public key, can be nil */
const uint8_t* recipientPublicKey; /* 64 bytes public key, can be nil */
uint32_t timestamp; /* Timestamp of creation message, expiry - ttl */
uint32_t ttl; /* TTL of message */
uint8_t topic[4]; /* Topic of message */
double pow; /* PoW value of received message */
uint8_t hash[32]; /* Hash of message */
} received_message;
typedef struct {
const uint8_t* symKeyID; /* 32 bytes identifier for symmetric key, set to nil if none */
const uint8_t* privateKeyID; /* 32 bytes identifier for asymmetric key, set to nil if none */
const uint8_t* source; /* 64 bytes public key, set to nil if none */
double minPow; /* Minimum PoW that message must have */
uint8_t topic[4]; /* Will default to 0x00000000 if not provided */
int allowP2P;
} filter_options;
typedef struct {
const uint8_t* symKeyID; /* 32 bytes identifier for symmetric key, set to nil if none */
const uint8_t* pubKey; /* 64 bytes public key, set to nil if none */
const uint8_t* sourceID; /* 32 bytes identifier for asymmetric key, set to nil if none */
uint32_t ttl; /* TTL of message */
uint8_t topic[4]; /* Will default to 0x00000000 if not provided */
uint8_t* payload; /* Payload to be send, can be len=0 but can not be nil */
size_t payloadLen; /* Payload length */
uint8_t* padding; /* Custom padding, can be set to nil */
size_t paddingLen; /* Padding length */
double powTime; /* Maximum time to calculate PoW */
double powTarget; /* Minimum PoW target to reach before stopping */
} post_message;
typedef struct {
uint8_t topic[4];
} topic;
typedef void (*received_msg_handler)(received_message* msg, void* udata);
/** Buffer lengths, can be used in go for convenience */
#define ID_LEN 32
#define SYMKEY_LEN 32
#define PRIVKEY_LEN 32
#define BLOOM_LEN 64
/** Initialize Nim and the Status library. Must be called before anything else
* of the API. Also, all following calls must come from the same thread as from
* which this call was done.
*/
void NimMain();
/** Start Ethereum node with Whisper capability and connect to Status fleet.
* Optionally start discovery and listen for incoming connections.
* The minPow value is the minimum required PoW that this node will allow.
* When privkey is null, a new keypair will be generated.
*/
bool nimbus_start(uint16_t port, bool startListening, bool enableDiscovery,
double minPow, const uint8_t* privkey, bool staging);
/** Add peers to connect to - must be called after nimbus_start */
bool nimbus_add_peer(const char* nodeId);
/**
* Should be called in regularly - for example in a busy loop (beautiful!) on
* dedicated thread.
*/
void nimbus_poll();
/** Asymmetric Keys API */
/** Raw 32 byte arrays are passed as IDs. The caller needs to provide a pointer
* to 32 bytes allocation for this. */
bool nimbus_new_keypair(uint8_t id[ID_LEN]);
bool nimbus_add_keypair(const uint8_t privkey[PRIVKEY_LEN], uint8_t id[ID_LEN]);
bool nimbus_delete_keypair(const uint8_t id[ID_LEN]);
bool nimbus_get_private_key(const uint8_t id[ID_LEN],
uint8_t privkey[PRIVKEY_LEN]);
/** Symmetric Keys API */
/** Raw 32 byte arrays are passed as IDs. The caller needs to provide a pointer
* to 32 bytes allocation for this. */
bool nimbus_add_symkey(const uint8_t symkey[SYMKEY_LEN], uint8_t id[ID_LEN]);
bool nimbus_add_symkey_from_password(const char* password, uint8_t id[ID_LEN]);
bool nimbus_delete_symkey(const uint8_t id[ID_LEN]);
bool nimbus_get_symkey(const uint8_t id[ID_LEN], uint8_t symkey[SYMKEY_LEN]);
/** Whisper message posting and receiving API */
/* Post Whisper message to the queue */
bool nimbus_post(post_message* msg);
/** Subscribe to given filter. The void pointer udata will be passed to the
* received_msg_handler callback.
*/
bool nimbus_subscribe_filter(filter_options* filter_options,
received_msg_handler msg, void* udata, uint8_t id[ID_LEN]);
bool nimbus_unsubscribe_filter(const uint8_t id[ID_LEN]);
/** Get the minimum required PoW of this node */
double nimbus_get_min_pow();
/** Get the currently set bloom filter of this node. This will automatically
*update for each filter subsribed to.
*/
void nimbus_get_bloom_filter(uint8_t bloomfilter[BLOOM_LEN]);
/** Example helper, can be removed */
topic nimbus_channel_to_topic(const char* channel);
/** Very limited Status chat API */
void nimbus_post_public(const char* channel, const char* payload);
void nimbus_join_public_chat(const char* channel, received_msg_handler msg);
#ifdef __cplusplus
}
#endif
#endif //__LIBNIMBUS_H__

View File

@ -1,493 +0,0 @@
#
# Nimbus
# (c) Copyright 2019
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import
chronos, chronicles, nimcrypto/[utils, hmac, pbkdf2, hash, sysrand], tables,
stew/ranges/ptr_arith, eth/[keys, rlp, p2p, async_utils],
eth/p2p/rlpx_protocols/whisper_protocol,
eth/p2p/[peer_pool, bootnodes, whispernodes], ../nimbus/rpc/key_storage,
../nimbus/random_keys
# TODO: lots of overlap with Nimbus Whisper RPC here, however not all
# the same due to type conversion (no use of Option and such). Perhaps some
# parts can be refactored in sharing some of the code.
const idLen = 32
type
Identifier = array[idLen, byte]
CReceivedMessage* = object
decoded*: ptr byte
decodedLen*: int # csize_t
source*: ptr byte
recipientPublicKey*: ptr byte
timestamp*: uint32
ttl*: uint32
topic*: Topic
pow*: float64
hash*: Hash
CFilterOptions* = object
symKeyID*: ptr byte
privateKeyID*: ptr byte
source*: ptr byte
minPow*: float64
topic*: Topic # lets go with one topic for now unless more are required
allowP2P*: bool
CPostMessage* = object
symKeyID*: ptr byte
pubKey*: ptr byte
sourceID*: ptr byte
ttl*: uint32
topic*: Topic
payload*: ptr byte
payloadLen*: int # csize_t
padding*: ptr byte
paddingLen*: int # csize_t
powTime*: float64
powTarget*: float64
CTopic* = object
topic*: Topic
# Don't do this at home, you'll never get rid of ugly globals like this!
var
node: EthereumNode
# You will only add more instead!
let whisperKeys = newKeyStorage()
proc generateRandomID(): Identifier =
while true: # TODO: error instead of looping?
if randomBytes(result) == idLen:
break
proc setBootNodes(nodes: openArray[string]): seq[ENode] =
result = newSeqOfCap[ENode](nodes.len)
for nodeId in nodes:
# For now we can just do assert as we only pass our own const arrays.
let enode = ENode.fromString(nodeId).expect("correct enode")
result.add(enode)
proc connectToNodes(nodes: openArray[string]) =
for nodeId in nodes:
# For now we can just do assert as we only pass our own const arrays.
let enode = ENode.fromString(nodeId).expect("correct enode")
traceAsyncErrors node.peerPool.connectToNode(newNode(enode))
# Setting up the node
proc nimbus_start(port: uint16, startListening: bool, enableDiscovery: bool,
minPow: float64, privateKey: ptr byte, staging: bool): bool
{.exportc, dynlib.} =
# TODO: any async calls can still create `Exception`, why?
let address = Address(
udpPort: port.Port, tcpPort: port.Port, ip: parseIpAddress("0.0.0.0"))
var keypair: KeyPair
if privateKey.isNil:
#var kp = KeyPair.random()
#if kp.isErr:
#error "Can't generate keypair", err = kp.error
#return false
#keypair = kp[]
keypair = randomKeyPair()
else:
let
privKey = PrivateKey.fromRaw(makeOpenArray(privateKey, 32))
if privKey.isErr:
error "Passed an invalid private key."
return false
keypair = privKey[].toKeyPair()
node = newEthereumNode(keypair, address, 1.NetworkId, nil, addAllCapabilities = false)
node.addCapability Whisper
node.protocolState(Whisper).config.powRequirement = minPow
# TODO: should we start the node with an empty bloomfilter?
# var bloom: Bloom
# node.protocolState(Whisper).config.bloom = bloom
let bootnodes = if staging: setBootNodes(StatusBootNodesStaging)
else: setBootNodes(StatusBootNodes)
traceAsyncErrors node.connectToNetwork(bootnodes, startListening,
enableDiscovery)
# Connect to known Status Whisper fleet directly
if staging: connectToNodes(WhisperNodesStaging)
else: connectToNodes(WhisperNodes)
result = true
proc nimbus_poll() {.exportc, dynlib.} =
poll()
proc nimbus_add_peer(nodeId: cstring): bool {.exportc, dynlib.} =
var
whisperNode: Node
let enode = ENode.fromString($nodeId)
if enode.isErr:
return false
try:
whisperNode = newNode(enode[])
except CatchableError:
return false
# TODO: call can create `Exception`, why?
traceAsyncErrors node.peerPool.connectToNode(whisperNode)
result = true
# Whisper API (Similar to Whisper JSON-RPC API)
proc nimbus_channel_to_topic(channel: cstring): CTopic
{.exportc, dynlib, raises: [Defect].} =
# Only used for the example, to conveniently convert channel to topic.
doAssert(not channel.isNil, "Channel cannot be nil.")
let hash = digest(keccak256, $channel)
for i in 0..<4:
result.topic[i] = hash.data[i]
# Asymmetric Keys
proc nimbus_new_keypair(id: var Identifier): bool
{.exportc, dynlib, raises: [Defect].} =
## Caller needs to provide as id a pointer to 32 bytes allocation.
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
id = generateRandomID()
try:
whisperKeys.asymKeys.add(id.toHex(), randomKeyPair())
result = true
except CatchableError:
# Don't think this can actually happen, comes from the `getPublicKey` part
# in `newKeyPair`
discard
proc nimbus_add_keypair(privateKey: ptr byte, id: var Identifier):
bool {.exportc, dynlib, raises: [Defect, Exception].} =
## Caller needs to provide as id a pointer to 32 bytes allocation.
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
doAssert(not privateKey.isNil, "Private key cannot be nil.")
var keypair: KeyPair
if privateKey.isNil:
#var kp = KeyPair.random()
#if kp.isErr:
#error "Can't generate keypair", err = kp.error
#return false
#keypair = kp[]
keypair = randomKeyPair()
else:
let
privKey = PrivateKey.fromRaw(makeOpenArray(privateKey, 32))
if privKey.isErr:
error "Passed an invalid private key."
return false
keypair = privKey[].toKeyPair()
result = true
id = generateRandomID()
whisperKeys.asymKeys.add(id.toHex(), keypair)
proc nimbus_delete_keypair(id: Identifier): bool
{.exportc, dynlib, raises: [].} =
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
var unneeded: KeyPair
result = whisperKeys.asymKeys.take(id.toHex(), unneeded)
proc nimbus_get_private_key(id: Identifier, privateKey: var PrivateKey):
bool {.exportc, dynlib, raises: [OSError, IOError, ValueError, Exception].} =
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
doAssert(not (unsafeAddr privateKey).isNil, "Private key cannot be nil.")
try:
privateKey = whisperKeys.asymkeys[id.toHex()].seckey
result = true
except KeyError:
error "Private key not found."
# Symmetric Keys
proc nimbus_add_symkey(symKey: ptr SymKey, id: var Identifier): bool
{.exportc, dynlib, raises: [Defect].} =
## Caller needs to provide as id a pointer to 32 bytes allocation.
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
doAssert(not symKey.isNil, "Symmetric key cannot be nil.")
id = generateRandomID()
result = true
# Copy of key happens at add
whisperKeys.symKeys.add(id.toHex, symKey[])
proc nimbus_add_symkey_from_password(password: cstring, id: var Identifier):
bool {.exportc, dynlib, raises: [Defect].} =
## Caller needs to provide as id a pointer to 32 bytes allocation.
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
doAssert(not password.isNil, "Password cannot be nil.")
var ctx: HMAC[sha256]
var symKey: SymKey
if pbkdf2(ctx, $password, "", 65356, symKey) != sizeof(SymKey):
return false
id = generateRandomID()
result = true
whisperKeys.symKeys.add(id.toHex(), symKey)
proc nimbus_delete_symkey(id: Identifier): bool
{.exportc, dynlib, raises: [Defect].} =
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
var unneeded: SymKey
result = whisperKeys.symKeys.take(id.toHex(), unneeded)
proc nimbus_get_symkey(id: Identifier, symKey: var SymKey):
bool {.exportc, dynlib, raises: [Defect, Exception].} =
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
doAssert(not (unsafeAddr symKey).isNil, "Symmetric key cannot be nil.")
try:
symKey = whisperKeys.symkeys[id.toHex()]
result = true
except KeyError:
error "Symmetric key not found."
# Whisper message posting and receiving
proc nimbus_post(message: ptr CPostMessage): bool {.exportc, dynlib.} =
## Encryption is mandatory.
## A symmetric key or an asymmetric key must be provided. Both is not allowed.
## Providing a payload is mandatory, it cannot be nil, but can be of length 0.
doAssert(not message.isNil, "Message pointer cannot be nil.")
var
sigPrivKey: Option[PrivateKey]
asymKey: Option[PublicKey]
symKey: Option[SymKey]
padding: Option[seq[byte]]
payload: seq[byte]
if not message.pubKey.isNil() and not message.symKeyID.isNil():
warn "Both symmetric and asymmetric keys are provided, choose one."
return false
if message.pubKey.isNil() and message.symKeyID.isNil():
warn "Both symmetric and asymmetric keys are nil, provide one."
return false
if not message.pubKey.isNil():
let pubkey = PublicKey.fromRaw(makeOpenArray(message.pubKey, 64))
if pubkey.isErr:
error "Passed an invalid public key for encryption."
return false
asymKey = some(pubkey[])
try:
if not message.symKeyID.isNil():
let symKeyId = makeOpenArray(message.symKeyID, idLen).toHex()
symKey = some(whisperKeys.symKeys[symKeyId])
if not message.sourceID.isNil():
let sourceId = makeOpenArray(message.sourceID, idLen).toHex()
sigPrivKey = some(whisperKeys.asymKeys[sourceId].seckey)
except KeyError:
warn "No key found with provided key id."
return false
if not message.payload.isNil():
# This will make a copy
payload = @(makeOpenArray(message.payload, message.payloadLen))
else:
warn "Message payload was nil, post aborted."
return false
if not message.padding.isNil():
# This will make a copy
padding = some(@(makeOpenArray(message.padding, message.paddingLen)))
# TODO: call can create `Exception`, why?
result = node.postMessage(asymKey,
symKey,
sigPrivKey,
ttl = message.ttl,
topic = message.topic,
payload = payload,
padding = padding,
powTime = message.powTime,
powTarget = message.powTarget)
proc nimbus_subscribe_filter(options: ptr CFilterOptions,
handler: proc (msg: ptr CReceivedMessage, udata: pointer)
{.gcsafe, cdecl, raises: [Defect].},
udata: pointer = nil, id: var Identifier): bool {.exportc, dynlib.} =
## Encryption is mandatory.
## A symmetric key or an asymmetric key must be provided. Both is not allowed.
## The received message needs to be copied before the passed handler ends.
doAssert(not (unsafeAddr id).isNil, "Key id cannot be nil.")
doAssert(not options.isNil, "Filter options pointer cannot be nil.")
doAssert(not handler.isNil, "Filter handler cannot be nil." )
var
src: Option[PublicKey]
symKey: Option[SymKey]
privateKey: Option[PrivateKey]
if not options.privateKeyID.isNil() and not options.symKeyID.isNil():
warn "Both symmetric and asymmetric keys are provided, choose one."
return false
if options.privateKeyID.isNil() and options.symKeyID.isNil():
warn "Both symmetric and asymmetric keys are nil, provide one."
return false
if not options.source.isNil():
let pubkey = PublicKey.fromRaw(makeOpenArray(options.source, 64))
if pubkey.isErr:
error "Passed an invalid public key as source."
return false
src = some(pubkey[])
try:
if not options.symKeyID.isNil():
let symKeyId = makeOpenArray(options.symKeyID, idLen).toHex()
symKey = some(whisperKeys.symKeys[symKeyId])
if not options.privateKeyID.isNil():
let privKeyId = makeOpenArray(options.privateKeyID, idLen).toHex()
privateKey = some(whisperKeys.asymKeys[privKeyId].seckey)
except KeyError:
return false
let filter = initFilter(src, privateKey, symKey, @[options.topic],
options.minPow, options.allowP2P)
proc c_handler(msg: ReceivedMessage) {.gcsafe, raises: [Defect].} =
var cmsg = CReceivedMessage(
decoded: unsafeAddr msg.decoded.payload[0],
decodedLen: msg.decoded.payload.len(),
timestamp: msg.timestamp,
ttl: msg.ttl,
topic: msg.topic,
pow: msg.pow,
hash: msg.hash
)
# Could also allocate here, but this should stay in scope until handler
# finishes so it should be fine.
var
source: array[RawPublicKeySize, byte]
recipientPublicKey: array[RawPublicKeySize, byte]
if msg.decoded.src.isSome():
# Need to pass the serialized form
source = msg.decoded.src.get().toRaw()
cmsg.source = addr source[0]
if msg.dst.isSome():
# Need to pass the serialized form
recipientPublicKey = msg.decoded.src.get().toRaw()
cmsg.recipientPublicKey = addr recipientPublicKey[0]
handler(addr cmsg, udata)
# TODO: call can create `Exception`, why?
# TODO: if we decide to internally also work with other IDs, we don't need
# to do this hex conversion back and forth.
hexToBytes(node.subscribeFilter(filter, c_handler), id)
# Bloom filter has to follow only the subscribed topics
# TODO: better to have an "adding" proc here
# TODO: call can create `Exception`, why?
traceAsyncErrors node.setBloomFilter(node.filtersToBloom())
result = true
proc nimbus_unsubscribe_filter(id: Identifier): bool
{.exportc, dynlib, raises: [].} =
doAssert(not(unsafeAddr id).isNil, "Filter id cannot be nil.")
result = node.unsubscribeFilter(id.toHex())
proc nimbus_get_min_pow(): float64 {.exportc, dynlib, raises: [].} =
result = node.protocolState(Whisper).config.powRequirement
proc nimbus_get_bloom_filter(bloom: var Bloom) {.exportc, dynlib, raises: [].} =
doAssert(not (unsafeAddr bloom).isNil, "Bloom pointer cannot be nil.")
bloom = node.protocolState(Whisper).config.bloom
# Nimbus limited Status chat API
# TODO: Return filter ID if we ever want to unsubscribe
proc subscribeChannel(
channel: string, handler: proc (msg: ReceivedMessage)
{.gcsafe, raises: [Defect].}) =
var ctx: HMAC[sha256]
var symKey: SymKey
discard ctx.pbkdf2(channel, "", 65356, symKey)
let channelHash = digest(keccak256, channel)
var topic: array[4, byte]
for i in 0..<4:
topic[i] = channelHash.data[i]
info "Subscribing to channel", channel, topic, symKey
discard node.subscribeFilter(initFilter(symKey = some(symKey),
topics = @[topic]),
handler)
proc nimbus_join_public_chat(channel: cstring,
handler: proc (msg: ptr CReceivedMessage)
{.gcsafe, cdecl, raises: [Defect].}) {.exportc, dynlib.} =
if handler.isNil:
subscribeChannel($channel, nil)
else:
proc c_handler(msg: ReceivedMessage) {.raises: [Defect].} =
var cmsg = CReceivedMessage(
decoded: unsafeAddr msg.decoded.payload[0],
decodedLen: msg.decoded.payload.len(),
timestamp: msg.timestamp,
ttl: msg.ttl,
topic: msg.topic,
pow: msg.pow,
hash: msg.hash
)
handler(addr cmsg)
subscribeChannel($channel, c_handler)
# TODO: Add signing key as parameter
# TODO: How would we do key management? In nimbus (like in rpc) or in status go?
proc nimbus_post_public(channel: cstring, payload: cstring)
{.exportc, dynlib.} =
let encPrivateKey = PrivateKey.fromHex("5dc5381cae54ba3174dc0d46040fe11614d0cc94d41185922585198b4fcef9d3")[]
var ctx: HMAC[sha256]
var symKey: SymKey
var npayload = cast[seq[byte]]($payload)
discard ctx.pbkdf2($channel, "", 65356, symKey)
let channelHash = digest(keccak256, $channel)
var topic: array[4, byte]
for i in 0..<4:
topic[i] = channelHash.data[i]
# TODO: Handle error case
discard node.postMessage(symKey = some(symKey),
src = some(encPrivateKey),
ttl = 20,
topic = topic,
payload = npayload,
powTarget = 0.002)

View File

@ -1,46 +0,0 @@
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include "libnimbus.h"
void NimMain();
void print_msg(received_message* msg, void* udata) {
// Note: early null chars will terminate string early
printf("received message %.*s\n", (int)msg->decodedLen, msg->decoded);
}
const char* channel = "status-test-c";
const char* msg = "testing message";
int main(int argc, char* argv[]) {
time_t lastmsg;
NimMain();
nimbus_start(30303, true, false, 0.002, NULL, false);
nimbus_join_public_chat(channel, print_msg);
lastmsg = time(NULL);
while(1) {
usleep(1);
if (lastmsg + 1 <= time(NULL)) {
lastmsg = time(NULL);
char buf[4096];
snprintf(buf, 4095,
"[\"~#c4\",[\"%s\",\"text/plain\",\"~:public-group-user-message\",%ld,%ld,[\"^ \",\"~:chat-id\",\"%s\",\"~:text\",\"%s\"]]]",
msg, lastmsg * 1000 * 100, lastmsg * 1000, channel, msg);
printf("Posting %s\n", buf);
nimbus_post_public(channel, buf);
}
nimbus_poll();
}
}

View File

@ -1,85 +0,0 @@
package main
import (
"fmt"
"runtime"
"time"
"unsafe"
)
/*
#include <stdlib.h>
// Passing "-lnimbus" to the Go linker through "-extldflags" is not enough. We need it in here, for some reason.
#cgo LDFLAGS: -Wl,-rpath,'$ORIGIN' -L${SRCDIR}/../build -lnimbus
#include "libnimbus.h"
void receiveHandler_cgo(received_message * msg); // Forward declaration.
*/
import "C"
// Arrange that main.main runs on main thread.
func init() {
runtime.LockOSThread()
}
func poll() {
for {
fmt.Println("POLLING")
time.Sleep(1 * time.Microsecond)
C.nimbus_poll()
}
}
//export receiveHandler
func receiveHandler(msg *C.received_message) {
receivedMsg := C.GoBytes(unsafe.Pointer(msg.decoded), C.int(msg.decodedLen))
fmt.Printf("[nim-status] received message %s\n", string(receivedMsg))
}
func Start() {
C.NimMain()
fmt.Println("[nim-status] Start Nimbus")
if C.nimbus_start(30306, true, false, 0.002, nil, false) == false {
panic("Can't start nimbus")
}
peer1 := C.CString("enode://2d3e27d7846564f9b964308038dfadd4076e4373ac938e020708ad8819fd4fd90e5eb8314140768f782db704cb313b60707b968f8b61108a6fecd705b041746d@192.168.0.33:30303")
defer C.free(unsafe.Pointer(peer1))
C.nimbus_add_peer(peer1)
}
func StatusListenAndPost(channel string) {
fmt.Println("[nim-status] Status Public ListenAndPost")
channelC := C.CString(channel)
defer C.free(unsafe.Pointer(channelC))
C.nimbus_join_public_chat(channelC,
(C.received_msg_handler)(unsafe.Pointer(C.receiveHandler_cgo)))
i := 0
for {
//fmt.Println("[nim-status] ListenAndPost (post @i==1000) i= ", i)
C.nimbus_poll()
t := time.Now().UnixNano() / int64(time.Millisecond)
i = i + 1
time.Sleep(1 * time.Microsecond)
message := fmt.Sprintf("[\"~#c4\",[\"Message:%d\",\"text/plain\",\"~:public-group-user-message\",%d,%d,[\"^ \",\"~:chat-id\",\"%s\",\"~:text\",\"Message:%d\"]]]", i, t*100, t, channel, i)
if i%1000 == 0 {
fmt.Println("[nim-status] posting", message)
messageC := C.CString(message)
C.nimbus_post_public(channelC, messageC)
C.free(unsafe.Pointer(messageC))
}
}
}
func main() {
fmt.Println("Hi main")
nprocs := runtime.GOMAXPROCS(0)
fmt.Println("GOMAXPROCS ", nprocs)
Start()
StatusListenAndPost("status-test-go")
}

View File

@ -1,138 +0,0 @@
package main
import (
"encoding/hex"
"fmt"
"runtime"
"time"
"unsafe"
)
/*
#include <stdlib.h>
#include <stdbool.h>
// Passing "-lnimbus" to the Go linker through "-extldflags" is not enough. We need it in here, for some reason.
#cgo LDFLAGS: -Wl,-rpath,'$ORIGIN' -L${SRCDIR}/../build -lnimbus
#include "libnimbus.h"
void receiveHandler_cgo(received_message * msg, void* udata); // Forward declaration.
*/
import "C"
// Arrange that main.main runs on main thread.
func init() {
runtime.LockOSThread()
}
//export receiveHandler
func receiveHandler(msg *C.received_message, udata unsafe.Pointer) {
receivedMsg := C.GoBytes(unsafe.Pointer(msg.decoded), C.int(msg.decodedLen))
fmt.Printf("[nim-status] received message %s\n", string(receivedMsg))
if msg.source != nil {
source := C.GoBytes(unsafe.Pointer(msg.source), 64)
fmt.Printf("[nim-status] source public key %x\n", string(source))
}
msgCount := (*int)(udata)
*msgCount += 1
fmt.Printf("[nim-status] message count %d\n", *msgCount)
}
func Start() {
C.NimMain()
fmt.Println("[nim-status] Start Nimbus")
privKeyHex := "a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617"
data, err := hex.DecodeString(privKeyHex)
if err != nil {
panic(err)
}
privKey := (*C.uint8_t)(C.CBytes(data))
defer C.free(unsafe.Pointer(privKey))
if C.nimbus_start(30306, true, false, 0.002, privKey, false) == false {
panic("Can't start nimbus")
}
}
func StatusListenAndPost(channel string) {
fmt.Println("[nim-status] Status Public ListenAndPost")
channelC := C.CString(channel)
defer C.free(unsafe.Pointer(channelC))
tmp := C.malloc(C.size_t(C.ID_LEN))
if C.nimbus_add_symkey_from_password(channelC, (*C.uint8_t)(tmp)) == false {
C.free(unsafe.Pointer(tmp))
panic("Cannot create symmetric key")
}
// No need to do this back and forth GO <-> C, just showing how it might work
// in implementations (when wrapped in calls passing Go Bytes or Strings).
symKeyId := C.GoBytes(tmp, C.ID_LEN)
C.free(unsafe.Pointer(tmp))
symKeyIdC := (*C.uint8_t)(C.CBytes(symKeyId))
defer C.free(unsafe.Pointer(symKeyIdC))
tmp = C.malloc(C.size_t(C.ID_LEN))
if C.nimbus_new_keypair((*C.uint8_t)(tmp)) == false {
C.free(unsafe.Pointer(tmp))
panic("Cannot create asymmetric keypair")
}
// No need to do this back and forth GO <-> C, just showing how it might work
// in implementations (when wrapped in calls passing Go Bytes or Strings).
asymKeyId := C.GoBytes(tmp, C.ID_LEN)
C.free(unsafe.Pointer(tmp))
asymKeyIdC := (*C.uint8_t)(C.CBytes(asymKeyId))
defer C.free(unsafe.Pointer(asymKeyIdC))
var msgCount int = 0
options := C.filter_options{symKeyID: symKeyIdC,
minPow: 0.002,
topic: C.nimbus_channel_to_topic(channelC).topic}
tmp = C.malloc(C.size_t(C.ID_LEN))
if C.nimbus_subscribe_filter(&options,
(C.received_msg_handler)(unsafe.Pointer(C.receiveHandler_cgo)),
unsafe.Pointer(&msgCount), (*C.uint8_t)(tmp)) == false {
C.free(unsafe.Pointer(tmp))
panic("Cannot subscribe filter")
}
filterId := C.GoBytes(tmp, C.ID_LEN)
C.free(unsafe.Pointer(tmp))
fmt.Printf("[nim-status] filter subscribed, id: %s\n",
hex.EncodeToString(filterId))
postMessage := C.post_message{symKeyID: symKeyIdC,
sourceID: asymKeyIdC,
ttl: 20,
topic: C.nimbus_channel_to_topic(channelC).topic,
powTarget: 0.002,
powTime: 1.0}
i := 0
for {
C.nimbus_poll()
t := time.Now().UnixNano() / int64(time.Millisecond)
i = i + 1
time.Sleep(1 * time.Microsecond)
message := fmt.Sprintf("[\"~#c4\",[\"Message:%d\",\"text/plain\",\"~:public-group-user-message\",%d,%d,[\"^ \",\"~:chat-id\",\"%s\",\"~:text\",\"Message:%d\"]]]", i, t*100, t, channel, i)
if i%1000 == 0 {
fmt.Printf("[nim-status] posting msg number %d: %s\n", msgCount, message)
postMessage.payload = (*C.uint8_t)(C.CBytes([]byte(message)))
postMessage.payloadLen = (C.size_t)(len([]byte(message)))
defer C.free(unsafe.Pointer(postMessage.payload))
if C.nimbus_post(&postMessage) == false {
fmt.Println("[nim-status] message could not be added to queue")
}
}
}
}
func main() {
nprocs := runtime.GOMAXPROCS(0)
fmt.Println("GOMAXPROCS ", nprocs)
Start()
StatusListenAndPost("status-test-go")
}