Files
status-go/internal/protocol/message_validator.go
Igor Sirotin f3e4363808 refactor: move protocol to internal/protocol
Part of the Go project layout migration, item 27.

Pure move plus import-path rewrite across 502 files. No API or behaviour
change. `internal/` keeps the messaging application logic unimportable
from outside the module, which is what the issue asks for -- status-go is
consumed through the C-bindings in mobile/, not as a Go library.

Things that had to follow the move, beyond the Go imports:

- tools/generate-handlers/template.txt. messenger_handlers.go is
  generated, and the template hard-codes the imports it emits, so the
  generated file kept importing protocol/common and failed typecheck.
- .gitignore. The ignore rule for that generated file was pinned to the
  old path; without moving it, a 1486-line generated file starts being
  tracked.
- Makefile: the logosstorage and torrent test targets (both the archive
  packages and ./protocol itself), the archive README, migration-protocol.
- scripts/run_unit_tests.sh, which names the protocol package explicitly
  to shard its tests.
- scripts/cleanup_generated_files.sh and .golangci.yml.

scripts/migration_check.sh also needed a fix that is not specific to this
move: it validated every file the branch touched under a migration dir
against the timestamp naming rule, and a directory rename makes every
migration in it look newly added. It now excludes renames, so moving a
migration is not mistaken for adding one.

refs #7067
2026-08-21 10:11:05 +02:00

291 lines
7.6 KiB
Go

