diff --git a/.gitbook/specifications/aol.md b/.gitbook/specifications/aol.md index 727a0243..a2f017c7 100644 --- a/.gitbook/specifications/aol.md +++ b/.gitbook/specifications/aol.md @@ -17,6 +17,8 @@ type MsgCreateTopic struct { A `MsgCreateTopic` is constructed to facilitate the AOL topic. Message sender can set a topic and description. The writer can add record after receiving the appropriate privileges. +Each field has limit. See the [Limits](#limits) section. + #### Add Writer ```go @@ -32,6 +34,8 @@ type MsgAddWriter struct { The owner of the exist topics can manage writer privileges. Writer who received privileges from the topic owner can add record to the topic. This means that the owner authenticate the writer. This `MsgAddWriter` performs a function similar to issuing a certificate. +Each field has limit. See the [Limits](#limits) section. + #### Delete Writer ```go @@ -45,6 +49,8 @@ type MsgDeleteWriter struct { This `MsgDeleteWriter` removes writer from the topic. It is impossible to add record to the topic after being deprived of authority. +Each field has limit. See the [Limits](#limits) section. + #### Add Record ```go @@ -61,3 +67,14 @@ type MsgAddRecord struct { This `MsgAddRecord` add record or any data to the topic. If `FeePayerAddress` is provided, node charges fee to FeePayer. +Each field has limit. See the [Limits](#limits) section. + +### Limits + +|Field|Min Length|Max Length|Charset| +|-----|----------|----------|-------| +|`TopicName`|1|70|`a-z`, `A-Z`, `0-9`, `.`, `_` and `-`| +|`Moniker`|0|70|`a-z`, `A-Z`, `0-9`, `.`, `_` and `-`| +|`Description`|0|5,000|Any| +|`Key`|0|70|Any| +|`Value`|0|5,000|Any| diff --git a/x/aol/types/errors.go b/x/aol/types/errors.go index 0bc63eb8..c7368237 100644 --- a/x/aol/types/errors.go +++ b/x/aol/types/errors.go @@ -15,6 +15,8 @@ const ( CodeWriterExists sdk.CodeType = 104 CodeWriterNotFound sdk.CodeType = 104 CodeWriterNotAuthorized sdk.CodeType = 105 + CodeInvalidTopic sdk.CodeType = 106 + CodeInvalidMoniker sdk.CodeType = 107 ) func ErrMessageTooLarge(descriptor string, got, max int) sdk.Error { @@ -41,3 +43,11 @@ func ErrWriterNotFound(writer sdk.AccAddress) sdk.Error { func ErrWriterNotAuthorized(writer sdk.AccAddress) sdk.Error { return sdk.NewError(DefaultCodespace, CodeWriterNotAuthorized, "writer %v not authorized", writer) } + +func ErrInvalidTopic(topic string) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidTopic, "invalid topic %v", topic) +} + +func ErrInvalidMoniker(moniker string) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeInvalidMoniker, "invalid moniker %v", moniker) +} diff --git a/x/aol/types/msgs.go b/x/aol/types/msgs.go index df0cff61..4d00277d 100644 --- a/x/aol/types/msgs.go +++ b/x/aol/types/msgs.go @@ -1,6 +1,8 @@ package types import ( + "regexp" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -31,11 +33,11 @@ func (msg MsgCreateTopic) Route() string { return RouterKey } func (msg MsgCreateTopic) Type() string { return "create_topic" } func (msg MsgCreateTopic) ValidateBasic() sdk.Error { - if len(msg.TopicName) > MaxTopicLength { - return ErrMessageTooLarge("topic_name", len(msg.TopicName), MaxTopicLength) + if err := validateTopic(msg.TopicName); err != nil { + return err } - if len(msg.Description) > MaxDescriptionLength { - return ErrMessageTooLarge("description", len(msg.Description), MaxDescriptionLength) + if err := validateDescription(msg.Description); err != nil { + return err } if msg.OwnerAddress.Empty() { return sdk.ErrInvalidAddress(msg.OwnerAddress.String()) @@ -66,14 +68,14 @@ func (msg MsgAddWriter) Route() string { return RouterKey } func (msg MsgAddWriter) Type() string { return "add_writer" } func (msg MsgAddWriter) ValidateBasic() sdk.Error { - if len(msg.TopicName) > MaxTopicLength { - return ErrMessageTooLarge("topic_name", len(msg.TopicName), MaxTopicLength) + if err := validateTopic(msg.TopicName); err != nil { + return err } - if len(msg.Moniker) > MaxMonikerLength { - return ErrMessageTooLarge("moniker", len(msg.Moniker), MaxMonikerLength) + if err := validateMoniker(msg.Moniker); err != nil { + return err } - if len(msg.Description) > MaxDescriptionLength { - return ErrMessageTooLarge("description", len(msg.Description), MaxDescriptionLength) + if err := validateDescription(msg.Description); err != nil { + return err } if msg.WriterAddress.Empty() { return sdk.ErrInvalidAddress(msg.WriterAddress.String()) @@ -105,9 +107,8 @@ func (msg MsgDeleteWriter) Route() string { return RouterKey } func (msg MsgDeleteWriter) Type() string { return "delete_writer" } func (msg MsgDeleteWriter) ValidateBasic() sdk.Error { - // TODO Empty Topic error - if len(msg.TopicName) > MaxTopicLength { - return ErrMessageTooLarge("topic_name", len(msg.TopicName), MaxTopicLength) + if err := validateTopic(msg.TopicName); err != nil { + return err } if msg.WriterAddress.Empty() { // TODO Error Message @@ -143,14 +144,14 @@ func (msg MsgAddRecord) Route() string { return RouterKey } func (msg MsgAddRecord) Type() string { return "add_record" } func (msg MsgAddRecord) ValidateBasic() sdk.Error { - if len(msg.TopicName) > MaxTopicLength { - return ErrMessageTooLarge("topic", len(msg.TopicName), MaxTopicLength) + if err := validateTopic(msg.TopicName); err != nil { + return err } - if len(msg.Key) > MaxRecordKeyLength { - return ErrMessageTooLarge("key", len(msg.Key), MaxRecordKeyLength) + if err := validateRecordKey(msg.Key); err != nil { + return err } - if len(msg.Value) > MaxRecordValueLength { - return ErrMessageTooLarge("value", len(msg.Value), MaxRecordValueLength) + if err := validateRecordValue(msg.Value); err != nil { + return err } if msg.WriterAddress.Empty() { return sdk.ErrInvalidAddress(msg.WriterAddress.String()) @@ -172,3 +173,50 @@ func (msg MsgAddRecord) GetSigners() []sdk.AccAddress { } return []sdk.AccAddress{msg.FeePayerAddress, msg.WriterAddress} } + +func validateTopic(topic string) sdk.Error { + if len(topic) > MaxTopicLength { + return ErrMessageTooLarge("topic", len(topic), MaxTopicLength) + } + + // cannot be an empty string + if !regexp.MustCompile("^[A-Za-z0-9._-]+$").MatchString(topic) { + return ErrInvalidTopic(topic) + } + + return nil +} + +func validateMoniker(moniker string) sdk.Error { + if len(moniker) > MaxMonikerLength { + return ErrMessageTooLarge("moniker", len(moniker), MaxMonikerLength) + } + + // can be an empty string + if !regexp.MustCompile("^[A-Za-z0-9._-]*$").MatchString(moniker) { + return ErrInvalidMoniker(moniker) + } + + return nil +} + +func validateDescription(description string) sdk.Error { + if len(description) > MaxDescriptionLength { + return ErrMessageTooLarge("description", len(description), MaxDescriptionLength) + } + return nil +} + +func validateRecordKey(key []byte) sdk.Error { + if len(key) > MaxRecordKeyLength { + return ErrMessageTooLarge("key", len(key), MaxRecordKeyLength) + } + return nil +} + +func validateRecordValue(value []byte) sdk.Error { + if len(value) > MaxRecordValueLength { + return ErrMessageTooLarge("value", len(value), MaxRecordValueLength) + } + return nil +} diff --git a/x/aol/types/msgs_test.go b/x/aol/types/msgs_test.go new file mode 100644 index 00000000..d9ce14b7 --- /dev/null +++ b/x/aol/types/msgs_test.go @@ -0,0 +1,73 @@ +package types + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateTopic(t *testing.T) { + assert.Nil(t, validateTopic("a.B_c-D123")) + + assert.Equal(t, ErrInvalidTopic(""), validateTopic("")) + assert.Equal(t, ErrInvalidTopic("a$"), validateTopic("a$")) + assert.Equal(t, ErrInvalidTopic("a b"), validateTopic("a b")) + assert.Equal(t, ErrInvalidTopic(" ab"), validateTopic(" ab")) + assert.Equal(t, ErrInvalidTopic("ab "), validateTopic("ab ")) + + var buf bytes.Buffer + for i := 0; i < MaxTopicLength+1; i++ { + buf.WriteByte('a') + } + assert.Equal(t, ErrMessageTooLarge("topic", MaxTopicLength+1, MaxTopicLength), validateTopic(buf.String())) +} + +func TestValidateMoniker(t *testing.T) { + assert.Nil(t, validateMoniker("a.B_c-D123")) + assert.Nil(t, validateMoniker("")) + + assert.Equal(t, ErrInvalidMoniker("a$"), validateMoniker("a$")) + assert.Equal(t, ErrInvalidMoniker("a b"), validateMoniker("a b")) + assert.Equal(t, ErrInvalidMoniker(" ab"), validateMoniker(" ab")) + assert.Equal(t, ErrInvalidMoniker("ab "), validateMoniker("ab ")) + + var buf bytes.Buffer + for i := 0; i < MaxMonikerLength+1; i++ { + buf.WriteByte('a') + } + assert.Equal(t, ErrMessageTooLarge("moniker", MaxMonikerLength+1, MaxMonikerLength), validateMoniker(buf.String())) +} + +func TestValidateDescription(t *testing.T) { + assert.Nil(t, validateDescription("")) + assert.Nil(t, validateDescription("abc")) + + var buf bytes.Buffer + for i := 0; i < MaxDescriptionLength+1; i++ { + buf.WriteByte('a') + } + assert.Equal(t, ErrMessageTooLarge("description", MaxDescriptionLength+1, MaxDescriptionLength), validateDescription(buf.String())) +} + +func TestValidateRecordKey(t *testing.T) { + assert.Nil(t, validateRecordKey([]byte{})) + assert.Nil(t, validateRecordKey([]byte("abc"))) + + var buf bytes.Buffer + for i := 0; i < MaxRecordKeyLength+1; i++ { + buf.WriteByte('a') + } + assert.Equal(t, ErrMessageTooLarge("key", MaxRecordKeyLength+1, MaxRecordKeyLength), validateRecordKey(buf.Bytes())) +} + +func TestValidateRecordValue(t *testing.T) { + assert.Nil(t, validateRecordValue([]byte{})) + assert.Nil(t, validateRecordValue([]byte("abc"))) + + var buf bytes.Buffer + for i := 0; i < MaxRecordValueLength+1; i++ { + buf.WriteByte('a') + } + assert.Equal(t, ErrMessageTooLarge("value", MaxRecordValueLength+1, MaxRecordValueLength), validateRecordValue(buf.Bytes())) +}