using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Serialization; namespace Waku { public class Config { public string? host { get; set; } public int? port { get; set; } public string? advertiseAddr { get; set; } public string? nodeKey { get; set; } public int? keepAliveInterval { get; set; } public bool? relay { get; set; } public int? minPeersToPublish {get; set; } public bool? enableFilter {get; set; } } public enum EventType { Unknown, Message } public class Message { public byte[] payload { get; set; } = new byte[0]; public string contentTopic { get; set; } = ""; public int version { get; set; } = 0; public long? timestamp { get; set; } } public class DecodedPayload { public string? pubkey { get; set; } public string? signature { get; set; } public byte[] data { get; set; } = new byte[0]; public byte[] padding { get; set; } = new byte[0]; } public class Peer { public string peerID { get; set; } = ""; public bool connected { get; set; } = false; public IList protocols { get; set; } = new List(); public IList addrs { get; set; } = new List(); } public class Event { public EventType type { get; set; } = EventType.Unknown; } public class MessageEventData { public string messageID { get; set; } = ""; public string pubsubTopic { get; set; } = Utils.DefaultPubsubTopic(); public Message wakuMessage { get; set; } = new Message(); } public class MessageEvent : Event { [JsonPropertyName("event")] public MessageEventData data { get; set; } = new(); } public class ContentFilter { public ContentFilter(string contentTopic) { this.contentTopic = contentTopic; } public string contentTopic { get; set; } = ""; } public class Cursor { public byte[] digest { get; set; } = new byte[0]; public string pubsubTopic { get; set; } = Utils.DefaultPubsubTopic(); public long receiverTime { get; set; } = 0; public long senderTime { get; set; } = 0; } public class PagingOptions { public PagingOptions(int pageSize, Cursor? cursor, bool forward) { this.pageSize = pageSize; this.cursor = cursor; this.forward = forward; } public int pageSize { get; set; } = 100; public Cursor? cursor { get; set; } = new(); public bool forward { get; set; } = true; } public class StoreQuery { public string? pubsubTopic { get; set; } = Utils.DefaultPubsubTopic(); public long? startTime { get; set; } public long? endTime { get; set; } public IList? contentFilters { get; set; } public PagingOptions? pagingOptions { get; set; } } public class StoreResponse { public IList messages { get; set; } = new List(); public PagingOptions? pagingInfo { get; set; } } public class FilterSubscription { public IList contentFilters { get; set; } = new List(); public string? topic { get; set; } } public class Node { private bool _running; private static SignalHandlerDelegate? _signalHandler; [DllImport(Constants.dllName)] internal static extern IntPtr waku_new(string configJSON); /// /// Initialize a go-waku node. /// /// Waku.Config containing the options used to initialize a go-waku node. It can be NULL to use defaults. All the keys from the configuration are optional /// The node id public Node(Config? c = null) { string jsonString = "{}"; if (c != null) { jsonString = JsonSerializer.Serialize(c); } IntPtr ptr = waku_new(jsonString); Response.HandleResponse(ptr); SetEventCallback(); } ~Node() { if (_running) { Stop(); } } private static void DefaultEventHandler(IntPtr signalJSON) { if (_signalHandler != null) { string signalStr = Response.PtrToStringUtf8(signalJSON, false); JsonSerializerOptions options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); Event? evt = JsonSerializer.Deserialize(signalStr, options); if (evt == null) return; switch (evt.type) { case EventType.Message: Event? msgEvt = JsonSerializer.Deserialize(signalStr, options); if (msgEvt != null) _signalHandler(msgEvt); break; } } } [UnmanagedFunctionPointer(CallingConvention.StdCall)] internal delegate void WakuEventHandlerDelegate(IntPtr signalJSON); [DllImport(Constants.dllName, CallingConvention = CallingConvention.Cdecl)] internal static extern void waku_set_event_callback(WakuEventHandlerDelegate cb); private void SetEventCallback() { waku_set_event_callback(DefaultEventHandler); } public delegate void SignalHandlerDelegate(Event evt); /// /// Register callback to act as signal handler and receive application signals which are used to react to asyncronous events in waku. /// SignalHandler with the signature `void SignalHandler(Waku.Event evt){ }` public void SetEventHandler(SignalHandlerDelegate h) { _signalHandler = h; } [DllImport(Constants.dllName)] internal static extern IntPtr waku_start(); /// /// Initialize a go-waku node mounting all the protocols that were enabled during the waku node instantiation. /// public void Start() { if(_running) { return } IntPtr ptr = waku_start(); Response.HandleResponse(ptr); _running = true; } [DllImport(Constants.dllName)] internal static extern IntPtr waku_stop(); /// /// Stops a go-waku node /// public void Stop() { if(!_running) { return } IntPtr ptr = waku_stop(); Response.HandleResponse(ptr); _running = false; } [DllImport(Constants.dllName)] internal static extern IntPtr waku_peerid(); /// /// Obtain the peer ID of the go-waku node. /// /// The base58 encoded peer Id public string PeerId() { IntPtr ptr = waku_peerid(); return Response.HandleResponse(ptr, "could not obtain the peerId"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_peer_cnt(); /// /// Obtain number of connected peers /// /// The number of peers connected to this node public int PeerCnt() { IntPtr ptr = waku_start(); return Response.HandleResponse(ptr, "could not obtain the peer count"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_listen_addresses(); /// /// Obtain the multiaddresses the wakunode is listening to /// /// List of multiaddresses public IList ListenAddresses() { IntPtr ptr = waku_listen_addresses(); return Response.HandleListResponse(ptr, "could not obtain the addresses"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_add_peer(string address, string protocolId); /// /// Add node multiaddress and protocol to the wakunode peerstore /// /// multiaddress of the peer being added /// protocol supported by the peer /// Base58 encoded peer Id public string AddPeer(string address, string protocolId) { IntPtr ptr = waku_add_peer(address, protocolId); return Response.HandleResponse(ptr, "could not obtain the peer id"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_connect(string address, int ms); /// /// Connect to peer at multiaddress. /// /// multiaddress of the peer being dialed /// max duration in milliseconds this function might take to execute. If the function execution takes longer than this value, the execution will be canceled and an error returned. Use 0 for unlimited duration public void Connect(string address, int ms = 0) { IntPtr ptr = waku_connect(address, ms); Response.HandleResponse(ptr); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_connect_peerid( string peerId, int ms); /// /// Connect to peer using peerID. /// /// peerID to dial. The peer must be already known, added with `AddPeer` /// max duration in milliseconds this function might take to execute. If the function execution takes longer than this value, the execution will be canceled and an error returned. Use 0 for unlimited duration public void ConnectPeerId(string peerId, int ms = 0) { IntPtr ptr = waku_connect_peerid(peerId, ms); Response.HandleResponse(ptr); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_disconnect(string peerID); /// /// Close connection to a known peer by peerID /// /// Base58 encoded peer ID to disconnect public void Disconnect(string peerId) { IntPtr ptr = waku_disconnect(peerId); Response.HandleResponse(ptr); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_relay_publish(string messageJSON, string? topic, int ms); /// /// Publish a message using waku relay. /// /// Message to broadcast /// Pubsub topic. Set to `null` to use the default pubsub topic /// If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned /// public string RelayPublish(Message msg, string? topic = null, int ms = 0) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_relay_publish(jsonMsg, topic, ms); return Response.HandleResponse(ptr, "could not obtain the message id"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_lightpush_publish(string messageJSON, string? topic, string? peerID, int ms); /// /// Publish a message using waku lightpush. /// /// Message to broadcast /// Pubsub topic. Set to `null` to use the default pubsub topic /// ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node /// If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned /// public string LightpushPublish(Message msg, string? topic = null, string? peerID = null, int ms = 0) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_lightpush_publish(jsonMsg, topic, peerID, ms); return Response.HandleResponse(ptr, "could not obtain the message id"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_relay_publish_enc_asymmetric(string messageJSON, string? topic, string publicKey, string? optionalSigningKey, int ms); /// /// Publish a message encrypted with an secp256k1 public key using waku relay. /// /// Message to broadcast /// Secp256k1 public key /// Optional secp256k1 private key for signing the message /// Pubsub topic. Set to `null` to use the default pubsub topic /// If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned /// public string RelayPublishEncodeAsymmetric(Message msg, string publicKey, string? optionalSigningKey = null, string? topic = null, int ms = 0) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_relay_publish_enc_asymmetric(jsonMsg, topic, publicKey, optionalSigningKey, ms); return Response.HandleResponse(ptr, "could not obtain the message id"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_lightpush_publish_enc_asymmetric(string messageJSON, string? topic, string? peerID, string publicKey, string? optionalSigningKey, int ms); /// /// Publish a message encrypted with an secp256k1 public key using waku lightpush. /// /// Message to broadcast /// Secp256k1 public key /// Optional secp256k1 private key for signing the message /// Pubsub topic. Set to `null` to use the default pubsub topic /// ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node /// If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned /// public string LightpushPublishEncodeAsymmetric(Message msg, string publicKey, string? optionalSigningKey = null, string? topic = null, string? peerID = null, int ms = 0) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_lightpush_publish_enc_asymmetric(jsonMsg, topic, peerID, publicKey, optionalSigningKey, ms); return Response.HandleResponse(ptr, "could not obtain the message id"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_relay_publish_enc_symmetric(string messageJSON, string? topic, string symmetricKey, string? optionalSigningKey, int ms); /// /// Publish a message encrypted with a 32 bytes symmetric key using waku relay. /// /// Message to broadcast /// 32 byte hex string containing a symmetric key /// Optional secp256k1 private key for signing the message /// Pubsub topic. Set to `null` to use the default pubsub topic /// If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned /// public string RelayPublishEncodeSymmetric(Message msg, string symmetricKey, string? optionalSigningKey = null, string? topic = null, int ms = 0) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_relay_publish_enc_symmetric(jsonMsg, topic, symmetricKey, optionalSigningKey, ms); return Response.HandleResponse(ptr, "could not obtain the message id"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_lightpush_publish_enc_symmetric(string messageJSON, string? topic, string? peerID, string symmetricKey, string? optionalSigningKey, int ms); /// /// Publish a message encrypted with a 32 bytes symmetric key using waku lightpush. /// /// Message to broadcast /// 32 byte hex string containing a symmetric key /// Optional secp256k1 private key for signing the message /// Pubsub topic. Set to `null` to use the default pubsub topic /// ID of a peer supporting the lightpush protocol. Use NULL to automatically select a node /// If ms is greater than 0, the broadcast of the message must happen before the timeout (in milliseconds) is reached, or an error will be returned /// public string RelayPublishEncodeSymmetric(Message msg, string symmetricKey, string? optionalSigningKey = null, string? topic = null, string? peerID = null, int ms = 0) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_lightpush_publish_enc_symmetric(jsonMsg, topic, peerID, symmetricKey, optionalSigningKey, ms); return Response.HandleResponse(ptr, "could not obtain the message id"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_decode_symmetric(string messageJSON, string symmetricKey); /// /// Decode a waku message using a symmetric key /// /// Message to decode /// Symmetric key used to decode the message /// DecodedPayload containing the decrypted message, padding, public key and signature (if available) public DecodedPayload DecodeSymmetric(Message msg, string symmetricKey) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_decode_symmetric(jsonMsg, symmetricKey); return Response.HandleDecodedPayloadResponse(ptr, "could not decode message"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_decode_asymmetric(string messageJSON, string privateKey); /// /// Decode a waku message using an asymmetric key /// /// Message to decode /// Secp256k1 private key used to decode the message /// DecodedPayload containing the decrypted message, padding, public key and signature (if available) public DecodedPayload DecodeAsymmetric(Message msg, string privateKey) { string jsonMsg = JsonSerializer.Serialize(msg); IntPtr ptr = waku_decode_asymmetric(jsonMsg, privateKey); return Response.HandleDecodedPayloadResponse(ptr, "could not decode message"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_relay_enough_peers(string topic); /// /// Determine if there are enough peers to publish a message on a topic. /// /// pubsub topic to verify. Use NULL to verify the number of peers in the default pubsub topic /// boolean indicating if there are enough peers or not public bool RelayEnoughPeers(string topic = null) { IntPtr ptr = waku_relay_enough_peers(topic); return Response.HandleResponse(ptr, "could not determine if there are enough peers"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_relay_subscribe(string? topic); /// /// Subscribe to a WakuRelay topic to receive messages. /// /// Pubsub topic to subscribe to. Use NULL for subscribing to the default pubsub topic /// Subscription Id public void RelaySubscribe(string? topic = null) { IntPtr ptr = waku_relay_subscribe(topic); Response.HandleResponse(ptr); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_relay_unsubscribe(string? topic); /// /// Closes the pubsub subscription to a pubsub topic. /// /// Pubsub topic to unsubscribe. Use NULL for unsubscribe from the default pubsub topic public void RelayUnsubscribe(string? topic = null) { IntPtr ptr = waku_relay_unsubscribe(topic); Response.HandleResponse(ptr); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_peers(); /// /// Get Peers /// /// Retrieve list of peers and their supported protocols public IList Peers() { IntPtr ptr = waku_peers(); return Response.HandleListResponse(ptr, "could not obtain the peers"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_store_query(string queryJSON, string? peerID, int ms); /// /// Query message history /// /// Query /// PeerID to ask the history from. Use NULL to automatically select a peer /// If ms is greater than 0, the response must be received before the timeout (in milliseconds) is reached, or an error will be returned /// Response containing the messages and cursor for pagination. Use the cursor in further queries to retrieve more results public StoreResponse StoreQuery(StoreQuery query, string? peerID = null, int ms = 0) { string queryJSON = JsonSerializer.Serialize(query); IntPtr ptr = waku_store_query(queryJSON, peerID, ms); return Response.HandleStoreResponse(ptr, "could not extract query response"); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_filter_subscribe(string filterJSON, string? peerID, int ms); /// /// Creates a subscription in a lightnode for messages /// /// Filter criteria /// PeerID to subscribe to /// If ms is greater than 0, the subscription must be done before the timeout (in milliseconds) is reached, or an error will be returned public FilterSubscribe(FilterSubscription filter, string? peerID = null, int ms = 0) { string filterJSON = JsonSerializer.Serialize(filter); IntPtr ptr = waku_filter_subscribe(filterJSON, peerID, ms); Response.HandleResponse(ptr); } [DllImport(Constants.dllName)] internal static extern IntPtr waku_filter_unsubscribe(string filterJSON, int ms); /// /// Removes subscriptions in a light node /// /// Filter criteria /// If ms is greater than 0, the unsubscription must be done before the timeout (in milliseconds) is reached, or an error will be returned public FilterUnsubscribe(FilterSubscription filter, int ms = 0) { string filterJSON = JsonSerializer.Serialize(filter); IntPtr ptr = waku_filter_unsubscribe(filterJSON, ms); Response.HandleResponse(ptr); } } }