{"record":{"id":"f35f0b07141c8584","repo":"netbirdio/netbird","slug":"invalid-message-length","errorCode":null,"errorMessage":"invalid message length","messagePattern":"invalid message length","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/relay/messages/message.go","lineNumber":53,"sourceCode":"\tsizeOfVersionByte = 1\n\tsizeOfMsgType     = 1\n\tsizeOfProtoHeader = sizeOfVersionByte + sizeOfMsgType\n\n\t// auth message\n\tsizeOfMagicByte     = 4\n\theaderSizeAuth      = sizeOfMagicByte + peerIDSize\n\toffsetMagicByte     = sizeOfProtoHeader\n\toffsetAuthPeerID    = sizeOfProtoHeader + sizeOfMagicByte\n\theaderTotalSizeAuth = sizeOfProtoHeader + headerSizeAuth\n\n\t// transport\n\theaderSizeTransport      = peerIDSize\n\toffsetTransportID        = sizeOfProtoHeader\n\theaderTotalSizeTransport = sizeOfProtoHeader + headerSizeTransport\n)\n\nvar (\n\tErrInvalidMessageLength = errors.New(\"invalid message length\")\n\tErrUnsupportedVersion   = errors.New(\"unsupported version\")\n\n\tmagicHeader = []byte{0x21, 0x12, 0xA4, 0x42}\n\n\thealthCheckMsg = []byte{byte(CurrentProtocolVersion), byte(MsgTypeHealthCheck)}\n)\n\ntype MsgType byte\n\nfunc (m MsgType) String() string {\n\tswitch m {\n\tcase MsgTypeHello:\n\t\treturn \"hello\"\n\tcase MsgTypeHelloResponse:\n\t\treturn \"hello response\"\n\tcase MsgTypeAuth:\n\t\treturn \"auth\"\n\tcase MsgTypeAuthResponse:","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/shared/relay/messages/message.go#L35-L71","documentation":"ErrInvalidMessageLength is the messages package's frame-length guard: every Marshal/Unmarshal helper first checks that the buffer is at least the fixed header size for its message type (protocol header, magic byte, and peer-ID fields as applicable). It fires when fewer bytes than a minimal valid frame are supplied, before any content is interpreted.","triggerScenarios":"A short read from a stream (not using io.ReadFull), a datagram truncated below the header size, or calling an Unmarshal* helper on an empty or undersized buffer.","commonSituations":"Custom integrations reading relay sockets with buffers smaller than the header; partial reads from net.Conn.Read treated as complete frames; truncated test fixtures.","solutions":["Read exactly the announced frame length (io.ReadFull) before unmarshalling","Check len(buf) against the expected header size before calling Unmarshal*","Log the received length to spot truncation introduced upstream"],"exampleFix":"// before\nn, _ := conn.Read(buf)\nmsg, err := messages.UnmarshalAuthMsg(buf)\n\n// after\nif _, err := io.ReadFull(conn, buf[:headerTotalSizeAuth]); err != nil {\n\treturn err\n}\nmsg, err := messages.UnmarshalAuthMsg(buf[:headerTotalSizeAuth])","handlingStrategy":"validation","validationCode":"if len(buf) < messages.HeaderTotalSizeAuth { // or the header size for the expected message type\n\treturn fmt.Errorf(\"frame too short: got %d bytes\", len(buf))\n}\npeerID, payload, err := messages.UnmarshalAuthMsg(buf)","typeGuard":null,"tryCatchPattern":"if _, _, err := messages.UnmarshalAuthMsg(buf); err != nil {\n\tif errors.Is(err, messages.ErrInvalidMessageLength) {\n\t\t// incomplete read: read the full frame length and retry the unmarshal\n\t}\n\treturn err\n}","preventionTips":["Use io.ReadFull for fixed headers and announced body lengths","Never treat a single Read's return as a complete frame","Check lengths before unmarshalling in tests and fixtures too"],"tags":["relay","protocol","parsing","framing"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}