package qlog import ( "errors" "fmt" "net" "time" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/internal/utils" "github.com/lucas-clemente/quic-go/logging" "github.com/francoispqt/gojay" ) func milliseconds(dur time.Duration) float64 { return float64(dur.Nanoseconds()) / 1e6 } type eventDetails interface { Category() category Name() string gojay.MarshalerJSONObject } type event struct { RelativeTime time.Duration eventDetails } var _ gojay.MarshalerJSONObject = event{} func (e event) IsNil() bool { return false } func (e event) MarshalJSONObject(enc *gojay.Encoder) { enc.Float64Key("time", milliseconds(e.RelativeTime)) enc.StringKey("name", e.Category().String()+":"+e.Name()) enc.ObjectKey("data", e.eventDetails) } type versions []versionNumber func (v versions) IsNil() bool { return false } func (v versions) MarshalJSONArray(enc *gojay.Encoder) { for _, e := range v { enc.AddString(e.String()) } } type rawInfo struct { Length logging.ByteCount // full packet length, including header and AEAD authentication tag PayloadLength logging.ByteCount // length of the packet payload, excluding AEAD tag } func (i rawInfo) IsNil() bool { return false } func (i rawInfo) MarshalJSONObject(enc *gojay.Encoder) { enc.Uint64Key("length", uint64(i.Length)) enc.Uint64KeyOmitEmpty("payload_length", uint64(i.PayloadLength)) } type eventConnectionStarted struct { SrcAddr *net.UDPAddr DestAddr *net.UDPAddr SrcConnectionID protocol.ConnectionID DestConnectionID protocol.ConnectionID } var _ eventDetails = &eventConnectionStarted{} func (e eventConnectionStarted) Category() category { return categoryTransport } func (e eventConnectionStarted) Name() string { return "connection_started" } func (e eventConnectionStarted) IsNil() bool { return false } func (e eventConnectionStarted) MarshalJSONObject(enc *gojay.Encoder) { if utils.IsIPv4(e.SrcAddr.IP) { enc.StringKey("ip_version", "ipv4") } else { enc.StringKey("ip_version", "ipv6") } enc.StringKey("src_ip", e.SrcAddr.IP.String()) enc.IntKey("src_port", e.SrcAddr.Port) enc.StringKey("dst_ip", e.DestAddr.IP.String()) enc.IntKey("dst_port", e.DestAddr.Port) enc.StringKey("src_cid", connectionID(e.SrcConnectionID).String()) enc.StringKey("dst_cid", connectionID(e.DestConnectionID).String()) } type eventVersionNegotiated struct { clientVersions, serverVersions []versionNumber chosenVersion versionNumber } func (e eventVersionNegotiated) Category() category { return categoryTransport } func (e eventVersionNegotiated) Name() string { return "version_information" } func (e eventVersionNegotiated) IsNil() bool { return false } func (e eventVersionNegotiated) MarshalJSONObject(enc *gojay.Encoder) { if len(e.clientVersions) > 0 { enc.ArrayKey("client_versions", versions(e.clientVersions)) } if len(e.serverVersions) > 0 { enc.ArrayKey("server_versions", versions(e.serverVersions)) } enc.StringKey("chosen_version", e.chosenVersion.String()) } type eventConnectionClosed struct { e error } func (e eventConnectionClosed) Category() category { return categoryTransport } func (e eventConnectionClosed) Name() string { return "connection_closed" } func (e eventConnectionClosed) IsNil() bool { return false } func (e eventConnectionClosed) MarshalJSONObject(enc *gojay.Encoder) { var ( statelessResetErr *quic.StatelessResetError handshakeTimeoutErr *quic.HandshakeTimeoutError idleTimeoutErr *quic.IdleTimeoutError applicationErr *quic.ApplicationError transportErr *quic.TransportError versionNegotiationErr *quic.VersionNegotiationError ) switch { case errors.As(e.e, &statelessResetErr): enc.StringKey("owner", ownerRemote.String()) enc.StringKey("trigger", "stateless_reset") enc.StringKey("stateless_reset_token", fmt.Sprintf("%x", statelessResetErr.Token)) case errors.As(e.e, &handshakeTimeoutErr): enc.StringKey("owner", ownerLocal.String()) enc.StringKey("trigger", "handshake_timeout") case errors.As(e.e, &idleTimeoutErr): enc.StringKey("owner", ownerLocal.String()) enc.StringKey("trigger", "idle_timeout") case errors.As(e.e, &applicationErr): owner := ownerLocal if applicationErr.Remote { owner = ownerRemote } enc.StringKey("owner", owner.String()) enc.Uint64Key("application_code", uint64(applicationErr.ErrorCode)) enc.StringKey("reason", applicationErr.ErrorMessage) case errors.As(e.e, &transportErr): owner := ownerLocal if transportErr.Remote { owner = ownerRemote } enc.StringKey("owner", owner.String()) enc.StringKey("connection_code", transportError(transportErr.ErrorCode).String()) enc.StringKey("reason", transportErr.ErrorMessage) case errors.As(e.e, &versionNegotiationErr): enc.StringKey("owner", ownerRemote.String()) enc.StringKey("trigger", "version_negotiation") } } type eventPacketSent struct { Header packetHeader Length logging.ByteCount PayloadLength logging.ByteCount Frames frames IsCoalesced bool Trigger string } var _ eventDetails = eventPacketSent{} func (e eventPacketSent) Category() category { return categoryTransport } func (e eventPacketSent) Name() string { return "packet_sent" } func (e eventPacketSent) IsNil() bool { return false } func (e eventPacketSent) MarshalJSONObject(enc *gojay.Encoder) { enc.ObjectKey("header", e.Header) enc.ObjectKey("raw", rawInfo{Length: e.Length, PayloadLength: e.PayloadLength}) enc.ArrayKeyOmitEmpty("frames", e.Frames) enc.BoolKeyOmitEmpty("is_coalesced", e.IsCoalesced) enc.StringKeyOmitEmpty("trigger", e.Trigger) } type eventPacketReceived struct { Header packetHeader Length logging.ByteCount PayloadLength logging.ByteCount Frames frames IsCoalesced bool Trigger string } var _ eventDetails = eventPacketReceived{} func (e eventPacketReceived) Category() category { return categoryTransport } func (e eventPacketReceived) Name() string { return "packet_received" } func (e eventPacketReceived) IsNil() bool { return false } func (e eventPacketReceived) MarshalJSONObject(enc *gojay.Encoder) { enc.ObjectKey("header", e.Header) enc.ObjectKey("raw", rawInfo{Length: e.Length, PayloadLength: e.PayloadLength}) enc.ArrayKeyOmitEmpty("frames", e.Frames) enc.BoolKeyOmitEmpty("is_coalesced", e.IsCoalesced) enc.StringKeyOmitEmpty("trigger", e.Trigger) } type eventRetryReceived struct { Header packetHeader } func (e eventRetryReceived) Category() category { return categoryTransport } func (e eventRetryReceived) Name() string { return "packet_received" } func (e eventRetryReceived) IsNil() bool { return false } func (e eventRetryReceived) MarshalJSONObject(enc *gojay.Encoder) { enc.ObjectKey("header", e.Header) } type eventVersionNegotiationReceived struct { Header packetHeader SupportedVersions []versionNumber } func (e eventVersionNegotiationReceived) Category() category { return categoryTransport } func (e eventVersionNegotiationReceived) Name() string { return "packet_received" } func (e eventVersionNegotiationReceived) IsNil() bool { return false } func (e eventVersionNegotiationReceived) MarshalJSONObject(enc *gojay.Encoder) { enc.ObjectKey("header", e.Header) enc.ArrayKey("supported_versions", versions(e.SupportedVersions)) } type eventPacketBuffered struct { PacketType logging.PacketType } func (e eventPacketBuffered) Category() category { return categoryTransport } func (e eventPacketBuffered) Name() string { return "packet_buffered" } func (e eventPacketBuffered) IsNil() bool { return false } func (e eventPacketBuffered) MarshalJSONObject(enc *gojay.Encoder) { //nolint:gosimple enc.ObjectKey("header", packetHeaderWithType{PacketType: e.PacketType}) enc.StringKey("trigger", "keys_unavailable") } type eventPacketDropped struct { PacketType logging.PacketType PacketSize protocol.ByteCount Trigger packetDropReason } func (e eventPacketDropped) Category() category { return categoryTransport } func (e eventPacketDropped) Name() string { return "packet_dropped" } func (e eventPacketDropped) IsNil() bool { return false } func (e eventPacketDropped) MarshalJSONObject(enc *gojay.Encoder) { enc.ObjectKey("header", packetHeaderWithType{PacketType: e.PacketType}) enc.ObjectKey("raw", rawInfo{Length: e.PacketSize}) enc.StringKey("trigger", e.Trigger.String()) } type metrics struct { MinRTT time.Duration SmoothedRTT time.Duration LatestRTT time.Duration RTTVariance time.Duration CongestionWindow protocol.ByteCount BytesInFlight protocol.ByteCount PacketsInFlight int } type eventMetricsUpdated struct { Last *metrics Current *metrics } func (e eventMetricsUpdated) Category() category { return categoryRecovery } func (e eventMetricsUpdated) Name() string { return "metrics_updated" } func (e eventMetricsUpdated) IsNil() bool { return false } func (e eventMetricsUpdated) MarshalJSONObject(enc *gojay.Encoder) { if e.Last == nil || e.Last.MinRTT != e.Current.MinRTT { enc.FloatKey("min_rtt", milliseconds(e.Current.MinRTT)) } if e.Last == nil || e.Last.SmoothedRTT != e.Current.SmoothedRTT { enc.FloatKey("smoothed_rtt", milliseconds(e.Current.SmoothedRTT)) } if e.Last == nil || e.Last.LatestRTT != e.Current.LatestRTT { enc.FloatKey("latest_rtt", milliseconds(e.Current.LatestRTT)) } if e.Last == nil || e.Last.RTTVariance != e.Current.RTTVariance { enc.FloatKey("rtt_variance", milliseconds(e.Current.RTTVariance)) } if e.Last == nil || e.Last.CongestionWindow != e.Current.CongestionWindow { enc.Uint64Key("congestion_window", uint64(e.Current.CongestionWindow)) } if e.Last == nil || e.Last.BytesInFlight != e.Current.BytesInFlight { enc.Uint64Key("bytes_in_flight", uint64(e.Current.BytesInFlight)) } if e.Last == nil || e.Last.PacketsInFlight != e.Current.PacketsInFlight { enc.Uint64KeyOmitEmpty("packets_in_flight", uint64(e.Current.PacketsInFlight)) } } type eventUpdatedPTO struct { Value uint32 } func (e eventUpdatedPTO) Category() category { return categoryRecovery } func (e eventUpdatedPTO) Name() string { return "metrics_updated" } func (e eventUpdatedPTO) IsNil() bool { return false } func (e eventUpdatedPTO) MarshalJSONObject(enc *gojay.Encoder) { enc.Uint32Key("pto_count", e.Value) } type eventPacketLost struct { PacketType logging.PacketType PacketNumber protocol.PacketNumber Trigger packetLossReason } func (e eventPacketLost) Category() category { return categoryRecovery } func (e eventPacketLost) Name() string { return "packet_lost" } func (e eventPacketLost) IsNil() bool { return false } func (e eventPacketLost) MarshalJSONObject(enc *gojay.Encoder) { enc.ObjectKey("header", packetHeaderWithTypeAndPacketNumber{ PacketType: e.PacketType, PacketNumber: e.PacketNumber, }) enc.StringKey("trigger", e.Trigger.String()) } type eventKeyUpdated struct { Trigger keyUpdateTrigger KeyType keyType Generation protocol.KeyPhase // we don't log the keys here, so we don't need `old` and `new`. } func (e eventKeyUpdated) Category() category { return categorySecurity } func (e eventKeyUpdated) Name() string { return "key_updated" } func (e eventKeyUpdated) IsNil() bool { return false } func (e eventKeyUpdated) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("trigger", e.Trigger.String()) enc.StringKey("key_type", e.KeyType.String()) if e.KeyType == keyTypeClient1RTT || e.KeyType == keyTypeServer1RTT { enc.Uint64Key("generation", uint64(e.Generation)) } } type eventKeyRetired struct { KeyType keyType Generation protocol.KeyPhase } func (e eventKeyRetired) Category() category { return categorySecurity } func (e eventKeyRetired) Name() string { return "key_retired" } func (e eventKeyRetired) IsNil() bool { return false } func (e eventKeyRetired) MarshalJSONObject(enc *gojay.Encoder) { if e.KeyType != keyTypeClient1RTT && e.KeyType != keyTypeServer1RTT { enc.StringKey("trigger", "tls") } enc.StringKey("key_type", e.KeyType.String()) if e.KeyType == keyTypeClient1RTT || e.KeyType == keyTypeServer1RTT { enc.Uint64Key("generation", uint64(e.Generation)) } } type eventTransportParameters struct { Restore bool Owner owner SentBy protocol.Perspective OriginalDestinationConnectionID protocol.ConnectionID InitialSourceConnectionID protocol.ConnectionID RetrySourceConnectionID *protocol.ConnectionID StatelessResetToken *protocol.StatelessResetToken DisableActiveMigration bool MaxIdleTimeout time.Duration MaxUDPPayloadSize protocol.ByteCount AckDelayExponent uint8 MaxAckDelay time.Duration ActiveConnectionIDLimit uint64 InitialMaxData protocol.ByteCount InitialMaxStreamDataBidiLocal protocol.ByteCount InitialMaxStreamDataBidiRemote protocol.ByteCount InitialMaxStreamDataUni protocol.ByteCount InitialMaxStreamsBidi int64 InitialMaxStreamsUni int64 PreferredAddress *preferredAddress MaxDatagramFrameSize protocol.ByteCount } func (e eventTransportParameters) Category() category { return categoryTransport } func (e eventTransportParameters) Name() string { if e.Restore { return "parameters_restored" } return "parameters_set" } func (e eventTransportParameters) IsNil() bool { return false } func (e eventTransportParameters) MarshalJSONObject(enc *gojay.Encoder) { if !e.Restore { enc.StringKey("owner", e.Owner.String()) if e.SentBy == protocol.PerspectiveServer { enc.StringKey("original_destination_connection_id", connectionID(e.OriginalDestinationConnectionID).String()) if e.StatelessResetToken != nil { enc.StringKey("stateless_reset_token", fmt.Sprintf("%x", e.StatelessResetToken[:])) } if e.RetrySourceConnectionID != nil { enc.StringKey("retry_source_connection_id", connectionID(*e.RetrySourceConnectionID).String()) } } enc.StringKey("initial_source_connection_id", connectionID(e.InitialSourceConnectionID).String()) } enc.BoolKey("disable_active_migration", e.DisableActiveMigration) enc.FloatKeyOmitEmpty("max_idle_timeout", milliseconds(e.MaxIdleTimeout)) enc.Int64KeyNullEmpty("max_udp_payload_size", int64(e.MaxUDPPayloadSize)) enc.Uint8KeyOmitEmpty("ack_delay_exponent", e.AckDelayExponent) enc.FloatKeyOmitEmpty("max_ack_delay", milliseconds(e.MaxAckDelay)) enc.Uint64KeyOmitEmpty("active_connection_id_limit", e.ActiveConnectionIDLimit) enc.Int64KeyOmitEmpty("initial_max_data", int64(e.InitialMaxData)) enc.Int64KeyOmitEmpty("initial_max_stream_data_bidi_local", int64(e.InitialMaxStreamDataBidiLocal)) enc.Int64KeyOmitEmpty("initial_max_stream_data_bidi_remote", int64(e.InitialMaxStreamDataBidiRemote)) enc.Int64KeyOmitEmpty("initial_max_stream_data_uni", int64(e.InitialMaxStreamDataUni)) enc.Int64KeyOmitEmpty("initial_max_streams_bidi", e.InitialMaxStreamsBidi) enc.Int64KeyOmitEmpty("initial_max_streams_uni", e.InitialMaxStreamsUni) if e.PreferredAddress != nil { enc.ObjectKey("preferred_address", e.PreferredAddress) } if e.MaxDatagramFrameSize != protocol.InvalidByteCount { enc.Int64Key("max_datagram_frame_size", int64(e.MaxDatagramFrameSize)) } } type preferredAddress struct { IPv4, IPv6 net.IP PortV4, PortV6 uint16 ConnectionID protocol.ConnectionID StatelessResetToken protocol.StatelessResetToken } var _ gojay.MarshalerJSONObject = &preferredAddress{} func (a preferredAddress) IsNil() bool { return false } func (a preferredAddress) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("ip_v4", a.IPv4.String()) enc.Uint16Key("port_v4", a.PortV4) enc.StringKey("ip_v6", a.IPv6.String()) enc.Uint16Key("port_v6", a.PortV6) enc.StringKey("connection_id", connectionID(a.ConnectionID).String()) enc.StringKey("stateless_reset_token", fmt.Sprintf("%x", a.StatelessResetToken)) } type eventLossTimerSet struct { TimerType timerType EncLevel protocol.EncryptionLevel Delta time.Duration } func (e eventLossTimerSet) Category() category { return categoryRecovery } func (e eventLossTimerSet) Name() string { return "loss_timer_updated" } func (e eventLossTimerSet) IsNil() bool { return false } func (e eventLossTimerSet) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("event_type", "set") enc.StringKey("timer_type", e.TimerType.String()) enc.StringKey("packet_number_space", encLevelToPacketNumberSpace(e.EncLevel)) enc.Float64Key("delta", milliseconds(e.Delta)) } type eventLossTimerExpired struct { TimerType timerType EncLevel protocol.EncryptionLevel } func (e eventLossTimerExpired) Category() category { return categoryRecovery } func (e eventLossTimerExpired) Name() string { return "loss_timer_updated" } func (e eventLossTimerExpired) IsNil() bool { return false } func (e eventLossTimerExpired) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("event_type", "expired") enc.StringKey("timer_type", e.TimerType.String()) enc.StringKey("packet_number_space", encLevelToPacketNumberSpace(e.EncLevel)) } type eventLossTimerCanceled struct{} func (e eventLossTimerCanceled) Category() category { return categoryRecovery } func (e eventLossTimerCanceled) Name() string { return "loss_timer_updated" } func (e eventLossTimerCanceled) IsNil() bool { return false } func (e eventLossTimerCanceled) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("event_type", "cancelled") } type eventCongestionStateUpdated struct { state congestionState } func (e eventCongestionStateUpdated) Category() category { return categoryRecovery } func (e eventCongestionStateUpdated) Name() string { return "congestion_state_updated" } func (e eventCongestionStateUpdated) IsNil() bool { return false } func (e eventCongestionStateUpdated) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("new", e.state.String()) } type eventGeneric struct { name string msg string } func (e eventGeneric) Category() category { return categoryTransport } func (e eventGeneric) Name() string { return e.name } func (e eventGeneric) IsNil() bool { return false } func (e eventGeneric) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("details", e.msg) }