fix: content topic validation as per rfc 51 (#874)

* fix: content topic validation as per rfc 51

* chore: update library API's and examples
This commit is contained in:
Prem Chaitanya Prathi 2023-11-08 18:24:24 +05:30 committed by GitHub
parent 43412c9da5
commit 28c0cd5d8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 56 additions and 51 deletions

View File

@ -25,7 +25,7 @@ import (
var log = utils.Logger().Named("basic2") var log = utils.Logger().Named("basic2")
func main() { func main() {
var cTopic, err = protocol.NewContentTopic("basic2", 1, "test", "proto") var cTopic, err = protocol.NewContentTopic("basic2", "1", "test", "proto")
if err != nil { if err != nil {
fmt.Println("Invalid contentTopic") fmt.Println("Invalid contentTopic")
return return

View File

@ -18,8 +18,10 @@ char *bobPubKey =
"0x045eef61a98ba1cf44a2736fac91183ea2bd86e67de20fe4bff467a71249a8a0c05f795d" "0x045eef61a98ba1cf44a2736fac91183ea2bd86e67de20fe4bff467a71249a8a0c05f795d"
"d7f28ced7c15eaa69c89d4212cc4f526ca5e9a62e88008f506d850cccd"; "d7f28ced7c15eaa69c89d4212cc4f526ca5e9a62e88008f506d850cccd";
void on_error(int ret, const char *result, void *user_data) { void on_error(int ret, const char *result, void *user_data)
if (ret == 0) { {
if (ret == 0)
{
return; return;
} }
@ -27,8 +29,10 @@ void on_error(int ret, const char *result, void *user_data) {
exit(1); exit(1);
} }
void on_response(int ret, const char *result, void *user_data) { void on_response(int ret, const char *result, void *user_data)
if (ret != 0) { {
if (ret != 0)
{
printf("function execution failed. Returned code: %d\n", ret); printf("function execution failed. Returned code: %d\n", ret);
exit(1); exit(1);
} }
@ -46,7 +50,8 @@ void on_response(int ret, const char *result, void *user_data) {
strcpy(*data_ref, result); strcpy(*data_ref, result);
} }
void callBack(int ret, const char *signal, void *user_data) { void callBack(int ret, const char *signal, void *user_data)
{
// This callback will be executed each time a new message is received // This callback will be executed each time a new message is received
// Example signal: // Example signal:
@ -65,7 +70,8 @@ void callBack(int ret, const char *signal, void *user_data) {
} }
}*/ }*/
if (ret != 0) { if (ret != 0)
{
printf("function execution failed. Returned code: %d\n", ret); printf("function execution failed. Returned code: %d\n", ret);
exit(1); exit(1);
} }
@ -73,13 +79,15 @@ void callBack(int ret, const char *signal, void *user_data) {
const nx_json *json = nx_json_parse((char *)signal, 0); const nx_json *json = nx_json_parse((char *)signal, 0);
const char *type = nx_json_get(json, "type")->text_value; const char *type = nx_json_get(json, "type")->text_value;
if (strcmp(type, "message") == 0) { if (strcmp(type, "message") == 0)
{
const nx_json *wakuMsgJson = const nx_json *wakuMsgJson =
nx_json_get(nx_json_get(json, "event"), "wakuMessage"); nx_json_get(nx_json_get(json, "event"), "wakuMessage");
const char *contentTopic = const char *contentTopic =
nx_json_get(wakuMsgJson, "contentTopic")->text_value; nx_json_get(wakuMsgJson, "contentTopic")->text_value;
if (strcmp(contentTopic, "/example/1/default/rfc26") == 0) { if (strcmp(contentTopic, "/example/1/default/rfc26") == 0)
{
char *msg = utils_extract_wakumessage_from_signal(wakuMsgJson); char *msg = utils_extract_wakumessage_from_signal(wakuMsgJson);
// Decode a message using asymmetric encryption // Decode a message using asymmetric encryption
@ -109,7 +117,8 @@ void callBack(int ret, const char *signal, void *user_data) {
nx_json_free(json); nx_json_free(json);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[])
{
// Set callback to be executed each time a message is received // Set callback to be executed each time a message is received
waku_set_event_callback(callBack); waku_set_event_callback(callBack);
@ -134,7 +143,7 @@ int main(int argc, char *argv[]) {
// Build a content topic // Build a content topic
char *contentTopic = NULL; char *contentTopic = NULL;
waku_content_topic("example", 1, "default", "rfc26", on_response, waku_content_topic("example", "1", "default", "rfc26", on_response,
(void *)&contentTopic); (void *)&contentTopic);
printf("Content Topic: %s\n", contentTopic); printf("Content Topic: %s\n", contentTopic);
@ -166,14 +175,15 @@ int main(int argc, char *argv[]) {
// waku_store_query(query, NULL, 0, on_response, (void*)&query_result); // waku_store_query(query, NULL, 0, on_response, (void*)&query_result);
// printf("%s\n", query_result); // printf("%s\n", query_result);
char contentFilter[1000]; char contentFilter[1000];
sprintf(contentFilter, sprintf(contentFilter,
"{\"pubsubTopic\":\"%s\",\"contentTopics\":[\"%s\"]}", "{\"pubsubTopic\":\"%s\",\"contentTopics\":[\"%s\"]}",
defaultPubsubTopic, contentTopic); defaultPubsubTopic, contentTopic);
waku_relay_subscribe(contentFilter, on_error, NULL); waku_relay_subscribe(contentFilter, on_error, NULL);
int i = 0; int i = 0;
int version = 1; int version = 1;
while (i < 5) { while (i < 5)
{
i++; i++;
char wakuMsg[1000]; char wakuMsg[1000];

View File

@ -34,7 +34,7 @@ func getFlags() []cli.Flag {
// Defaults // Defaults
options.Fleet = fleetProd options.Fleet = fleetProd
testCT, err := protocol.NewContentTopic("toy-chat", 3, "mingde", "proto") testCT, err := protocol.NewContentTopic("toy-chat", "3", "mingde", "proto")
if err != nil { if err != nil {
panic("invalid contentTopic") panic("invalid contentTopic")
} }

View File

@ -32,7 +32,7 @@ var contractAddress = "0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4"
var keystorePath = "./rlnKeystore.json" var keystorePath = "./rlnKeystore.json"
var keystorePassword = "password" var keystorePassword = "password"
var membershipIndex = uint(0) var membershipIndex = uint(0)
var contentTopic, _ = protocol.NewContentTopic("rln", 1, "test", "proto") var contentTopic, _ = protocol.NewContentTopic("rln", "1", "test", "proto")
var pubsubTopic = protocol.DefaultPubsubTopic{} var pubsubTopic = protocol.DefaultPubsubTopic{}
// ============================================================================ // ============================================================================

View File

@ -194,12 +194,11 @@ func waku_peer_cnt(cb C.WakuCallBack, userData unsafe.Pointer) C.int {
// Create a content topic string according to RFC 23 // Create a content topic string according to RFC 23
// //
//export waku_content_topic //export waku_content_topic
func waku_content_topic(applicationName *C.char, applicationVersion C.uint, contentTopicName *C.char, encoding *C.char, cb C.WakuCallBack, userData unsafe.Pointer) C.int { func waku_content_topic(applicationName *C.char, applicationVersion *C.char, contentTopicName *C.char, encoding *C.char, cb C.WakuCallBack, userData unsafe.Pointer) C.int {
contentTopic, _ := protocol.NewContentTopic(C.GoString(applicationName), uint32(applicationVersion), C.GoString(contentTopicName), C.GoString(encoding)) contentTopic, _ := protocol.NewContentTopic(C.GoString(applicationName), C.GoString(applicationVersion), C.GoString(contentTopicName), C.GoString(encoding))
return onSuccesfulResponse(contentTopic.String(), cb, userData) return onSuccesfulResponse(contentTopic.String(), cb, userData)
} }
// Get the default pubsub topic used in waku2: /waku/2/default-waku/proto // Get the default pubsub topic used in waku2: /waku/2/default-waku/proto
// //
//export waku_default_pubsub_topic //export waku_default_pubsub_topic

View File

@ -73,8 +73,8 @@ func PeerCnt() string {
} }
// ContentTopic creates a content topic string according to RFC 23 // ContentTopic creates a content topic string according to RFC 23
func ContentTopic(applicationName string, applicationVersion int, contentTopicName string, encoding string) string { func ContentTopic(applicationName string, applicationVersion string, contentTopicName string, encoding string) string {
contentTopic, _ := protocol.NewContentTopic(applicationName, uint32(applicationVersion), contentTopicName, encoding) contentTopic, _ := protocol.NewContentTopic(applicationName, applicationVersion, contentTopicName, encoding)
return contentTopic.String() return contentTopic.String()
} }

View File

@ -335,8 +335,8 @@ func PeerCnt() (int, error) {
} }
// ContentTopic creates a content topic string according to RFC 23 // ContentTopic creates a content topic string according to RFC 23
func ContentTopic(applicationName string, applicationVersion int, contentTopicName string, encoding string) string { func ContentTopic(applicationName string, applicationVersion string, contentTopicName string, encoding string) string {
contentTopic, _ := protocol.NewContentTopic(applicationName, uint32(applicationVersion), contentTopicName, encoding) contentTopic, _ := protocol.NewContentTopic(applicationName, applicationVersion, contentTopicName, encoding)
return contentTopic.String() return contentTopic.String()
} }

View File

@ -15,7 +15,7 @@ var ErrInvalidGeneration = errors.New("generation should be a number")
type ContentTopic struct { type ContentTopic struct {
ContentTopicParams ContentTopicParams
ApplicationName string ApplicationName string
ApplicationVersion uint32 ApplicationVersion string
ContentTopicName string ContentTopicName string
Encoding string Encoding string
} }
@ -35,12 +35,13 @@ type ContentTopicOption func(*ContentTopicParams)
// String formats a content topic in string format as per RFC 23. // String formats a content topic in string format as per RFC 23.
func (ct ContentTopic) String() string { func (ct ContentTopic) String() string {
return fmt.Sprintf("/%s/%d/%s/%s", ct.ApplicationName, ct.ApplicationVersion, ct.ContentTopicName, ct.Encoding) return fmt.Sprintf("/%s/%s/%s/%s", ct.ApplicationName, ct.ApplicationVersion, ct.ContentTopicName, ct.Encoding)
} }
// NewContentTopic creates a new content topic based on params specified. // NewContentTopic creates a new content topic based on params specified.
// Returns ErrInvalidGeneration if an unsupported generation is specified. // Returns ErrInvalidGeneration if an unsupported generation is specified.
func NewContentTopic(applicationName string, applicationVersion uint32, // Note that this is recommended to be used for autosharding where contentTopic format is enforced as per https://rfc.vac.dev/spec/51/#content-topics-format-for-autosharding
func NewContentTopic(applicationName string, applicationVersion string,
contentTopicName string, encoding string, opts ...ContentTopicOption) (ContentTopic, error) { contentTopicName string, encoding string, opts ...ContentTopicOption) (ContentTopic, error) {
params := new(ContentTopicParams) params := new(ContentTopicParams)
@ -83,18 +84,19 @@ func (ct ContentTopic) Equal(ct2 ContentTopic) bool {
} }
// StringToContentTopic can be used to create a ContentTopic object from a string // StringToContentTopic can be used to create a ContentTopic object from a string
// Note that this has to be used only when following the rfc format of contentTopic, which is currently validated only for Autosharding.
// For static and named-sharding, contentTopic can be of any format and hence it is not recommended to use this function.
// This can be updated if required to handle such a case.
func StringToContentTopic(s string) (ContentTopic, error) { func StringToContentTopic(s string) (ContentTopic, error) {
p := strings.Split(s, "/") p := strings.Split(s, "/")
switch len(p) { switch len(p) {
case 5: case 5:
vNum, err := strconv.ParseUint(p[2], 10, 32) if len(p[1]) == 0 || len(p[2]) == 0 || len(p[3]) == 0 || len(p[4]) == 0 {
if err != nil {
return ContentTopic{}, ErrInvalidFormat return ContentTopic{}, ErrInvalidFormat
} }
return ContentTopic{ return ContentTopic{
ApplicationName: p[1], ApplicationName: p[1],
ApplicationVersion: uint32(vNum), ApplicationVersion: p[2],
ContentTopicName: p[3], ContentTopicName: p[3],
Encoding: p[4], Encoding: p[4],
}, nil }, nil
@ -106,15 +108,13 @@ func StringToContentTopic(s string) (ContentTopic, error) {
if err != nil || generation > 0 { if err != nil || generation > 0 {
return ContentTopic{}, ErrInvalidGeneration return ContentTopic{}, ErrInvalidGeneration
} }
vNum, err := strconv.ParseUint(p[3], 10, 32) if len(p[2]) == 0 || len(p[3]) == 0 || len(p[4]) == 0 || len(p[5]) == 0 {
if err != nil {
return ContentTopic{}, ErrInvalidFormat return ContentTopic{}, ErrInvalidFormat
} }
return ContentTopic{ return ContentTopic{
ContentTopicParams: ContentTopicParams{Generation: generation}, ContentTopicParams: ContentTopicParams{Generation: generation},
ApplicationName: p[2], ApplicationName: p[2],
ApplicationVersion: uint32(vNum), ApplicationVersion: p[3],
ContentTopicName: p[4], ContentTopicName: p[4],
Encoding: p[5], Encoding: p[5],
}, nil }, nil

View File

@ -1,7 +1,6 @@
package protocol package protocol
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -22,7 +21,6 @@ func TestEnvelope(t *testing.T) {
topic := e.PubsubTopic() topic := e.PubsubTopic()
require.Equal(t, "test", topic) require.Equal(t, "test", topic)
hash := e.Hash() hash := e.Hash()
fmt.Println(hash)
require.Equal( require.Equal(
t, t,

View File

@ -3,7 +3,6 @@ package relay
import ( import (
"context" "context"
"crypto/rand" "crypto/rand"
"fmt"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -53,7 +52,6 @@ func TestWakuRelay(t *testing.T) {
bytesToSend := []byte{1} bytesToSend := []byte{1}
go func() { go func() {
defer cancel() defer cancel()
env := <-subs[0].Ch env := <-subs[0].Ch
t.Log("received msg", logging.HexString("hash", env.Hash())) t.Log("received msg", logging.HexString("hash", env.Hash()))
}() }()
@ -114,7 +112,7 @@ func TestGossipsubScore(t *testing.T) {
for { for {
_, err := sub.Next(context.Background()) _, err := sub.Next(context.Background())
if err != nil { if err != nil {
fmt.Println(err) t.Log(err)
} }
} }
}() }()

View File

@ -225,7 +225,7 @@ func FromBitVector(buf []byte) (RelayShards, error) {
// This is based on Autosharding algorithm defined in RFC 51 // This is based on Autosharding algorithm defined in RFC 51
func GetShardFromContentTopic(topic ContentTopic, shardCount int) StaticShardingPubsubTopic { func GetShardFromContentTopic(topic ContentTopic, shardCount int) StaticShardingPubsubTopic {
bytes := []byte(topic.ApplicationName) bytes := []byte(topic.ApplicationName)
bytes = append(bytes, []byte(fmt.Sprintf("%d", topic.ApplicationVersion))...) bytes = append(bytes, []byte(topic.ApplicationVersion)...)
hash := hash.SHA256(bytes) hash := hash.SHA256(bytes)
//We only use the last 64 bits of the hash as having more shards is unlikely. //We only use the last 64 bits of the hash as having more shards is unlikely.

View File

@ -9,28 +9,28 @@ import (
) )
func TestContentTopicAndSharding(t *testing.T) { func TestContentTopicAndSharding(t *testing.T) {
ct, err := NewContentTopic("waku", 2, "test", "proto") ct, err := NewContentTopic("waku", "2", "test", "proto")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ct.String(), "/waku/2/test/proto") require.Equal(t, ct.String(), "/waku/2/test/proto")
_, err = StringToContentTopic("/waku/-1/a/b") _, err = StringToContentTopic("/waku/-1/a/b")
require.Error(t, ErrInvalidFormat, err) require.NoError(t, err)
_, err = StringToContentTopic("waku/1/a/b") _, err = StringToContentTopic("waku/1/a/b")
require.Error(t, ErrInvalidFormat, err) require.Error(t, err, ErrInvalidFormat)
_, err = StringToContentTopic("////") _, err = StringToContentTopic("////")
require.Error(t, ErrInvalidFormat, err) require.Error(t, err, ErrInvalidFormat)
_, err = StringToContentTopic("/waku/1/a") _, err = StringToContentTopic("/waku/1/a")
require.Error(t, ErrInvalidFormat, err) require.Error(t, err, ErrInvalidFormat)
ct2, err := StringToContentTopic("/waku/2/test/proto") ct2, err := StringToContentTopic("/waku/2/test/proto")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ct.String(), ct2.String()) require.Equal(t, ct.String(), ct2.String())
require.True(t, ct.Equal(ct2)) require.True(t, ct.Equal(ct2))
ct3, err := NewContentTopic("waku", 2, "test2", "proto") ct3, err := NewContentTopic("waku", "2a", "test2", "proto")
require.NoError(t, err) require.NoError(t, err)
require.False(t, ct.Equal(ct3)) require.False(t, ct.Equal(ct3))
@ -45,12 +45,12 @@ func TestContentTopicAndSharding(t *testing.T) {
require.Equal(t, NewStaticShardingPubsubTopic(ClusterIndex, 3), nsPubSubT1) require.Equal(t, NewStaticShardingPubsubTopic(ClusterIndex, 3), nsPubSubT1)
_, err = StringToContentTopic("/abc/toychat/2/huilong/proto") _, err = StringToContentTopic("/abc/toychat/2/huilong/proto")
require.Error(t, ErrInvalidGeneration, err) require.Error(t, err, ErrInvalidGeneration)
_, err = StringToContentTopic("/1/toychat/2/huilong/proto") _, err = StringToContentTopic("/1/toychat/2/huilong/proto")
require.Error(t, ErrInvalidGeneration, err) require.Error(t, err, ErrInvalidGeneration)
ct5, err := NewContentTopic("waku", 2, "test2", "proto", WithGeneration(0)) ct5, err := NewContentTopic("waku", "2b", "test2", "proto", WithGeneration(0))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ct5.Generation, 0) require.Equal(t, ct5.Generation, 0)
} }
@ -65,7 +65,7 @@ func randomContentTopic() (ContentTopic, error) {
randomChar := 'a' + rune(rand.Intn(26)) randomChar := 'a' + rune(rand.Intn(26))
app = app + string(randomChar) app = app + string(randomChar)
} }
version := uint32(1) version := "1"
var name = "" var name = ""