{"record":{"id":"ce9acde44a84e6c5","repo":"gravitational/teleport","slug":"message-is-tdp-not-tdpb","errorCode":null,"errorMessage":"message is TDP, not TDPB","messagePattern":"message is TDP, not TDPB","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/srv/desktop/tdp/protocol/tdpb/tdpb.go","lineNumber":44,"sourceCode":"\t\"errors\"\n\t\"io\"\n\n\t\"github.com/gravitational/trace\"\n\t\"google.golang.org/protobuf/proto\"\n\n\ttdpbv1 \"github.com/gravitational/teleport/api/gen/proto/go/teleport/desktop/v1\"\n\t\"github.com/gravitational/teleport/lib/srv/desktop/tdp\"\n)\n\n// ProtocolName is the identifier for the TDPB protocol.\nconst ProtocolName = \"teleport-tdpb-1.0\"\n\n// ErrUnknownMessage is returned when an unknown message is decoded.\nvar ErrUnknownMessage = errors.New(\"decoded unknown TDPB message\")\n\n// ErrIsTDP is returned when a legacy TDP message is received\n// during or after a connection upgrade to TDPB.\nvar ErrIsTDP = errors.New(\"message is TDP, not TDPB\")\n\nconst (\n\t// We can differentiate between TDP and TDPB messages on the wire\n\t// by inspecting the first byte received. A non-empty first byte\n\t// is presumed to be a TDP message, otherwise, TDPB.\n\t// Since the first byte of a TDPB message is the high 8 bits of its\n\t// length, we must take care not to allow TDPB messages that\n\t// meet or exceed length 2^24 (16MiB).\n\t// Once TDP is fully deprecated we can relax this constraint, although\n\t// it's unlikely we would ever want messages anywhere near this size.\n\tmaxMessageLength = (1 << 24) - 1\n\ttdpbHeaderLength = 4 // sizeof(uint32)\n)\n\n// ClientHello is the first message sent by the client, and advertises\n// client capabilities and connection properties.\ntype ClientHello tdpbv1.ClientHello\n","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/srv/desktop/tdp/protocol/tdpb/tdpb.go#L26-L62","documentation":"Sentinel error returned by DecodeWithTDPDiscard/ReadMessage when the frame's first byte indicates a legacy TDP message rather than a TDPB message (TDPB frames start with a zero first byte, since it holds the high 8 bits of the message length).","triggerScenarios":"A connection upgraded to TDPB still receives a legacy TDP frame (non-zero first byte), so DecodeWithTDPDiscard returns ErrIsTDP instead of a TDPB message.","commonSituations":"Upgrade handshake race where the client sends leftover TDP frames after upgrade; misconfigured client not honoring the protocol upgrade; tests asserting the discard behavior.","solutions":["Ensure the sender stops sending legacy TDP messages once the connection is upgraded to TDPB","In readers, treat ErrIsTDP with errors.Is and discard/skip the frame as intended by DecodeWithTDPDiscard","Investigate upgrade sequencing if legacy frames persist after upgrade completion"],"exampleFix":"// before\nmsg, err := tdpb.ReadMessage(conn)\nif err != nil { return err }\n// after\nmsg, err := tdpb.ReadMessage(conn)\nif errors.Is(err, tdpb.ErrIsTDP) { continue } // legacy frame, discard\nif err != nil { return trace.Wrap(err) }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"msg, err := tdpb.DecodeWithTDPDiscard(rdr)\nif errors.Is(err, tdpb.ErrIsTDP) {\n    continue // legacy TDP frame, discard\n}\nif err != nil { return nil, trace.Wrap(err) }","preventionTips":["Complete the TDP-to-TDPB upgrade handshake before streaming TDPB","Use errors.Is on the sentinel to discard legacy frames","Verify clients honor protocol upgrade responses"],"tags":["tdpb","tdp","protocol-upgrade"],"backgroundTag":"protocol-upgrade-desync","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}