mirror of
https://github.com/logos-messaging/logos-messaging-go.git
synced 2026-01-08 00:43:10 +00:00
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:
parent
43412c9da5
commit
28c0cd5d8e
@ -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
|
||||||
|
|||||||
@ -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];
|
||||||
|
|||||||
@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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{}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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 = ""
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user