package protocol
import (
"errors"
"fmt"
"strings"
protocolcommon "github.com/status-im/status-go/internal/protocol/common"
"github.com/status-im/status-go/internal/protocol/protobuf"
"github.com/status-im/status-go/internal/protocol/v1"
)
const maxChatMessageTextLength = 4096
const maxStatusMessageText = 128
// maxWhisperDrift is how many milliseconds we allow the clock value to differ
// from whisperTimestamp
const MaxWhisperFutureDriftMs uint64 = 120000
func validateClockValue(clock uint64, whisperTimestamp uint64) error {
if clock == 0 {
return errors.New("clock can't be 0")
}
if clock > whisperTimestamp && clock-whisperTimestamp > MaxWhisperFutureDriftMs {
return errors.New("clock value too high")
}
return nil
}
func ValidateMembershipUpdateMessage(message *protocol.MembershipUpdateMessage, timeNowMs uint64) error {
for _, e := range message.Events {
if err := validateClockValue(e.ClockValue, timeNowMs); err != nil {
return err
}
}
return nil
}
func ValidateStatusUpdate(message *protobuf.StatusUpdate) error {
if message == nil {
return errors.New("message can't be nil")
}
if message.Clock == 0 {
return errors.New("clock can't be 0")
}
if message.StatusType == protobuf.StatusUpdate_UNKNOWN_STATUS_TYPE {
return errors.New("unknown status type")
}
if len([]rune(message.CustomText)) > maxStatusMessageText {
return fmt.Errorf("custom text shouldn't be longer than %d", maxStatusMessageText)
}
return nil
}
func ValidateEditMessage(message *protobuf.EditMessage) error {
if message == nil {
return errors.New("message can't be nil")
}
if message.Clock == 0 {
return errors.New("clock can't be 0")
}
if len(message.ChatId) == 0 {
return errors.New("chat-id can't be empty")
}
if len(message.MessageId) == 0 {
return errors.New("message-id can't be empty")
}
if message.MessageType == protobuf.MessageType_UNKNOWN_MESSAGE_TYPE || message.MessageType == protobuf.MessageType_SYSTEM_MESSAGE_PRIVATE_GROUP {
return errors.New("unknown message type")
}
return ValidateText(message.Text)
}
func ValidateDeleteMessage(message *protobuf.DeleteMessage) error {
if message == nil {
return errors.New("message can't be nil")
}
if len(message.ChatId) == 0 {
return errors.New("chat-id can't be empty")
}
if len(message.MessageId) == 0 {
return errors.New("message-id can't be empty")
}
if message.MessageType == protobuf.MessageType_UNKNOWN_MESSAGE_TYPE || message.MessageType == protobuf.MessageType_SYSTEM_MESSAGE_PRIVATE_GROUP {
return errors.New("unknown message type")
}
return nil
}
func ValidateDeleteForMeMessage(message *protobuf.SyncDeleteForMeMessage) error {
if message == nil {
return errors.New("message can't be nil")
}
if len(message.MessageId) == 0 {
return errors.New("message-id can't be empty")
}
return nil
}
func ValidateReceivedPairInstallation(message *protobuf.SyncPairInstallation, whisperTimestamp uint64) error {
if err := validateClockValue(message.Clock, whisperTimestamp); err != nil {
return err
}
if len(strings.TrimSpace(message.Name)) == 0 {
return errors.New("name can't be empty")
}
if len(strings.TrimSpace(message.DeviceType)) == 0 {
return errors.New("device type can't be empty")
}
if len(strings.TrimSpace(message.InstallationId)) == 0 {
return errors.New("installationId can't be empty")
}
return nil
}
func ValidateText(text string) error {
if len(strings.TrimSpace(text)) == 0 {
return errors.New("text can't be empty")
}
if len([]rune(text)) > maxChatMessageTextLength {
return fmt.Errorf("text shouldn't be longer than %d", maxChatMessageTextLength)
}
return nil
}
func ValidateReceivedChatMessage(message *protobuf.ChatMessage, whisperTimestamp uint64) error {
if err := validateClockValue(message.Clock, whisperTimestamp); err != nil {
return err
}
if message.Timestamp == 0 {
return errors.New("timestamp can't be 0")
}
if message.ContentType != protobuf.ChatMessage_DISCORD_MESSAGE &&
message.ContentType != protobuf.ChatMessage_BRIDGE_MESSAGE &&
(message.ContentType != protobuf.ChatMessage_IMAGE || message.Text != "") {
if err := ValidateText(message.Text); err != nil {
return err
}
}
if len(message.ChatId) == 0 {
return errors.New("chatId can't be empty")
}
if message.MessageType == protobuf.MessageType_UNKNOWN_MESSAGE_TYPE || message.MessageType == protobuf.MessageType_SYSTEM_MESSAGE_PRIVATE_GROUP {
return errors.New("unknown message type")
}
switch message.ContentType {
case protobuf.ChatMessage_UNKNOWN_CONTENT_TYPE:
return errors.New("unknown content type")
case protobuf.ChatMessage_TRANSACTION_COMMAND:
return errors.New("can't receive request address for transaction from others")
case protobuf.ChatMessage_STICKER:
if message.Payload == nil {
return errors.New("no sticker content")
}
sticker := message.GetSticker()
if sticker == nil {
return errors.New("no sticker content")
}
if len(sticker.Hash) == 0 {
return errors.New("sticker hash not set")
}
case protobuf.ChatMessage_IMAGE:
if message.Payload == nil {
return errors.New("no image content")
}
image := message.GetImage()
if image == nil {
return errors.New("no image content")
}
if len(image.Payload) == 0 {
return errors.New("image payload empty")
}
if image.Format == protobuf.ImageFormat_UNKNOWN_IMAGE_FORMAT {
return errors.New("image type unknown")
}
case protobuf.ChatMessage_BRIDGE_MESSAGE:
if message.Payload == nil {
return errors.New("no bridge message content")
}
bridgeMessage := message.GetBridgeMessage()
if bridgeMessage == nil {
return errors.New("no bridge message content")
}
if len(bridgeMessage.UserName) == 0 {
return errors.New("no username")
}
if len(bridgeMessage.BridgeName) == 0 {
return errors.New("no bridge name")
}
if len(bridgeMessage.Content) == 0 {
return errors.New("no bridge message content text")
}
}
if message.ContentType == protobuf.ChatMessage_AUDIO {
if message.Payload == nil {
return errors.New("no audio content")
}
audio := message.GetAudio()
if audio == nil {
return errors.New("no audio content")
}
if len(audio.Payload) == 0 {
return errors.New("audio payload empty")
}
if audio.Type == protobuf.AudioMessage_UNKNOWN_AUDIO_TYPE {
return errors.New("audio type unknown")
}
}
if message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP {
return errors.New("private group system message content type not allowed")
}
if message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_SENT ||
message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_ACCEPTED ||
message.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_REMOVED {
return errors.New("mutual state event system message content type not allowed")
}
if err := protocolcommon.ValidateDisplayName(&message.DisplayName); err != nil {
return err
}
return nil
}
func ValidateReceivedEmojiReaction(emoji *protobuf.EmojiReaction, whisperTimestamp uint64) error {
if err := validateClockValue(emoji.Clock, whisperTimestamp); err != nil {
return err
}
if len(emoji.MessageId) == 0 {
return errors.New("message-id can't be empty")
}
if len(emoji.ChatId) == 0 {
return errors.New("chat-id can't be empty")
}
if emoji.Type == protobuf.EmojiReaction_UNKNOWN_EMOJI_REACTION_TYPE && emoji.Emoji == "" {
return errors.New("unknown emoji reaction type")
}
if emoji.MessageType == protobuf.MessageType_UNKNOWN_MESSAGE_TYPE {
return errors.New("unknown message type")
}
return nil
}
func ValidateReceivedGroupChatInvitation(invitation *protobuf.GroupChatInvitation) error {
if len(invitation.ChatId) == 0 {
return errors.New("chat-id can't be empty")
}
return nil
}