fatedier/frp · error
decode ServerHello transcript: %w
Error message
decode ServerHello transcript: %w
What it means
Thrown by NewClientCryptoContext when the stored ServerHello transcript payload fails to json.Unmarshal. Like the client-hello check, it guards that the transcript bytes used for the transcript hash are the exact JSON the server sent.
Source
Thrown at pkg/proto/wire/crypto.go:139
}
return ""
}
func NewCryptoContext(algorithm string, clientHelloPayload, serverHelloPayload []byte) *CryptoContext {
return &CryptoContext{
Algorithm: algorithm,
TranscriptHash: HashCryptoTranscript(clientHelloPayload, serverHelloPayload),
}
}
func NewClientCryptoContext(clientHelloPayload, serverHelloPayload []byte) (*CryptoContext, error) {
var clientHello ClientHello
if err := json.Unmarshal(clientHelloPayload, &clientHello); err != nil {
return nil, fmt.Errorf("decode ClientHello transcript: %w", err)
}
var serverHello ServerHello
if err := json.Unmarshal(serverHelloPayload, &serverHello); err != nil {
return nil, fmt.Errorf("decode ServerHello transcript: %w", err)
}
if err := ValidateServerHelloForClient(clientHello, serverHello); err != nil {
return nil, err
}
return NewCryptoContext(serverHello.Selected.Crypto.Algorithm, clientHelloPayload, serverHelloPayload), nil
}
func HashCryptoTranscript(clientHelloPayload, serverHelloPayload []byte) []byte {
h := sha256.New()
_, _ = h.Write([]byte(cryptoTranscriptLabel))
writeCryptoTranscriptPart(h, "client hello", clientHelloPayload)
writeCryptoTranscriptPart(h, "server hello", serverHelloPayload)
return h.Sum(nil)
}
func writeCryptoTranscriptPart(h hash.Hash, label string, payload []byte) {
var length [8]byteView on GitHub (pinned to 6c8a8d0a97)
Solutions
- Read the ServerHello with ReadJSONFrame(frameTypeServerHello, &hello) and keep the exact f.Payload bytes for NewCryptoContext.
- Log the offending payload (truncated) when this error fires — the wrapped json error tells you the byte offset of the syntax problem.
- Ensure the server actually completed its hello before the client captures the bytes.
Example fix
// before
var hello wire.ServerHello
_ = conn.ReadJSONFrame(wire.FrameTypeServerHello, &hello)
raw, _ := json.Marshal(hello) // re-marshaled, not the wire bytes
ctx, err := wire.NewClientCryptoContext(clientRaw, raw)
// after
f, err := conn.ReadFrame()
if err != nil { return err }
ctx, err := wire.NewClientCryptoContext(clientRaw, f.Payload) Defensive patterns
Strategy: validation
Validate before calling
if !json.Valid(serverHelloPayload) {
return errors.New("server hello transcript is not valid JSON")
} Try / catch
if _, err := wire.NewClientCryptoContext(clientRaw, serverRaw); err != nil {
if strings.HasPrefix(err.Error(), "decode ServerHello") {
// log truncated payload; likely wrong frame captured or server died mid-hello
}
} Prevention
- Keep the raw server hello payload from ReadFrame alongside the decoded struct.
- Treat a failed server hello decode as a dead connection — never reuse the stream.
When it happens
Trigger: Passing a serverHelloPayload that is not valid ServerHello JSON: truncated frame payload, wrong frame's payload, a plain-text error page read instead of the hello, or an empty slice.
Common situations: Reading the server hello with the wrong frame type expectation and feeding whatever came first; a server that closed mid-handshake leaving a partial payload; tests hand-crafting the server payload as a map that marshals differently than expected.
Related errors
- decode ClientHello transcript: %w
- no supported crypto algorithm
- invalid crypto client random length %d, want %d
- unknown selected crypto algorithm: %s
- selected crypto algorithm was not advertised by client: %s
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/e2043489ce25e715.
Report an issue: GitHub.