Trying out some Whisper RPC alike API
This commit is contained in:
parent
ef540fdb14
commit
23baeaa15d
2
Makefile
2
Makefile
|
@ -87,6 +87,8 @@ wrappers: | build deps libnimbus.so go-checks
|
||||||
$(CC) wrappers/wrapper_example.c -Wl,-rpath,'$$ORIGIN' -Lbuild -lnimbus -lm -g -o build/C_wrapper_example
|
$(CC) wrappers/wrapper_example.c -Wl,-rpath,'$$ORIGIN' -Lbuild -lnimbus -lm -g -o build/C_wrapper_example
|
||||||
echo -e $(BUILD_MSG) "build/go_wrapper_example" && \
|
echo -e $(BUILD_MSG) "build/go_wrapper_example" && \
|
||||||
go build -linkshared -o build/go_wrapper_example wrappers/wrapper_example.go wrappers/cfuncs.go
|
go build -linkshared -o build/go_wrapper_example wrappers/wrapper_example.go wrappers/cfuncs.go
|
||||||
|
echo -e $(BUILD_MSG) "build/go_wrapper_whisper_example" && \
|
||||||
|
go build -linkshared -o build/go_wrapper_whisper_example wrappers/wrapper_whisper_example.go wrappers/cfuncs.go
|
||||||
|
|
||||||
libnimbus.a: | build deps
|
libnimbus.a: | build deps
|
||||||
echo -e $(BUILD_MSG) "build/$@" && \
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
|
|
|
@ -9,8 +9,9 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t* decoded;
|
int8_t* decoded;
|
||||||
size_t decodedLen;
|
size_t decodedLen;
|
||||||
|
uint8_t source[64];
|
||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
uint32_t ttl;
|
uint32_t ttl;
|
||||||
uint8_t topic[4];
|
uint8_t topic[4];
|
||||||
|
@ -18,6 +19,30 @@ typedef struct {
|
||||||
uint8_t hash[32];
|
uint8_t hash[32];
|
||||||
} received_message;
|
} received_message;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* symKeyID;
|
||||||
|
const char* privateKeyID;
|
||||||
|
uint8_t sig[64];
|
||||||
|
double minPow;
|
||||||
|
uint8_t topic[4];
|
||||||
|
} filter_options;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* symKeyID;
|
||||||
|
uint8_t pubKey[64];
|
||||||
|
const char* sig;
|
||||||
|
uint32_t ttl;
|
||||||
|
uint8_t topic[4];
|
||||||
|
char* payload;
|
||||||
|
char* padding;
|
||||||
|
double powTime;
|
||||||
|
double powTarget;
|
||||||
|
} post_message;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t topic[4];
|
||||||
|
} topic;
|
||||||
|
|
||||||
typedef void (*received_msg_handler)(received_message* msg);
|
typedef void (*received_msg_handler)(received_message* msg);
|
||||||
|
|
||||||
/** Initialize Nim and the status library */
|
/** Initialize Nim and the status library */
|
||||||
|
@ -38,6 +63,19 @@ void nimbus_poll();
|
||||||
void nimbus_post(const char* channel, const char* payload);
|
void nimbus_post(const char* channel, const char* payload);
|
||||||
void nimbus_subscribe(const char* channel, received_msg_handler msg);
|
void nimbus_subscribe(const char* channel, received_msg_handler msg);
|
||||||
|
|
||||||
|
/* Whisper API */
|
||||||
|
|
||||||
|
topic nimbus_string_to_topic(const char* s);
|
||||||
|
/* Generate asymmetric keypair */
|
||||||
|
const char* nimbus_new_keypair();
|
||||||
|
/* Generate symmetric key from password */
|
||||||
|
const char* nimbus_add_symkey_from_password(const char* password);
|
||||||
|
/* Subscribe to given filter */
|
||||||
|
void nimbus_whisper_subscribe(filter_options* filter_options,
|
||||||
|
received_msg_handler msg);
|
||||||
|
/* Post Whisper message */
|
||||||
|
void nimbus_whisper_post(post_message* msg);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Stratus
|
# Nimbus
|
||||||
# (c) Copyright 2018
|
# (c) Copyright 2018
|
||||||
# Status Research & Development GmbH
|
# Status Research & Development GmbH
|
||||||
#
|
#
|
||||||
|
@ -8,26 +8,68 @@
|
||||||
# MIT license (LICENSE-MIT)
|
# MIT license (LICENSE-MIT)
|
||||||
|
|
||||||
import
|
import
|
||||||
chronos, chronicles, nimcrypto/[utils, hmac, pbkdf2, hash],
|
chronos, chronicles, nimcrypto/[utils, hmac, pbkdf2, hash], tables,
|
||||||
eth/[keys, rlp, p2p], eth/p2p/rlpx_protocols/whisper_protocol,
|
eth/[keys, rlp, p2p], eth/p2p/rlpx_protocols/whisper_protocol,
|
||||||
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes]
|
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes]
|
||||||
|
|
||||||
|
from stew/byteutils import hexToSeqByte, hexToByteArray
|
||||||
|
|
||||||
|
# TODO: If we really want/need this type of API for the keys, put it somewhere
|
||||||
|
# seperate as it is the same code for Whisper RPC
|
||||||
|
type
|
||||||
|
WhisperKeys* = ref object
|
||||||
|
asymKeys*: Table[string, KeyPair]
|
||||||
|
symKeys*: Table[string, SymKey]
|
||||||
|
|
||||||
|
KeyGenerationError = object of CatchableError
|
||||||
|
|
||||||
|
proc newWhisperKeys*(): WhisperKeys =
|
||||||
|
new(result)
|
||||||
|
result.asymKeys = initTable[string, KeyPair]()
|
||||||
|
result.symKeys = initTable[string, SymKey]()
|
||||||
|
|
||||||
|
# TODO: again, lots of overlap with Nimbus whisper RPC here, however not all
|
||||||
|
# the same due to type conversion (no use of Option and such)
|
||||||
type
|
type
|
||||||
CReceivedMessage* = object
|
CReceivedMessage* = object
|
||||||
decoded*: ptr byte
|
decoded*: ptr byte
|
||||||
decodedLen*: csize
|
decodedLen*: csize
|
||||||
|
source*: PublicKey
|
||||||
timestamp*: uint32
|
timestamp*: uint32
|
||||||
ttl*: uint32
|
ttl*: uint32
|
||||||
topic*: Topic
|
topic*: Topic
|
||||||
pow*: float64
|
pow*: float64
|
||||||
hash*: Hash
|
hash*: Hash
|
||||||
|
|
||||||
|
CFilterOptions* = object
|
||||||
|
symKeyID*: cstring
|
||||||
|
privateKeyID*: cstring
|
||||||
|
sig*: PublicKey
|
||||||
|
minPow*: float64
|
||||||
|
topic*: Topic # lets go with one topic for now
|
||||||
|
|
||||||
|
CPostMessage* = object
|
||||||
|
symKeyID*: cstring
|
||||||
|
pubKey*: PublicKey
|
||||||
|
sig*: cstring
|
||||||
|
ttl*: uint32
|
||||||
|
topic*: Topic
|
||||||
|
payload*: cstring
|
||||||
|
padding*: cstring
|
||||||
|
powTime*: float64
|
||||||
|
powTarget*: float64
|
||||||
|
|
||||||
|
CTopic* = object
|
||||||
|
topic*: Topic
|
||||||
|
|
||||||
proc `$`*(digest: SymKey): string =
|
proc `$`*(digest: SymKey): string =
|
||||||
for c in digest: result &= hexChar(c.byte)
|
for c in digest: result &= hexChar(c.byte)
|
||||||
|
|
||||||
# Don't do this at home, you'll never get rid of ugly globals like this!
|
# Don't do this at home, you'll never get rid of ugly globals like this!
|
||||||
var
|
var
|
||||||
node: EthereumNode
|
node: EthereumNode
|
||||||
|
# You will only add more instead!
|
||||||
|
let whisperKeys = newWhisperKeys()
|
||||||
|
|
||||||
# TODO: Return filter ID if we ever want to unsubscribe
|
# TODO: Return filter ID if we ever want to unsubscribe
|
||||||
proc subscribeChannel(
|
proc subscribeChannel(
|
||||||
|
@ -150,3 +192,125 @@ proc nimbus_add_peer(nodeId: cstring) {.exportc.} =
|
||||||
var whisperNode = newNode(whisperENode)
|
var whisperNode = newNode(whisperENode)
|
||||||
|
|
||||||
asyncCheck node.peerPool.connectToNode(whisperNode)
|
asyncCheck node.peerPool.connectToNode(whisperNode)
|
||||||
|
|
||||||
|
# Whisper API (Similar to Whisper RPC API)
|
||||||
|
# Mostly an example for now, lots of things to fix if continued like this.
|
||||||
|
|
||||||
|
proc nimbus_string_to_topic(s: cstring): CTopic {.exportc.} =
|
||||||
|
let hash = digest(keccak256, $s)
|
||||||
|
for i in 0..<4:
|
||||||
|
result.topic[i] = hash.data[i]
|
||||||
|
|
||||||
|
proc nimbus_new_keypair(): cstring {.exportc.} =
|
||||||
|
result = generateRandomID()
|
||||||
|
whisperKeys.asymKeys.add($result, newKeyPair())
|
||||||
|
|
||||||
|
proc nimbus_add_keypair(key: PrivateKey): cstring = discard
|
||||||
|
proc nimbus_delete_keypair(id: cstring) = discard
|
||||||
|
proc nimbus_add_symkey(key: SymKey): cstring = discard
|
||||||
|
|
||||||
|
proc nimbus_add_symkey_from_password(password: cstring): cstring {.exportc.} =
|
||||||
|
setupForeignThreadGc()
|
||||||
|
|
||||||
|
var ctx: HMAC[sha256]
|
||||||
|
var symKey: SymKey
|
||||||
|
if pbkdf2(ctx, $password, "", 65356, symKey) != sizeof(SymKey):
|
||||||
|
raise newException(KeyGenerationError, "Failed generating key")
|
||||||
|
|
||||||
|
result = generateRandomID()
|
||||||
|
|
||||||
|
whisperKeys.symKeys.add($result, symKey)
|
||||||
|
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
|
||||||
|
proc nimbus_delete_symkey(id: cstring) = discard
|
||||||
|
|
||||||
|
proc nimbus_whisper_post(message: ptr CPostMessage) {.exportc.} =
|
||||||
|
setupForeignThreadGc()
|
||||||
|
|
||||||
|
var
|
||||||
|
sigPrivKey: Option[PrivateKey]
|
||||||
|
asymKey: Option[PublicKey]
|
||||||
|
symKey: Option[SymKey]
|
||||||
|
padding: Option[Bytes]
|
||||||
|
payload: Bytes
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# - check if there is a asymKey and/or pubKey or do we not care?
|
||||||
|
# - fail if payload is nil?
|
||||||
|
# - error handling on key access
|
||||||
|
|
||||||
|
# TODO: How to arrange optional pubkey?
|
||||||
|
# - Ptr with check on Nil? (or just cstring?)
|
||||||
|
# - Convert also Options?
|
||||||
|
# - Or just add different API calls?
|
||||||
|
# asymKey = some(message.pubKey)
|
||||||
|
asymKey = none(PublicKey)
|
||||||
|
|
||||||
|
if not message.symKeyID.isNil():
|
||||||
|
symKey = some(whisperKeys.symKeys[$message.symKeyID])
|
||||||
|
if not message.sig.isNil():
|
||||||
|
sigPrivKey = some(whisperKeys.asymKeys[$message.sig].seckey)
|
||||||
|
if not message.payload.isNil():
|
||||||
|
# TODO: Is this cast OK?
|
||||||
|
payload = cast[Bytes]($message.payload)
|
||||||
|
# payload = cast[Bytes](@($message.payload))
|
||||||
|
if not message.padding.isNil():
|
||||||
|
padding = some(cast[Bytes]($message.padding))
|
||||||
|
|
||||||
|
# TODO: Handle error case
|
||||||
|
discard node.postMessage(asymKey,
|
||||||
|
symKey,
|
||||||
|
sigPrivKey,
|
||||||
|
ttl = message.ttl,
|
||||||
|
topic = message.topic,
|
||||||
|
payload = payload,
|
||||||
|
padding = padding,
|
||||||
|
powTime = message.powTime,
|
||||||
|
powTarget = message.powTarget)
|
||||||
|
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
|
||||||
|
proc nimbus_whisper_subscribe(options: ptr CFilterOptions,
|
||||||
|
handler: proc (msg: ptr CReceivedMessage)
|
||||||
|
{.gcsafe, cdecl.}) {.exportc.} =
|
||||||
|
setupForeignThreadGc()
|
||||||
|
|
||||||
|
# TODO: same remarks as in nimbus_whisper_post()
|
||||||
|
|
||||||
|
var filter: Filter
|
||||||
|
filter.src = none(PublicKey)
|
||||||
|
if not options.symKeyID.isNil():
|
||||||
|
# if options.symKeyID.len() > 0:
|
||||||
|
filter.symKey= some(whisperKeys.symKeys[$options.symKeyID])
|
||||||
|
if not options.privateKeyID.isNil():
|
||||||
|
filter.privateKey= some(whisperKeys.asymKeys[$options.privateKeyID].seckey)
|
||||||
|
filter.powReq = options.minPow
|
||||||
|
filter.topics = @[options.topic]
|
||||||
|
filter.allowP2P = false
|
||||||
|
|
||||||
|
if handler.isNil:
|
||||||
|
discard node.subscribeFilter(filter, nil)
|
||||||
|
return
|
||||||
|
|
||||||
|
proc c_handler(msg: ReceivedMessage) {.gcsafe.} =
|
||||||
|
var cmsg = CReceivedMessage(
|
||||||
|
decoded: unsafeAddr msg.decoded.payload[0],
|
||||||
|
decodedLen: csize msg.decoded.payload.len(),
|
||||||
|
timestamp: msg.timestamp,
|
||||||
|
ttl: msg.ttl,
|
||||||
|
topic: msg.topic,
|
||||||
|
pow: msg.pow,
|
||||||
|
hash: msg.hash
|
||||||
|
)
|
||||||
|
|
||||||
|
if msg.decoded.src.isSome():
|
||||||
|
cmsg.source = msg.decoded.src.get()
|
||||||
|
|
||||||
|
handler(addr cmsg)
|
||||||
|
|
||||||
|
discard node.subscribeFilter(filter, c_handler)
|
||||||
|
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
|
||||||
|
proc nimbus_whisper_unsubscribe(id: cstring) = discard
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -Wl,-rpath,'$ORIGIN' -L${SRCDIR}/../build -lnimbus -lm
|
||||||
|
#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) {
|
||||||
|
fmt.Printf("[nim-status] received message %s\n",
|
||||||
|
C.GoStringN((*C.char)(msg.decoded), (C.int)(msg.decodedLen)) )
|
||||||
|
fmt.Printf("[nim-status] source public key %x\n", msg.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
C.NimMain()
|
||||||
|
fmt.Println("[nim-status] Start Nimbus")
|
||||||
|
C.nimbus_start(30306)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StatusListenAndPost(channel string) {
|
||||||
|
fmt.Println("[nim-status] Status Public ListenAndPost")
|
||||||
|
|
||||||
|
// TODO: free the CStrings?
|
||||||
|
// TODO: Is this doing a copy or not? If not, shouldn't we see issues when the
|
||||||
|
// nim GC kicks in?
|
||||||
|
symKeyId := C.GoString(C.nimbus_add_symkey_from_password(C.CString(channel)))
|
||||||
|
asymKeyId := C.GoString(C.nimbus_new_keypair())
|
||||||
|
|
||||||
|
options := C.filter_options{symKeyID: C.CString(symKeyId),
|
||||||
|
minPow: 0.002,
|
||||||
|
topic: C.nimbus_string_to_topic(C.CString(channel)).topic}
|
||||||
|
C.nimbus_whisper_subscribe(&options,
|
||||||
|
(C.received_msg_handler)(unsafe.Pointer(C.receiveHandler_cgo)))
|
||||||
|
|
||||||
|
postMessage := C.post_message{symKeyID: C.CString(symKeyId),
|
||||||
|
sig: C.CString(asymKeyId),
|
||||||
|
ttl: 20,
|
||||||
|
topic: C.nimbus_string_to_topic(C.CString(channel)).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.Println("[nim-status] posting", message)
|
||||||
|
postMessage.payload = (C.CString(message))
|
||||||
|
C.nimbus_whisper_post(&postMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
nprocs := runtime.GOMAXPROCS(0)
|
||||||
|
fmt.Println("GOMAXPROCS ", nprocs)
|
||||||
|
|
||||||
|
Start()
|
||||||
|
StatusListenAndPost("status-test-go")
|
||||||
|
}
|
Loading…
Reference in New Issue