| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build go1.21 |
| |
| package quic |
| |
| import ( |
| "fmt" |
| "log/slog" |
| "strconv" |
| "time" |
| ) |
| |
| // A debugFrame is a representation of the contents of a QUIC frame, |
| // used for debug logs and testing but not the primary serving path. |
| type debugFrame interface { |
| String() string |
| write(w *packetWriter) bool |
| LogValue() slog.Value |
| } |
| |
| func parseDebugFrame(b []byte) (f debugFrame, n int) { |
| if len(b) == 0 { |
| return nil, -1 |
| } |
| switch b[0] { |
| case frameTypePadding: |
| f, n = parseDebugFramePadding(b) |
| case frameTypePing: |
| f, n = parseDebugFramePing(b) |
| case frameTypeAck, frameTypeAckECN: |
| f, n = parseDebugFrameAck(b) |
| case frameTypeResetStream: |
| f, n = parseDebugFrameResetStream(b) |
| case frameTypeStopSending: |
| f, n = parseDebugFrameStopSending(b) |
| case frameTypeCrypto: |
| f, n = parseDebugFrameCrypto(b) |
| case frameTypeNewToken: |
| f, n = parseDebugFrameNewToken(b) |
| case frameTypeStreamBase, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f: |
| f, n = parseDebugFrameStream(b) |
| case frameTypeMaxData: |
| f, n = parseDebugFrameMaxData(b) |
| case frameTypeMaxStreamData: |
| f, n = parseDebugFrameMaxStreamData(b) |
| case frameTypeMaxStreamsBidi, frameTypeMaxStreamsUni: |
| f, n = parseDebugFrameMaxStreams(b) |
| case frameTypeDataBlocked: |
| f, n = parseDebugFrameDataBlocked(b) |
| case frameTypeStreamDataBlocked: |
| f, n = parseDebugFrameStreamDataBlocked(b) |
| case frameTypeStreamsBlockedBidi, frameTypeStreamsBlockedUni: |
| f, n = parseDebugFrameStreamsBlocked(b) |
| case frameTypeNewConnectionID: |
| f, n = parseDebugFrameNewConnectionID(b) |
| case frameTypeRetireConnectionID: |
| f, n = parseDebugFrameRetireConnectionID(b) |
| case frameTypePathChallenge: |
| f, n = parseDebugFramePathChallenge(b) |
| case frameTypePathResponse: |
| f, n = parseDebugFramePathResponse(b) |
| case frameTypeConnectionCloseTransport: |
| f, n = parseDebugFrameConnectionCloseTransport(b) |
| case frameTypeConnectionCloseApplication: |
| f, n = parseDebugFrameConnectionCloseApplication(b) |
| case frameTypeHandshakeDone: |
| f, n = parseDebugFrameHandshakeDone(b) |
| default: |
| return nil, -1 |
| } |
| return f, n |
| } |
| |
| // debugFramePadding is a sequence of PADDING frames. |
| type debugFramePadding struct { |
| size int |
| to int // alternate for writing packets: pad to |
| } |
| |
| func parseDebugFramePadding(b []byte) (f debugFramePadding, n int) { |
| for n < len(b) && b[n] == frameTypePadding { |
| n++ |
| } |
| f.size = n |
| return f, n |
| } |
| |
| func (f debugFramePadding) String() string { |
| return fmt.Sprintf("PADDING*%v", f.size) |
| } |
| |
| func (f debugFramePadding) write(w *packetWriter) bool { |
| if w.avail() == 0 { |
| return false |
| } |
| if f.to > 0 { |
| w.appendPaddingTo(f.to) |
| return true |
| } |
| for i := 0; i < f.size && w.avail() > 0; i++ { |
| w.b = append(w.b, frameTypePadding) |
| } |
| return true |
| } |
| |
| func (f debugFramePadding) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "padding"), |
| slog.Int("length", f.size), |
| ) |
| } |
| |
| // debugFramePing is a PING frame. |
| type debugFramePing struct{} |
| |
| func parseDebugFramePing(b []byte) (f debugFramePing, n int) { |
| return f, 1 |
| } |
| |
| func (f debugFramePing) String() string { |
| return "PING" |
| } |
| |
| func (f debugFramePing) write(w *packetWriter) bool { |
| return w.appendPingFrame() |
| } |
| |
| func (f debugFramePing) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "ping"), |
| ) |
| } |
| |
| // debugFrameAck is an ACK frame. |
| type debugFrameAck struct { |
| ackDelay unscaledAckDelay |
| ranges []i64range[packetNumber] |
| } |
| |
| func parseDebugFrameAck(b []byte) (f debugFrameAck, n int) { |
| f.ranges = nil |
| _, f.ackDelay, n = consumeAckFrame(b, func(_ int, start, end packetNumber) { |
| f.ranges = append(f.ranges, i64range[packetNumber]{ |
| start: start, |
| end: end, |
| }) |
| }) |
| // Ranges are parsed high to low; reverse ranges slice to order them low to high. |
| for i := 0; i < len(f.ranges)/2; i++ { |
| j := len(f.ranges) - 1 |
| f.ranges[i], f.ranges[j] = f.ranges[j], f.ranges[i] |
| } |
| return f, n |
| } |
| |
| func (f debugFrameAck) String() string { |
| s := fmt.Sprintf("ACK Delay=%v", f.ackDelay) |
| for _, r := range f.ranges { |
| s += fmt.Sprintf(" [%v,%v)", r.start, r.end) |
| } |
| return s |
| } |
| |
| func (f debugFrameAck) write(w *packetWriter) bool { |
| return w.appendAckFrame(rangeset[packetNumber](f.ranges), f.ackDelay) |
| } |
| |
| func (f debugFrameAck) LogValue() slog.Value { |
| return slog.StringValue("error: debugFrameAck should not appear as a slog Value") |
| } |
| |
| // debugFrameScaledAck is an ACK frame with scaled ACK Delay. |
| // |
| // This type is used in qlog events, which need access to the delay as a duration. |
| type debugFrameScaledAck struct { |
| ackDelay time.Duration |
| ranges []i64range[packetNumber] |
| } |
| |
| func (f debugFrameScaledAck) LogValue() slog.Value { |
| var ackDelay slog.Attr |
| if f.ackDelay >= 0 { |
| ackDelay = slog.Duration("ack_delay", f.ackDelay) |
| } |
| return slog.GroupValue( |
| slog.String("frame_type", "ack"), |
| // Rather than trying to convert the ack ranges into the slog data model, |
| // pass a value that can JSON-encode itself. |
| slog.Any("acked_ranges", debugAckRanges(f.ranges)), |
| ackDelay, |
| ) |
| } |
| |
| type debugAckRanges []i64range[packetNumber] |
| |
| // AppendJSON appends a JSON encoding of the ack ranges to b, and returns it. |
| // This is different than the standard json.Marshaler, but more efficient. |
| // Since we only use this in cooperation with the qlog package, |
| // encoding/json compatibility is irrelevant. |
| func (r debugAckRanges) AppendJSON(b []byte) []byte { |
| b = append(b, '[') |
| for i, ar := range r { |
| start, end := ar.start, ar.end-1 // qlog ranges are closed-closed |
| if i != 0 { |
| b = append(b, ',') |
| } |
| b = append(b, '[') |
| b = strconv.AppendInt(b, int64(start), 10) |
| if start != end { |
| b = append(b, ',') |
| b = strconv.AppendInt(b, int64(end), 10) |
| } |
| b = append(b, ']') |
| } |
| b = append(b, ']') |
| return b |
| } |
| |
| func (r debugAckRanges) String() string { |
| return string(r.AppendJSON(nil)) |
| } |
| |
| // debugFrameResetStream is a RESET_STREAM frame. |
| type debugFrameResetStream struct { |
| id streamID |
| code uint64 |
| finalSize int64 |
| } |
| |
| func parseDebugFrameResetStream(b []byte) (f debugFrameResetStream, n int) { |
| f.id, f.code, f.finalSize, n = consumeResetStreamFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameResetStream) String() string { |
| return fmt.Sprintf("RESET_STREAM ID=%v Code=%v FinalSize=%v", f.id, f.code, f.finalSize) |
| } |
| |
| func (f debugFrameResetStream) write(w *packetWriter) bool { |
| return w.appendResetStreamFrame(f.id, f.code, f.finalSize) |
| } |
| |
| func (f debugFrameResetStream) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "reset_stream"), |
| slog.Uint64("stream_id", uint64(f.id)), |
| slog.Uint64("final_size", uint64(f.finalSize)), |
| ) |
| } |
| |
| // debugFrameStopSending is a STOP_SENDING frame. |
| type debugFrameStopSending struct { |
| id streamID |
| code uint64 |
| } |
| |
| func parseDebugFrameStopSending(b []byte) (f debugFrameStopSending, n int) { |
| f.id, f.code, n = consumeStopSendingFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameStopSending) String() string { |
| return fmt.Sprintf("STOP_SENDING ID=%v Code=%v", f.id, f.code) |
| } |
| |
| func (f debugFrameStopSending) write(w *packetWriter) bool { |
| return w.appendStopSendingFrame(f.id, f.code) |
| } |
| |
| func (f debugFrameStopSending) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "stop_sending"), |
| slog.Uint64("stream_id", uint64(f.id)), |
| slog.Uint64("error_code", uint64(f.code)), |
| ) |
| } |
| |
| // debugFrameCrypto is a CRYPTO frame. |
| type debugFrameCrypto struct { |
| off int64 |
| data []byte |
| } |
| |
| func parseDebugFrameCrypto(b []byte) (f debugFrameCrypto, n int) { |
| f.off, f.data, n = consumeCryptoFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameCrypto) String() string { |
| return fmt.Sprintf("CRYPTO Offset=%v Length=%v", f.off, len(f.data)) |
| } |
| |
| func (f debugFrameCrypto) write(w *packetWriter) bool { |
| b, added := w.appendCryptoFrame(f.off, len(f.data)) |
| copy(b, f.data) |
| return added |
| } |
| |
| func (f debugFrameCrypto) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "crypto"), |
| slog.Int64("offset", f.off), |
| slog.Int("length", len(f.data)), |
| ) |
| } |
| |
| // debugFrameNewToken is a NEW_TOKEN frame. |
| type debugFrameNewToken struct { |
| token []byte |
| } |
| |
| func parseDebugFrameNewToken(b []byte) (f debugFrameNewToken, n int) { |
| f.token, n = consumeNewTokenFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameNewToken) String() string { |
| return fmt.Sprintf("NEW_TOKEN Token=%x", f.token) |
| } |
| |
| func (f debugFrameNewToken) write(w *packetWriter) bool { |
| return w.appendNewTokenFrame(f.token) |
| } |
| |
| func (f debugFrameNewToken) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "new_token"), |
| slogHexstring("token", f.token), |
| ) |
| } |
| |
| // debugFrameStream is a STREAM frame. |
| type debugFrameStream struct { |
| id streamID |
| fin bool |
| off int64 |
| data []byte |
| } |
| |
| func parseDebugFrameStream(b []byte) (f debugFrameStream, n int) { |
| f.id, f.off, f.fin, f.data, n = consumeStreamFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameStream) String() string { |
| fin := "" |
| if f.fin { |
| fin = " FIN" |
| } |
| return fmt.Sprintf("STREAM ID=%v%v Offset=%v Length=%v", f.id, fin, f.off, len(f.data)) |
| } |
| |
| func (f debugFrameStream) write(w *packetWriter) bool { |
| b, added := w.appendStreamFrame(f.id, f.off, len(f.data), f.fin) |
| copy(b, f.data) |
| return added |
| } |
| |
| func (f debugFrameStream) LogValue() slog.Value { |
| var fin slog.Attr |
| if f.fin { |
| fin = slog.Bool("fin", true) |
| } |
| return slog.GroupValue( |
| slog.String("frame_type", "stream"), |
| slog.Uint64("stream_id", uint64(f.id)), |
| slog.Int64("offset", f.off), |
| slog.Int("length", len(f.data)), |
| fin, |
| ) |
| } |
| |
| // debugFrameMaxData is a MAX_DATA frame. |
| type debugFrameMaxData struct { |
| max int64 |
| } |
| |
| func parseDebugFrameMaxData(b []byte) (f debugFrameMaxData, n int) { |
| f.max, n = consumeMaxDataFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameMaxData) String() string { |
| return fmt.Sprintf("MAX_DATA Max=%v", f.max) |
| } |
| |
| func (f debugFrameMaxData) write(w *packetWriter) bool { |
| return w.appendMaxDataFrame(f.max) |
| } |
| |
| func (f debugFrameMaxData) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "max_data"), |
| slog.Int64("maximum", f.max), |
| ) |
| } |
| |
| // debugFrameMaxStreamData is a MAX_STREAM_DATA frame. |
| type debugFrameMaxStreamData struct { |
| id streamID |
| max int64 |
| } |
| |
| func parseDebugFrameMaxStreamData(b []byte) (f debugFrameMaxStreamData, n int) { |
| f.id, f.max, n = consumeMaxStreamDataFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameMaxStreamData) String() string { |
| return fmt.Sprintf("MAX_STREAM_DATA ID=%v Max=%v", f.id, f.max) |
| } |
| |
| func (f debugFrameMaxStreamData) write(w *packetWriter) bool { |
| return w.appendMaxStreamDataFrame(f.id, f.max) |
| } |
| |
| func (f debugFrameMaxStreamData) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "max_stream_data"), |
| slog.Uint64("stream_id", uint64(f.id)), |
| slog.Int64("maximum", f.max), |
| ) |
| } |
| |
| // debugFrameMaxStreams is a MAX_STREAMS frame. |
| type debugFrameMaxStreams struct { |
| streamType streamType |
| max int64 |
| } |
| |
| func parseDebugFrameMaxStreams(b []byte) (f debugFrameMaxStreams, n int) { |
| f.streamType, f.max, n = consumeMaxStreamsFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameMaxStreams) String() string { |
| return fmt.Sprintf("MAX_STREAMS Type=%v Max=%v", f.streamType, f.max) |
| } |
| |
| func (f debugFrameMaxStreams) write(w *packetWriter) bool { |
| return w.appendMaxStreamsFrame(f.streamType, f.max) |
| } |
| |
| func (f debugFrameMaxStreams) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "max_streams"), |
| slog.String("stream_type", f.streamType.qlogString()), |
| slog.Int64("maximum", f.max), |
| ) |
| } |
| |
| // debugFrameDataBlocked is a DATA_BLOCKED frame. |
| type debugFrameDataBlocked struct { |
| max int64 |
| } |
| |
| func parseDebugFrameDataBlocked(b []byte) (f debugFrameDataBlocked, n int) { |
| f.max, n = consumeDataBlockedFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameDataBlocked) String() string { |
| return fmt.Sprintf("DATA_BLOCKED Max=%v", f.max) |
| } |
| |
| func (f debugFrameDataBlocked) write(w *packetWriter) bool { |
| return w.appendDataBlockedFrame(f.max) |
| } |
| |
| func (f debugFrameDataBlocked) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "data_blocked"), |
| slog.Int64("limit", f.max), |
| ) |
| } |
| |
| // debugFrameStreamDataBlocked is a STREAM_DATA_BLOCKED frame. |
| type debugFrameStreamDataBlocked struct { |
| id streamID |
| max int64 |
| } |
| |
| func parseDebugFrameStreamDataBlocked(b []byte) (f debugFrameStreamDataBlocked, n int) { |
| f.id, f.max, n = consumeStreamDataBlockedFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameStreamDataBlocked) String() string { |
| return fmt.Sprintf("STREAM_DATA_BLOCKED ID=%v Max=%v", f.id, f.max) |
| } |
| |
| func (f debugFrameStreamDataBlocked) write(w *packetWriter) bool { |
| return w.appendStreamDataBlockedFrame(f.id, f.max) |
| } |
| |
| func (f debugFrameStreamDataBlocked) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "stream_data_blocked"), |
| slog.Uint64("stream_id", uint64(f.id)), |
| slog.Int64("limit", f.max), |
| ) |
| } |
| |
| // debugFrameStreamsBlocked is a STREAMS_BLOCKED frame. |
| type debugFrameStreamsBlocked struct { |
| streamType streamType |
| max int64 |
| } |
| |
| func parseDebugFrameStreamsBlocked(b []byte) (f debugFrameStreamsBlocked, n int) { |
| f.streamType, f.max, n = consumeStreamsBlockedFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameStreamsBlocked) String() string { |
| return fmt.Sprintf("STREAMS_BLOCKED Type=%v Max=%v", f.streamType, f.max) |
| } |
| |
| func (f debugFrameStreamsBlocked) write(w *packetWriter) bool { |
| return w.appendStreamsBlockedFrame(f.streamType, f.max) |
| } |
| |
| func (f debugFrameStreamsBlocked) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "streams_blocked"), |
| slog.String("stream_type", f.streamType.qlogString()), |
| slog.Int64("limit", f.max), |
| ) |
| } |
| |
| // debugFrameNewConnectionID is a NEW_CONNECTION_ID frame. |
| type debugFrameNewConnectionID struct { |
| seq int64 |
| retirePriorTo int64 |
| connID []byte |
| token statelessResetToken |
| } |
| |
| func parseDebugFrameNewConnectionID(b []byte) (f debugFrameNewConnectionID, n int) { |
| f.seq, f.retirePriorTo, f.connID, f.token, n = consumeNewConnectionIDFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameNewConnectionID) String() string { |
| return fmt.Sprintf("NEW_CONNECTION_ID Seq=%v Retire=%v ID=%x Token=%x", f.seq, f.retirePriorTo, f.connID, f.token[:]) |
| } |
| |
| func (f debugFrameNewConnectionID) write(w *packetWriter) bool { |
| return w.appendNewConnectionIDFrame(f.seq, f.retirePriorTo, f.connID, f.token) |
| } |
| |
| func (f debugFrameNewConnectionID) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "new_connection_id"), |
| slog.Int64("sequence_number", f.seq), |
| slog.Int64("retire_prior_to", f.retirePriorTo), |
| slogHexstring("connection_id", f.connID), |
| slogHexstring("stateless_reset_token", f.token[:]), |
| ) |
| } |
| |
| // debugFrameRetireConnectionID is a NEW_CONNECTION_ID frame. |
| type debugFrameRetireConnectionID struct { |
| seq int64 |
| } |
| |
| func parseDebugFrameRetireConnectionID(b []byte) (f debugFrameRetireConnectionID, n int) { |
| f.seq, n = consumeRetireConnectionIDFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameRetireConnectionID) String() string { |
| return fmt.Sprintf("RETIRE_CONNECTION_ID Seq=%v", f.seq) |
| } |
| |
| func (f debugFrameRetireConnectionID) write(w *packetWriter) bool { |
| return w.appendRetireConnectionIDFrame(f.seq) |
| } |
| |
| func (f debugFrameRetireConnectionID) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "retire_connection_id"), |
| slog.Int64("sequence_number", f.seq), |
| ) |
| } |
| |
| // debugFramePathChallenge is a PATH_CHALLENGE frame. |
| type debugFramePathChallenge struct { |
| data pathChallengeData |
| } |
| |
| func parseDebugFramePathChallenge(b []byte) (f debugFramePathChallenge, n int) { |
| f.data, n = consumePathChallengeFrame(b) |
| return f, n |
| } |
| |
| func (f debugFramePathChallenge) String() string { |
| return fmt.Sprintf("PATH_CHALLENGE Data=%x", f.data) |
| } |
| |
| func (f debugFramePathChallenge) write(w *packetWriter) bool { |
| return w.appendPathChallengeFrame(f.data) |
| } |
| |
| func (f debugFramePathChallenge) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "path_challenge"), |
| slog.String("data", fmt.Sprintf("%x", f.data)), |
| ) |
| } |
| |
| // debugFramePathResponse is a PATH_RESPONSE frame. |
| type debugFramePathResponse struct { |
| data pathChallengeData |
| } |
| |
| func parseDebugFramePathResponse(b []byte) (f debugFramePathResponse, n int) { |
| f.data, n = consumePathResponseFrame(b) |
| return f, n |
| } |
| |
| func (f debugFramePathResponse) String() string { |
| return fmt.Sprintf("PATH_RESPONSE Data=%x", f.data) |
| } |
| |
| func (f debugFramePathResponse) write(w *packetWriter) bool { |
| return w.appendPathResponseFrame(f.data) |
| } |
| |
| func (f debugFramePathResponse) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "path_response"), |
| slog.String("data", fmt.Sprintf("%x", f.data)), |
| ) |
| } |
| |
| // debugFrameConnectionCloseTransport is a CONNECTION_CLOSE frame carrying a transport error. |
| type debugFrameConnectionCloseTransport struct { |
| code transportError |
| frameType uint64 |
| reason string |
| } |
| |
| func parseDebugFrameConnectionCloseTransport(b []byte) (f debugFrameConnectionCloseTransport, n int) { |
| f.code, f.frameType, f.reason, n = consumeConnectionCloseTransportFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameConnectionCloseTransport) String() string { |
| s := fmt.Sprintf("CONNECTION_CLOSE Code=%v", f.code) |
| if f.frameType != 0 { |
| s += fmt.Sprintf(" FrameType=%v", f.frameType) |
| } |
| if f.reason != "" { |
| s += fmt.Sprintf(" Reason=%q", f.reason) |
| } |
| return s |
| } |
| |
| func (f debugFrameConnectionCloseTransport) write(w *packetWriter) bool { |
| return w.appendConnectionCloseTransportFrame(f.code, f.frameType, f.reason) |
| } |
| |
| func (f debugFrameConnectionCloseTransport) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "connection_close"), |
| slog.String("error_space", "transport"), |
| slog.Uint64("error_code_value", uint64(f.code)), |
| slog.String("reason", f.reason), |
| ) |
| } |
| |
| // debugFrameConnectionCloseApplication is a CONNECTION_CLOSE frame carrying an application error. |
| type debugFrameConnectionCloseApplication struct { |
| code uint64 |
| reason string |
| } |
| |
| func parseDebugFrameConnectionCloseApplication(b []byte) (f debugFrameConnectionCloseApplication, n int) { |
| f.code, f.reason, n = consumeConnectionCloseApplicationFrame(b) |
| return f, n |
| } |
| |
| func (f debugFrameConnectionCloseApplication) String() string { |
| s := fmt.Sprintf("CONNECTION_CLOSE AppCode=%v", f.code) |
| if f.reason != "" { |
| s += fmt.Sprintf(" Reason=%q", f.reason) |
| } |
| return s |
| } |
| |
| func (f debugFrameConnectionCloseApplication) write(w *packetWriter) bool { |
| return w.appendConnectionCloseApplicationFrame(f.code, f.reason) |
| } |
| |
| func (f debugFrameConnectionCloseApplication) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "connection_close"), |
| slog.String("error_space", "application"), |
| slog.Uint64("error_code_value", uint64(f.code)), |
| slog.String("reason", f.reason), |
| ) |
| } |
| |
| // debugFrameHandshakeDone is a HANDSHAKE_DONE frame. |
| type debugFrameHandshakeDone struct{} |
| |
| func parseDebugFrameHandshakeDone(b []byte) (f debugFrameHandshakeDone, n int) { |
| return f, 1 |
| } |
| |
| func (f debugFrameHandshakeDone) String() string { |
| return "HANDSHAKE_DONE" |
| } |
| |
| func (f debugFrameHandshakeDone) write(w *packetWriter) bool { |
| return w.appendHandshakeDoneFrame() |
| } |
| |
| func (f debugFrameHandshakeDone) LogValue() slog.Value { |
| return slog.GroupValue( |
| slog.String("frame_type", "handshake_done"), |
| ) |
| } |