]> git.feebdaed.xyz Git - 0xmirror/quic-go.git/commitdiff
qlog: implement a minimal jsontext-like JSON encoder (#5353)
authorMarten Seemann <martenseemann@gmail.com>
Mon, 6 Oct 2025 04:48:40 +0000 (12:48 +0800)
committerGitHub <noreply@github.com>
Mon, 6 Oct 2025 04:48:40 +0000 (06:48 +0200)
* qlog: use fork of encoding/json/jsontext instead of unmaintained gojay

* implement a minimal jsontext-compatible encoder

* qlogtext: improve fuzz test

* qlog: simplify JSON encoding error handling

* qlog: make use of jsontext.Bool

14 files changed:
go.mod
go.sum
qlog/connection_tracer.go
qlog/event.go
qlog/event_test.go
qlog/frame.go
qlog/frame_test.go
qlog/json_helper_test.go
qlog/jsontext/encoder.go [new file with mode: 0644]
qlog/jsontext/encoder_test.go [new file with mode: 0644]
qlog/packet_header.go
qlog/packet_header_test.go
qlog/trace.go
qlog/writer.go

diff --git a/go.mod b/go.mod
index 5b398544aba9d752385d26c080788978386bbdaf..3ca69a0e407f7d68d29874ad41926e1e705630bd 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,6 @@ module github.com/quic-go/quic-go
 go 1.24
 
 require (
-       github.com/francoispqt/gojay v1.2.13
        github.com/prometheus/client_golang v1.19.1
        github.com/quic-go/qpack v0.5.1
        github.com/stretchr/testify v1.9.0
@@ -19,6 +18,7 @@ require (
        github.com/beorn7/perks v1.0.1 // indirect
        github.com/cespare/xxhash/v2 v2.2.0 // indirect
        github.com/davecgh/go-spew v1.1.1 // indirect
+       github.com/kr/text v0.1.0 // indirect
        github.com/pmezard/go-difflib v1.0.0 // indirect
        github.com/prometheus/client_model v0.5.0 // indirect
        github.com/prometheus/common v0.48.0 // indirect
diff --git a/go.sum b/go.sum
index 7c6be70b1c405750ca5ca44ac92e16815eb4d8a6..9828a4e200971e11e9cd0433e1d2332287f23f6f 100644 (file)
--- a/go.sum
+++ b/go.sum
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
-dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
-dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
-dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
-dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
-git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
-github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
-github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
-github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
-github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
-github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
-github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
-github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
-github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
-github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
-github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
 github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
 github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
 github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
-github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
 github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
-github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
-github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
-github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
-github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
-github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
-github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
-github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
-github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
-github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
-github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
-github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
-github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
-github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
-github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
-github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
-github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
-github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
-github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
-github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
-github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
-github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
-github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
-github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
-github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
-github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
-go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
 go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
 go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
-go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
-golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
-golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
 golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
 golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
 golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
 golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
 golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
 golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
 golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
-google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
-google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
-google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
-google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
-google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
 google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
-sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
index 1761b41cbb492ecaa921471890eccff99f508c78..d78035caeb5b08094a3a29593cc543c8d382abda 100644 (file)
@@ -9,8 +9,6 @@ import (
        "github.com/quic-go/quic-go/internal/utils"
        "github.com/quic-go/quic-go/internal/wire"
        "github.com/quic-go/quic-go/logging"
-
-       "github.com/francoispqt/gojay"
 )
 
 type connectionTracer struct {
@@ -255,7 +253,7 @@ func (t *connectionTracer) SentShortHeaderPacket(
 }
 
 func (t *connectionTracer) sentPacket(
-       hdr gojay.MarshalerJSONObject,
+       hdr jsontextEncoder,
        size, payloadLen logging.ByteCount,
        ecn logging.ECN,
        ack *logging.AckFrame,
index 4490d69bbdfdae425c86f70afdfbc1c8a7699417..0c35b63a1c4fcf99bb957aee6479caa09781665c 100644 (file)
@@ -10,15 +10,14 @@ import (
        "github.com/quic-go/quic-go"
        "github.com/quic-go/quic-go/internal/protocol"
        "github.com/quic-go/quic-go/logging"
-
-       "github.com/francoispqt/gojay"
+       "github.com/quic-go/quic-go/qlog/jsontext"
 )
 
 func milliseconds(dur time.Duration) float64 { return float64(dur.Nanoseconds()) / 1e6 }
 
 type eventDetails interface {
        Name() string
-       gojay.MarshalerJSONObject
+       Encode(*jsontext.Encoder) error
 }
 
 type event struct {
@@ -26,22 +25,47 @@ type event struct {
        eventDetails
 }
 
-var _ gojay.MarshalerJSONObject = event{}
+type jsontextEncoder interface {
+       Encode(*jsontext.Encoder) error
+}
+
+type encoderHelper struct {
+       enc *jsontext.Encoder
+       err error
+}
 
-func (e event) IsNil() bool { return false }
-func (e event) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.Float64Key("time", milliseconds(e.RelativeTime))
-       enc.StringKey("name", e.Name())
-       enc.ObjectKey("data", e.eventDetails)
+func (h *encoderHelper) WriteToken(t jsontext.Token) {
+       if h.err != nil {
+               return
+       }
+       h.err = h.enc.WriteToken(t)
+}
+
+func (e event) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("time"))
+       h.WriteToken(jsontext.Float(milliseconds(e.RelativeTime)))
+       h.WriteToken(jsontext.String("name"))
+       h.WriteToken(jsontext.String(e.Name()))
+       h.WriteToken(jsontext.String("data"))
+       if err := e.eventDetails.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type versions []version
 
-func (v versions) IsNil() bool { return false }
-func (v versions) MarshalJSONArray(enc *gojay.Encoder) {
+func (v versions) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginArray)
        for _, e := range v {
-               enc.AddString(e.String())
+               h.WriteToken(jsontext.String(e.String()))
        }
+       h.WriteToken(jsontext.EndArray)
+       return h.err
 }
 
 type rawInfo struct {
@@ -49,37 +73,52 @@ type rawInfo struct {
        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))
+func (i rawInfo) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("length"))
+       h.WriteToken(jsontext.Uint(uint64(i.Length)))
+       if i.PayloadLength != 0 {
+               h.WriteToken(jsontext.String("payload_length"))
+               h.WriteToken(jsontext.Uint(uint64(i.PayloadLength)))
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventConnectionStarted struct {
-       SrcAddr  *net.UDPAddr
-       DestAddr *net.UDPAddr
-
+       SrcAddr          *net.UDPAddr
+       DestAddr         *net.UDPAddr
        SrcConnectionID  protocol.ConnectionID
        DestConnectionID protocol.ConnectionID
 }
 
-var _ eventDetails = &eventConnectionStarted{}
-
 func (e eventConnectionStarted) Name() string { return "transport:connection_started" }
-func (e eventConnectionStarted) IsNil() bool  { return false }
 
-func (e eventConnectionStarted) MarshalJSONObject(enc *gojay.Encoder) {
+func (e eventConnectionStarted) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        if e.SrcAddr.IP.To4() != nil {
-               enc.StringKey("ip_version", "ipv4")
+               h.WriteToken(jsontext.String("ip_version"))
+               h.WriteToken(jsontext.String("ipv4"))
        } else {
-               enc.StringKey("ip_version", "ipv6")
+               h.WriteToken(jsontext.String("ip_version"))
+               h.WriteToken(jsontext.String("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", e.SrcConnectionID.String())
-       enc.StringKey("dst_cid", e.DestConnectionID.String())
+       h.WriteToken(jsontext.String("src_ip"))
+       h.WriteToken(jsontext.String(e.SrcAddr.IP.String()))
+       h.WriteToken(jsontext.String("src_port"))
+       h.WriteToken(jsontext.Int(int64(e.SrcAddr.Port)))
+       h.WriteToken(jsontext.String("dst_ip"))
+       h.WriteToken(jsontext.String(e.DestAddr.IP.String()))
+       h.WriteToken(jsontext.String("dst_port"))
+       h.WriteToken(jsontext.Int(int64(e.DestAddr.Port)))
+       h.WriteToken(jsontext.String("src_cid"))
+       h.WriteToken(jsontext.String(e.SrcConnectionID.String()))
+       h.WriteToken(jsontext.String("dst_cid"))
+       h.WriteToken(jsontext.String(e.DestConnectionID.String()))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventVersionNegotiated struct {
@@ -88,16 +127,26 @@ type eventVersionNegotiated struct {
 }
 
 func (e eventVersionNegotiated) Name() string { return "transport:version_information" }
-func (e eventVersionNegotiated) IsNil() bool  { return false }
 
-func (e eventVersionNegotiated) MarshalJSONObject(enc *gojay.Encoder) {
+func (e eventVersionNegotiated) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        if len(e.clientVersions) > 0 {
-               enc.ArrayKey("client_versions", versions(e.clientVersions))
+               h.WriteToken(jsontext.String("client_versions"))
+               if err := versions(e.clientVersions).Encode(enc); err != nil {
+                       return err
+               }
        }
        if len(e.serverVersions) > 0 {
-               enc.ArrayKey("server_versions", versions(e.serverVersions))
+               h.WriteToken(jsontext.String("server_versions"))
+               if err := versions(e.serverVersions).Encode(enc); err != nil {
+                       return err
+               }
        }
-       enc.StringKey("chosen_version", e.chosenVersion.String())
+       h.WriteToken(jsontext.String("chosen_version"))
+       h.WriteToken(jsontext.String(e.chosenVersion.String()))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventConnectionClosed struct {
@@ -105,9 +154,10 @@ type eventConnectionClosed struct {
 }
 
 func (e eventConnectionClosed) Name() string { return "transport:connection_closed" }
-func (e eventConnectionClosed) IsNil() bool  { return false }
 
-func (e eventConnectionClosed) MarshalJSONObject(enc *gojay.Encoder) {
+func (e eventConnectionClosed) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        var (
                statelessResetErr     *quic.StatelessResetError
                handshakeTimeoutErr   *quic.HandshakeTimeoutError
@@ -118,37 +168,52 @@ func (e eventConnectionClosed) MarshalJSONObject(enc *gojay.Encoder) {
        )
        switch {
        case errors.As(e.e, &statelessResetErr):
-               enc.StringKey("owner", ownerRemote.String())
-               enc.StringKey("trigger", "stateless_reset")
+               h.WriteToken(jsontext.String("owner"))
+               h.WriteToken(jsontext.String(ownerRemote.String()))
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String("stateless_reset"))
        case errors.As(e.e, &handshakeTimeoutErr):
-               enc.StringKey("owner", ownerLocal.String())
-               enc.StringKey("trigger", "handshake_timeout")
+               h.WriteToken(jsontext.String("owner"))
+               h.WriteToken(jsontext.String(ownerLocal.String()))
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String("handshake_timeout"))
        case errors.As(e.e, &idleTimeoutErr):
-               enc.StringKey("owner", ownerLocal.String())
-               enc.StringKey("trigger", "idle_timeout")
+               h.WriteToken(jsontext.String("owner"))
+               h.WriteToken(jsontext.String(ownerLocal.String()))
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String("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)
+               h.WriteToken(jsontext.String("owner"))
+               h.WriteToken(jsontext.String(owner.String()))
+               h.WriteToken(jsontext.String("application_code"))
+               h.WriteToken(jsontext.Uint(uint64(applicationErr.ErrorCode)))
+               h.WriteToken(jsontext.String("reason"))
+               h.WriteToken(jsontext.String(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)
+               h.WriteToken(jsontext.String("owner"))
+               h.WriteToken(jsontext.String(owner.String()))
+               h.WriteToken(jsontext.String("connection_code"))
+               h.WriteToken(jsontext.String(transportError(transportErr.ErrorCode).String()))
+               h.WriteToken(jsontext.String("reason"))
+               h.WriteToken(jsontext.String(transportErr.ErrorMessage))
        case errors.As(e.e, &versionNegotiationErr):
-               enc.StringKey("trigger", "version_mismatch")
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String("version_mismatch"))
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventPacketSent struct {
-       Header        gojay.MarshalerJSONObject // either a shortHeader or a packetHeader
+       Header        jsontextEncoder // either a shortHeader or a packetHeader
        Length        logging.ByteCount
        PayloadLength logging.ByteCount
        Frames        frames
@@ -157,24 +222,43 @@ type eventPacketSent struct {
        Trigger       string
 }
 
-var _ eventDetails = eventPacketSent{}
-
 func (e eventPacketSent) Name() string { return "transport: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)
+func (e eventPacketSent) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := e.Header.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("raw"))
+       if err := (rawInfo{Length: e.Length, PayloadLength: e.PayloadLength}).Encode(enc); err != nil {
+               return err
+       }
+       if len(e.Frames) > 0 {
+               h.WriteToken(jsontext.String("frames"))
+               if err := e.Frames.Encode(enc); err != nil {
+                       return err
+               }
+       }
+       if e.IsCoalesced {
+               h.WriteToken(jsontext.String("is_coalesced"))
+               h.WriteToken(jsontext.True)
+       }
        if e.ECN != logging.ECNUnsupported {
-               enc.StringKey("ecn", ecn(e.ECN).String())
+               h.WriteToken(jsontext.String("ecn"))
+               h.WriteToken(jsontext.String(ecn(e.ECN).String()))
        }
-       enc.StringKeyOmitEmpty("trigger", e.Trigger)
+       if e.Trigger != "" {
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String(e.Trigger))
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventPacketReceived struct {
-       Header        gojay.MarshalerJSONObject // either a shortHeader or a packetHeader
+       Header        jsontextEncoder // either a shortHeader or a packetHeader
        Length        logging.ByteCount
        PayloadLength logging.ByteCount
        Frames        frames
@@ -183,20 +267,39 @@ type eventPacketReceived struct {
        Trigger       string
 }
 
-var _ eventDetails = eventPacketReceived{}
-
 func (e eventPacketReceived) Name() string { return "transport: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)
+func (e eventPacketReceived) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := e.Header.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("raw"))
+       if err := (rawInfo{Length: e.Length, PayloadLength: e.PayloadLength}).Encode(enc); err != nil {
+               return err
+       }
+       if len(e.Frames) > 0 {
+               h.WriteToken(jsontext.String("frames"))
+               if err := e.Frames.Encode(enc); err != nil {
+                       return err
+               }
+       }
+       if e.IsCoalesced {
+               h.WriteToken(jsontext.String("is_coalesced"))
+               h.WriteToken(jsontext.True)
+       }
        if e.ECN != logging.ECNUnsupported {
-               enc.StringKey("ecn", ecn(e.ECN).String())
+               h.WriteToken(jsontext.String("ecn"))
+               h.WriteToken(jsontext.String(ecn(e.ECN).String()))
+       }
+       if e.Trigger != "" {
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String(e.Trigger))
        }
-       enc.StringKeyOmitEmpty("trigger", e.Trigger)
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventRetryReceived struct {
@@ -204,10 +307,16 @@ type eventRetryReceived struct {
 }
 
 func (e eventRetryReceived) Name() string { return "transport:packet_received" }
-func (e eventRetryReceived) IsNil() bool  { return false }
 
-func (e eventRetryReceived) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.ObjectKey("header", e.Header)
+func (e eventRetryReceived) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := e.Header.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventVersionNegotiationReceived struct {
@@ -216,11 +325,20 @@ type eventVersionNegotiationReceived struct {
 }
 
 func (e eventVersionNegotiationReceived) Name() string { return "transport: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))
+func (e eventVersionNegotiationReceived) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := e.Header.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("supported_versions"))
+       if err := versions(e.SupportedVersions).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventVersionNegotiationSent struct {
@@ -229,11 +347,20 @@ type eventVersionNegotiationSent struct {
 }
 
 func (e eventVersionNegotiationSent) Name() string { return "transport:packet_sent" }
-func (e eventVersionNegotiationSent) IsNil() bool  { return false }
 
-func (e eventVersionNegotiationSent) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.ObjectKey("header", e.Header)
-       enc.ArrayKey("supported_versions", versions(e.SupportedVersions))
+func (e eventVersionNegotiationSent) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := e.Header.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("supported_versions"))
+       if err := versions(e.SupportedVersions).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventPacketBuffered struct {
@@ -242,13 +369,25 @@ type eventPacketBuffered struct {
 }
 
 func (e eventPacketBuffered) Name() string { return "transport: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, PacketNumber: protocol.InvalidPacketNumber})
-       enc.ObjectKey("raw", rawInfo{Length: e.PacketSize})
-       enc.StringKey("trigger", "keys_unavailable")
+func (e eventPacketBuffered) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := (packetHeaderWithType{
+               PacketType:   e.PacketType,
+               PacketNumber: protocol.InvalidPacketNumber,
+       }).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("raw"))
+       if err := (rawInfo{Length: e.PacketSize}).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("trigger"))
+       h.WriteToken(jsontext.String("keys_unavailable"))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventPacketDropped struct {
@@ -259,23 +398,32 @@ type eventPacketDropped struct {
 }
 
 func (e eventPacketDropped) Name() string { return "transport:packet_dropped" }
-func (e eventPacketDropped) IsNil() bool  { return false }
 
-func (e eventPacketDropped) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.ObjectKey("header", packetHeaderWithType{
+func (e eventPacketDropped) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := (packetHeaderWithType{
                PacketType:   e.PacketType,
                PacketNumber: e.PacketNumber,
-       })
-       enc.ObjectKey("raw", rawInfo{Length: e.PacketSize})
-       enc.StringKey("trigger", e.Trigger.String())
+       }).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("raw"))
+       if err := (rawInfo{Length: e.PacketSize}).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("trigger"))
+       h.WriteToken(jsontext.String(e.Trigger.String()))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type metrics struct {
-       MinRTT      time.Duration
-       SmoothedRTT time.Duration
-       LatestRTT   time.Duration
-       RTTVariance time.Duration
-
+       MinRTT           time.Duration
+       SmoothedRTT      time.Duration
+       LatestRTT        time.Duration
+       RTTVariance      time.Duration
        CongestionWindow protocol.ByteCount
        BytesInFlight    protocol.ByteCount
        PacketsInFlight  int
@@ -287,11 +435,16 @@ type eventMTUUpdated struct {
 }
 
 func (e eventMTUUpdated) Name() string { return "recovery:mtu_updated" }
-func (e eventMTUUpdated) IsNil() bool  { return false }
 
-func (e eventMTUUpdated) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.Uint64Key("mtu", uint64(e.mtu))
-       enc.BoolKey("done", e.done)
+func (e eventMTUUpdated) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("mtu"))
+       h.WriteToken(jsontext.Uint(uint64(e.mtu)))
+       h.WriteToken(jsontext.String("done"))
+       h.WriteToken(jsontext.Bool(e.done))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventMetricsUpdated struct {
@@ -300,31 +453,40 @@ type eventMetricsUpdated struct {
 }
 
 func (e eventMetricsUpdated) Name() string { return "recovery:metrics_updated" }
-func (e eventMetricsUpdated) IsNil() bool  { return false }
 
-func (e eventMetricsUpdated) MarshalJSONObject(enc *gojay.Encoder) {
+func (e eventMetricsUpdated) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        if e.Last == nil || e.Last.MinRTT != e.Current.MinRTT {
-               enc.FloatKey("min_rtt", milliseconds(e.Current.MinRTT))
+               h.WriteToken(jsontext.String("min_rtt"))
+               h.WriteToken(jsontext.Float(milliseconds(e.Current.MinRTT)))
        }
        if e.Last == nil || e.Last.SmoothedRTT != e.Current.SmoothedRTT {
-               enc.FloatKey("smoothed_rtt", milliseconds(e.Current.SmoothedRTT))
+               h.WriteToken(jsontext.String("smoothed_rtt"))
+               h.WriteToken(jsontext.Float(milliseconds(e.Current.SmoothedRTT)))
        }
        if e.Last == nil || e.Last.LatestRTT != e.Current.LatestRTT {
-               enc.FloatKey("latest_rtt", milliseconds(e.Current.LatestRTT))
+               h.WriteToken(jsontext.String("latest_rtt"))
+               h.WriteToken(jsontext.Float(milliseconds(e.Current.LatestRTT)))
        }
        if e.Last == nil || e.Last.RTTVariance != e.Current.RTTVariance {
-               enc.FloatKey("rtt_variance", milliseconds(e.Current.RTTVariance))
+               h.WriteToken(jsontext.String("rtt_variance"))
+               h.WriteToken(jsontext.Float(milliseconds(e.Current.RTTVariance)))
        }
-
        if e.Last == nil || e.Last.CongestionWindow != e.Current.CongestionWindow {
-               enc.Uint64Key("congestion_window", uint64(e.Current.CongestionWindow))
+               h.WriteToken(jsontext.String("congestion_window"))
+               h.WriteToken(jsontext.Uint(uint64(e.Current.CongestionWindow)))
        }
        if e.Last == nil || e.Last.BytesInFlight != e.Current.BytesInFlight {
-               enc.Uint64Key("bytes_in_flight", uint64(e.Current.BytesInFlight))
+               h.WriteToken(jsontext.String("bytes_in_flight"))
+               h.WriteToken(jsontext.Uint(uint64(e.Current.BytesInFlight)))
        }
        if e.Last == nil || e.Last.PacketsInFlight != e.Current.PacketsInFlight {
-               enc.Uint64Key("packets_in_flight", uint64(e.Current.PacketsInFlight))
+               h.WriteToken(jsontext.String("packets_in_flight"))
+               h.WriteToken(jsontext.Uint(uint64(e.Current.PacketsInFlight)))
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventUpdatedPTO struct {
@@ -332,10 +494,14 @@ type eventUpdatedPTO struct {
 }
 
 func (e eventUpdatedPTO) Name() string { return "recovery:metrics_updated" }
-func (e eventUpdatedPTO) IsNil() bool  { return false }
 
-func (e eventUpdatedPTO) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.Uint32Key("pto_count", e.Value)
+func (e eventUpdatedPTO) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("pto_count"))
+       h.WriteToken(jsontext.Uint(uint64(e.Value)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventPacketLost struct {
@@ -345,14 +511,21 @@ type eventPacketLost struct {
 }
 
 func (e eventPacketLost) Name() string { return "recovery:packet_lost" }
-func (e eventPacketLost) IsNil() bool  { return false }
 
-func (e eventPacketLost) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.ObjectKey("header", packetHeaderWithTypeAndPacketNumber{
+func (e eventPacketLost) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("header"))
+       if err := (packetHeaderWithTypeAndPacketNumber{
                PacketType:   e.PacketType,
                PacketNumber: e.PacketNumber,
-       })
-       enc.StringKey("trigger", e.Trigger.String())
+       }).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("trigger"))
+       h.WriteToken(jsontext.String(e.Trigger.String()))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventSpuriousLoss struct {
@@ -363,13 +536,20 @@ type eventSpuriousLoss struct {
 }
 
 func (e eventSpuriousLoss) Name() string { return "recovery:spurious_loss" }
-func (e eventSpuriousLoss) IsNil() bool  { return false }
 
-func (e eventSpuriousLoss) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("packet_number_space", encLevelToPacketNumberSpace(e.EncLevel))
-       enc.Uint64Key("packet_number", uint64(e.PacketNumber))
-       enc.Uint64Key("reordering_packets", e.Reordering)
-       enc.Float64Key("reordering_time", milliseconds(e.Duration))
+func (e eventSpuriousLoss) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("packet_number_space"))
+       h.WriteToken(jsontext.String(encLevelToPacketNumberSpace(e.EncLevel)))
+       h.WriteToken(jsontext.String("packet_number"))
+       h.WriteToken(jsontext.Uint(uint64(e.PacketNumber)))
+       h.WriteToken(jsontext.String("reordering_packets"))
+       h.WriteToken(jsontext.Uint(e.Reordering))
+       h.WriteToken(jsontext.String("reordering_time"))
+       h.WriteToken(jsontext.Float(milliseconds(e.Duration)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventKeyUpdated struct {
@@ -380,14 +560,20 @@ type eventKeyUpdated struct {
 }
 
 func (e eventKeyUpdated) Name() string { return "security: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())
+func (e eventKeyUpdated) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("trigger"))
+       h.WriteToken(jsontext.String(e.Trigger.String()))
+       h.WriteToken(jsontext.String("key_type"))
+       h.WriteToken(jsontext.String(e.KeyType.String()))
        if e.KeyType == keyTypeClient1RTT || e.KeyType == keyTypeServer1RTT {
-               enc.Uint64Key("key_phase", uint64(e.KeyPhase))
+               h.WriteToken(jsontext.String("key_phase"))
+               h.WriteToken(jsontext.Uint(uint64(e.KeyPhase)))
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventKeyDiscarded struct {
@@ -396,46 +582,47 @@ type eventKeyDiscarded struct {
 }
 
 func (e eventKeyDiscarded) Name() string { return "security:key_discarded" }
-func (e eventKeyDiscarded) IsNil() bool  { return false }
 
-func (e eventKeyDiscarded) MarshalJSONObject(enc *gojay.Encoder) {
+func (e eventKeyDiscarded) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        if e.KeyType != keyTypeClient1RTT && e.KeyType != keyTypeServer1RTT {
-               enc.StringKey("trigger", "tls")
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String("tls"))
        }
-       enc.StringKey("key_type", e.KeyType.String())
+       h.WriteToken(jsontext.String("key_type"))
+       h.WriteToken(jsontext.String(e.KeyType.String()))
        if e.KeyType == keyTypeClient1RTT || e.KeyType == keyTypeServer1RTT {
-               enc.Uint64Key("key_phase", uint64(e.KeyPhase))
+               h.WriteToken(jsontext.String("key_phase"))
+               h.WriteToken(jsontext.Uint(uint64(e.KeyPhase)))
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventTransportParameters struct {
-       Restore bool
-       Owner   owner
-       SentBy  protocol.Perspective
-
+       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
-       EnableResetStreamAt  bool
+       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
+       EnableResetStreamAt             bool
 }
 
 func (e eventTransportParameters) Name() string {
@@ -445,45 +632,89 @@ func (e eventTransportParameters) Name() string {
        return "transport:parameters_set"
 }
 
-func (e eventTransportParameters) IsNil() bool { return false }
-
-func (e eventTransportParameters) MarshalJSONObject(enc *gojay.Encoder) {
+func (e eventTransportParameters) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        if !e.Restore {
-               enc.StringKey("owner", e.Owner.String())
+               h.WriteToken(jsontext.String("owner"))
+               h.WriteToken(jsontext.String(e.Owner.String()))
                if e.SentBy == protocol.PerspectiveServer {
-                       enc.StringKey("original_destination_connection_id", e.OriginalDestinationConnectionID.String())
+                       h.WriteToken(jsontext.String("original_destination_connection_id"))
+                       h.WriteToken(jsontext.String(e.OriginalDestinationConnectionID.String()))
                        if e.StatelessResetToken != nil {
-                               enc.StringKey("stateless_reset_token", fmt.Sprintf("%x", e.StatelessResetToken[:]))
+                               h.WriteToken(jsontext.String("stateless_reset_token"))
+                               h.WriteToken(jsontext.String(fmt.Sprintf("%x", e.StatelessResetToken[:])))
                        }
                        if e.RetrySourceConnectionID != nil {
-                               enc.StringKey("retry_source_connection_id", (*e.RetrySourceConnectionID).String())
+                               h.WriteToken(jsontext.String("retry_source_connection_id"))
+                               h.WriteToken(jsontext.String((*e.RetrySourceConnectionID).String()))
                        }
                }
-               enc.StringKey("initial_source_connection_id", 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)
-
+               h.WriteToken(jsontext.String("initial_source_connection_id"))
+               h.WriteToken(jsontext.String(e.InitialSourceConnectionID.String()))
+       }
+       h.WriteToken(jsontext.String("disable_active_migration"))
+       h.WriteToken(jsontext.Bool(e.DisableActiveMigration))
+       if e.MaxIdleTimeout != 0 {
+               h.WriteToken(jsontext.String("max_idle_timeout"))
+               h.WriteToken(jsontext.Float(milliseconds(e.MaxIdleTimeout)))
+       }
+       if e.MaxUDPPayloadSize != 0 {
+               h.WriteToken(jsontext.String("max_udp_payload_size"))
+               h.WriteToken(jsontext.Int(int64(e.MaxUDPPayloadSize)))
+       }
+       if e.AckDelayExponent != 0 {
+               h.WriteToken(jsontext.String("ack_delay_exponent"))
+               h.WriteToken(jsontext.Uint(uint64(e.AckDelayExponent)))
+       }
+       if e.MaxAckDelay != 0 {
+               h.WriteToken(jsontext.String("max_ack_delay"))
+               h.WriteToken(jsontext.Float(milliseconds(e.MaxAckDelay)))
+       }
+       if e.ActiveConnectionIDLimit != 0 {
+               h.WriteToken(jsontext.String("active_connection_id_limit"))
+               h.WriteToken(jsontext.Uint(e.ActiveConnectionIDLimit))
+       }
+       if e.InitialMaxData != 0 {
+               h.WriteToken(jsontext.String("initial_max_data"))
+               h.WriteToken(jsontext.Int(int64(e.InitialMaxData)))
+       }
+       if e.InitialMaxStreamDataBidiLocal != 0 {
+               h.WriteToken(jsontext.String("initial_max_stream_data_bidi_local"))
+               h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataBidiLocal)))
+       }
+       if e.InitialMaxStreamDataBidiRemote != 0 {
+               h.WriteToken(jsontext.String("initial_max_stream_data_bidi_remote"))
+               h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataBidiRemote)))
+       }
+       if e.InitialMaxStreamDataUni != 0 {
+               h.WriteToken(jsontext.String("initial_max_stream_data_uni"))
+               h.WriteToken(jsontext.Int(int64(e.InitialMaxStreamDataUni)))
+       }
+       if e.InitialMaxStreamsBidi != 0 {
+               h.WriteToken(jsontext.String("initial_max_streams_bidi"))
+               h.WriteToken(jsontext.Int(e.InitialMaxStreamsBidi))
+       }
+       if e.InitialMaxStreamsUni != 0 {
+               h.WriteToken(jsontext.String("initial_max_streams_uni"))
+               h.WriteToken(jsontext.Int(e.InitialMaxStreamsUni))
+       }
        if e.PreferredAddress != nil {
-               enc.ObjectKey("preferred_address", e.PreferredAddress)
+               h.WriteToken(jsontext.String("preferred_address"))
+               if err := e.PreferredAddress.Encode(enc); err != nil {
+                       return err
+               }
        }
        if e.MaxDatagramFrameSize != protocol.InvalidByteCount {
-               enc.Int64Key("max_datagram_frame_size", int64(e.MaxDatagramFrameSize))
+               h.WriteToken(jsontext.String("max_datagram_frame_size"))
+               h.WriteToken(jsontext.Int(int64(e.MaxDatagramFrameSize)))
        }
        if e.EnableResetStreamAt {
-               enc.BoolKey("reset_stream_at", true)
+               h.WriteToken(jsontext.String("reset_stream_at"))
+               h.WriteToken(jsontext.True)
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type preferredAddress struct {
@@ -492,20 +723,27 @@ type preferredAddress struct {
        StatelessResetToken protocol.StatelessResetToken
 }
 
-var _ gojay.MarshalerJSONObject = &preferredAddress{}
-
-func (a preferredAddress) IsNil() bool { return false }
-func (a preferredAddress) MarshalJSONObject(enc *gojay.Encoder) {
+func (a preferredAddress) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        if a.IPv4.IsValid() {
-               enc.StringKey("ip_v4", a.IPv4.Addr().String())
-               enc.Uint16Key("port_v4", a.IPv4.Port())
+               h.WriteToken(jsontext.String("ip_v4"))
+               h.WriteToken(jsontext.String(a.IPv4.Addr().String()))
+               h.WriteToken(jsontext.String("port_v4"))
+               h.WriteToken(jsontext.Uint(uint64(a.IPv4.Port())))
        }
        if a.IPv6.IsValid() {
-               enc.StringKey("ip_v6", a.IPv6.Addr().String())
-               enc.Uint16Key("port_v6", a.IPv6.Port())
+               h.WriteToken(jsontext.String("ip_v6"))
+               h.WriteToken(jsontext.String(a.IPv6.Addr().String()))
+               h.WriteToken(jsontext.String("port_v6"))
+               h.WriteToken(jsontext.Uint(uint64(a.IPv6.Port())))
        }
-       enc.StringKey("connection_id", a.ConnectionID.String())
-       enc.StringKey("stateless_reset_token", fmt.Sprintf("%x", a.StatelessResetToken))
+       h.WriteToken(jsontext.String("connection_id"))
+       h.WriteToken(jsontext.String(a.ConnectionID.String()))
+       h.WriteToken(jsontext.String("stateless_reset_token"))
+       h.WriteToken(jsontext.String(fmt.Sprintf("%x", a.StatelessResetToken)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventLossTimerSet struct {
@@ -515,13 +753,20 @@ type eventLossTimerSet struct {
 }
 
 func (e eventLossTimerSet) Name() string { return "recovery: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))
+func (e eventLossTimerSet) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("event_type"))
+       h.WriteToken(jsontext.String("set"))
+       h.WriteToken(jsontext.String("timer_type"))
+       h.WriteToken(jsontext.String(e.TimerType.String()))
+       h.WriteToken(jsontext.String("packet_number_space"))
+       h.WriteToken(jsontext.String(encLevelToPacketNumberSpace(e.EncLevel)))
+       h.WriteToken(jsontext.String("delta"))
+       h.WriteToken(jsontext.Float(milliseconds(e.Delta)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventLossTimerExpired struct {
@@ -530,21 +775,31 @@ type eventLossTimerExpired struct {
 }
 
 func (e eventLossTimerExpired) Name() string { return "recovery: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))
+func (e eventLossTimerExpired) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("event_type"))
+       h.WriteToken(jsontext.String("expired"))
+       h.WriteToken(jsontext.String("timer_type"))
+       h.WriteToken(jsontext.String(e.TimerType.String()))
+       h.WriteToken(jsontext.String("packet_number_space"))
+       h.WriteToken(jsontext.String(encLevelToPacketNumberSpace(e.EncLevel)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventLossTimerCanceled struct{}
 
 func (e eventLossTimerCanceled) Name() string { return "recovery:loss_timer_updated" }
-func (e eventLossTimerCanceled) IsNil() bool  { return false }
 
-func (e eventLossTimerCanceled) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("event_type", "cancelled")
+func (e eventLossTimerCanceled) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("event_type"))
+       h.WriteToken(jsontext.String("cancelled"))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventCongestionStateUpdated struct {
@@ -552,10 +807,14 @@ type eventCongestionStateUpdated struct {
 }
 
 func (e eventCongestionStateUpdated) Name() string { return "recovery:congestion_state_updated" }
-func (e eventCongestionStateUpdated) IsNil() bool  { return false }
 
-func (e eventCongestionStateUpdated) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("new", e.state.String())
+func (e eventCongestionStateUpdated) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("new"))
+       h.WriteToken(jsontext.String(e.state.String()))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventECNStateUpdated struct {
@@ -564,11 +823,18 @@ type eventECNStateUpdated struct {
 }
 
 func (e eventECNStateUpdated) Name() string { return "recovery:ecn_state_updated" }
-func (e eventECNStateUpdated) IsNil() bool  { return false }
 
-func (e eventECNStateUpdated) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("new", ecnState(e.state).String())
-       enc.StringKeyOmitEmpty("trigger", ecnStateTrigger(e.trigger).String())
+func (e eventECNStateUpdated) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("new"))
+       h.WriteToken(jsontext.String(ecnState(e.state).String()))
+       if e.trigger != 0 {
+               h.WriteToken(jsontext.String("trigger"))
+               h.WriteToken(jsontext.String(ecnStateTrigger(e.trigger).String()))
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventALPNInformation struct {
@@ -576,10 +842,14 @@ type eventALPNInformation struct {
 }
 
 func (e eventALPNInformation) Name() string { return "transport:alpn_information" }
-func (e eventALPNInformation) IsNil() bool  { return false }
 
-func (e eventALPNInformation) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("chosen_alpn", e.chosenALPN)
+func (e eventALPNInformation) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("chosen_alpn"))
+       h.WriteToken(jsontext.String(e.chosenALPN))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type eventGeneric struct {
@@ -588,8 +858,12 @@ type eventGeneric struct {
 }
 
 func (e eventGeneric) Name() string { return "transport:" + e.name }
-func (e eventGeneric) IsNil() bool  { return false }
 
-func (e eventGeneric) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("details", e.msg)
+func (e eventGeneric) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("details"))
+       h.WriteToken(jsontext.String(e.msg))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
index f8c1c57bddbe23abba3018acac38b6993bcb6540..4d2d041786ec6e55e0167ba819a82c39db6689b6 100644 (file)
@@ -6,7 +6,8 @@ import (
        "testing"
        "time"
 
-       "github.com/francoispqt/gojay"
+       "github.com/quic-go/quic-go/qlog/jsontext"
+
        "github.com/stretchr/testify/require"
 )
 
@@ -14,17 +15,27 @@ type mevent struct{}
 
 var _ eventDetails = mevent{}
 
-func (mevent) Name() string                         { return "foobar:mevent" }
-func (mevent) IsNil() bool                          { return false }
-func (mevent) MarshalJSONObject(enc *gojay.Encoder) { enc.StringKey("event", "details") }
+func (mevent) Name() string { return "foobar:mevent" }
+func (mevent) Encode(enc *jsontext.Encoder) error {
+       if err := enc.WriteToken(jsontext.BeginObject); err != nil {
+               return err
+       }
+       if err := enc.WriteToken(jsontext.String("event")); err != nil {
+               return err
+       }
+       if err := enc.WriteToken(jsontext.String("details")); err != nil {
+               return err
+       }
+       return enc.WriteToken(jsontext.EndObject)
+}
 
 func TestEventMarshaling(t *testing.T) {
        buf := &bytes.Buffer{}
-       enc := gojay.NewEncoder(buf)
-       err := enc.Encode(event{
+       enc := jsontext.NewEncoder(buf)
+       err := (event{
                RelativeTime: 1337 * time.Microsecond,
                eventDetails: mevent{},
-       })
+       }).Encode(enc)
        require.NoError(t, err)
 
        var decoded map[string]any
index 5b1d4c42f71162506573602706f344f7f5d19e89..244802d712463572614854e76eff9c4a6fcb832b 100644 (file)
@@ -5,246 +5,416 @@ import (
 
        "github.com/quic-go/quic-go/internal/wire"
        "github.com/quic-go/quic-go/logging"
-
-       "github.com/francoispqt/gojay"
+       "github.com/quic-go/quic-go/qlog/jsontext"
 )
 
 type frame struct {
        Frame logging.Frame
 }
 
-var _ gojay.MarshalerJSONObject = frame{}
-
-var _ gojay.MarshalerJSONArray = frames{}
-
-func (f frame) MarshalJSONObject(enc *gojay.Encoder) {
+func (f frame) Encode(enc *jsontext.Encoder) error {
        switch frame := f.Frame.(type) {
        case *logging.PingFrame:
-               marshalPingFrame(enc, frame)
+               return encodePingFrame(enc, frame)
        case *logging.AckFrame:
-               marshalAckFrame(enc, frame)
+               return encodeAckFrame(enc, frame)
        case *logging.ResetStreamFrame:
-               marshalResetStreamFrame(enc, frame)
+               return encodeResetStreamFrame(enc, frame)
        case *logging.StopSendingFrame:
-               marshalStopSendingFrame(enc, frame)
+               return encodeStopSendingFrame(enc, frame)
        case *logging.CryptoFrame:
-               marshalCryptoFrame(enc, frame)
+               return encodeCryptoFrame(enc, frame)
        case *logging.NewTokenFrame:
-               marshalNewTokenFrame(enc, frame)
+               return encodeNewTokenFrame(enc, frame)
        case *logging.StreamFrame:
-               marshalStreamFrame(enc, frame)
+               return encodeStreamFrame(enc, frame)
        case *logging.MaxDataFrame:
-               marshalMaxDataFrame(enc, frame)
+               return encodeMaxDataFrame(enc, frame)
        case *logging.MaxStreamDataFrame:
-               marshalMaxStreamDataFrame(enc, frame)
+               return encodeMaxStreamDataFrame(enc, frame)
        case *logging.MaxStreamsFrame:
-               marshalMaxStreamsFrame(enc, frame)
+               return encodeMaxStreamsFrame(enc, frame)
        case *logging.DataBlockedFrame:
-               marshalDataBlockedFrame(enc, frame)
+               return encodeDataBlockedFrame(enc, frame)
        case *logging.StreamDataBlockedFrame:
-               marshalStreamDataBlockedFrame(enc, frame)
+               return encodeStreamDataBlockedFrame(enc, frame)
        case *logging.StreamsBlockedFrame:
-               marshalStreamsBlockedFrame(enc, frame)
+               return encodeStreamsBlockedFrame(enc, frame)
        case *logging.NewConnectionIDFrame:
-               marshalNewConnectionIDFrame(enc, frame)
+               return encodeNewConnectionIDFrame(enc, frame)
        case *logging.RetireConnectionIDFrame:
-               marshalRetireConnectionIDFrame(enc, frame)
+               return encodeRetireConnectionIDFrame(enc, frame)
        case *logging.PathChallengeFrame:
-               marshalPathChallengeFrame(enc, frame)
+               return encodePathChallengeFrame(enc, frame)
        case *logging.PathResponseFrame:
-               marshalPathResponseFrame(enc, frame)
+               return encodePathResponseFrame(enc, frame)
        case *logging.ConnectionCloseFrame:
-               marshalConnectionCloseFrame(enc, frame)
+               return encodeConnectionCloseFrame(enc, frame)
        case *logging.HandshakeDoneFrame:
-               marshalHandshakeDoneFrame(enc, frame)
+               return encodeHandshakeDoneFrame(enc, frame)
        case *logging.DatagramFrame:
-               marshalDatagramFrame(enc, frame)
+               return encodeDatagramFrame(enc, frame)
        case *logging.AckFrequencyFrame:
-               marshalAckFrequencyFrame(enc, frame)
+               return encodeAckFrequencyFrame(enc, frame)
        case *logging.ImmediateAckFrame:
-               marshalImmediateAckFrame(enc, frame)
+               return encodeImmediateAckFrame(enc, frame)
        default:
                panic("unknown frame type")
        }
 }
 
-func (f frame) IsNil() bool { return false }
-
 type frames []frame
 
-func (fs frames) IsNil() bool { return fs == nil }
-func (fs frames) MarshalJSONArray(enc *gojay.Encoder) {
+func (fs frames) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginArray)
        for _, f := range fs {
-               enc.Object(f)
+               if err := f.Encode(enc); err != nil {
+                       return err
+               }
        }
+       h.WriteToken(jsontext.EndArray)
+       return h.err
 }
 
-func marshalPingFrame(enc *gojay.Encoder, _ *wire.PingFrame) {
-       enc.StringKey("frame_type", "ping")
+func encodePingFrame(enc *jsontext.Encoder, _ *logging.PingFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("ping"))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type ackRanges []wire.AckRange
 
-func (ars ackRanges) MarshalJSONArray(enc *gojay.Encoder) {
+func (ars ackRanges) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginArray)
        for _, r := range ars {
-               enc.Array(ackRange(r))
+               if err := ackRange(r).Encode(enc); err != nil {
+                       return err
+               }
        }
+       h.WriteToken(jsontext.EndArray)
+       return h.err
 }
 
-func (ars ackRanges) IsNil() bool { return false }
-
 type ackRange wire.AckRange
 
-func (ar ackRange) MarshalJSONArray(enc *gojay.Encoder) {
-       enc.AddInt64(int64(ar.Smallest))
+func (ar ackRange) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginArray)
+       h.WriteToken(jsontext.Int(int64(ar.Smallest)))
        if ar.Smallest != ar.Largest {
-               enc.AddInt64(int64(ar.Largest))
+               h.WriteToken(jsontext.Int(int64(ar.Largest)))
        }
+       h.WriteToken(jsontext.EndArray)
+       return h.err
 }
 
-func (ar ackRange) IsNil() bool { return false }
-
-func marshalAckFrame(enc *gojay.Encoder, f *logging.AckFrame) {
-       enc.StringKey("frame_type", "ack")
-       enc.FloatKeyOmitEmpty("ack_delay", milliseconds(f.DelayTime))
-       enc.ArrayKey("acked_ranges", ackRanges(f.AckRanges))
-       if hasECN := f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0; hasECN {
-               enc.Uint64Key("ect0", f.ECT0)
-               enc.Uint64Key("ect1", f.ECT1)
-               enc.Uint64Key("ce", f.ECNCE)
+func encodeAckFrame(enc *jsontext.Encoder, f *logging.AckFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("ack"))
+       if f.DelayTime != 0 {
+               h.WriteToken(jsontext.String("ack_delay"))
+               h.WriteToken(jsontext.Float(milliseconds(f.DelayTime)))
+       }
+       h.WriteToken(jsontext.String("acked_ranges"))
+       if err := ackRanges(f.AckRanges).Encode(enc); err != nil {
+               return err
+       }
+       hasECN := f.ECT0 > 0 || f.ECT1 > 0 || f.ECNCE > 0
+       if hasECN {
+               h.WriteToken(jsontext.String("ect0"))
+               h.WriteToken(jsontext.Uint(f.ECT0))
+               h.WriteToken(jsontext.String("ect1"))
+               h.WriteToken(jsontext.Uint(f.ECT1))
+               h.WriteToken(jsontext.String("ce"))
+               h.WriteToken(jsontext.Uint(f.ECNCE))
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalResetStreamFrame(enc *gojay.Encoder, f *logging.ResetStreamFrame) {
+func encodeResetStreamFrame(enc *jsontext.Encoder, f *logging.ResetStreamFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
        if f.ReliableSize > 0 {
-               enc.StringKey("frame_type", "reset_stream_at")
+               h.WriteToken(jsontext.String("reset_stream_at"))
        } else {
-               enc.StringKey("frame_type", "reset_stream")
+               h.WriteToken(jsontext.String("reset_stream"))
        }
-       enc.Int64Key("stream_id", int64(f.StreamID))
-       enc.Int64Key("error_code", int64(f.ErrorCode))
-       enc.Int64Key("final_size", int64(f.FinalSize))
+       h.WriteToken(jsontext.String("stream_id"))
+       h.WriteToken(jsontext.Uint(uint64(f.StreamID)))
+       h.WriteToken(jsontext.String("error_code"))
+       h.WriteToken(jsontext.Uint(uint64(f.ErrorCode)))
+       h.WriteToken(jsontext.String("final_size"))
+       h.WriteToken(jsontext.Uint(uint64(f.FinalSize)))
        if f.ReliableSize > 0 {
-               enc.Int64Key("reliable_size", int64(f.ReliableSize))
+               h.WriteToken(jsontext.String("reliable_size"))
+               h.WriteToken(jsontext.Uint(uint64(f.ReliableSize)))
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalStopSendingFrame(enc *gojay.Encoder, f *logging.StopSendingFrame) {
-       enc.StringKey("frame_type", "stop_sending")
-       enc.Int64Key("stream_id", int64(f.StreamID))
-       enc.Int64Key("error_code", int64(f.ErrorCode))
+func encodeStopSendingFrame(enc *jsontext.Encoder, f *logging.StopSendingFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("stop_sending"))
+       h.WriteToken(jsontext.String("stream_id"))
+       h.WriteToken(jsontext.Uint(uint64(f.StreamID)))
+       h.WriteToken(jsontext.String("error_code"))
+       h.WriteToken(jsontext.Uint(uint64(f.ErrorCode)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalCryptoFrame(enc *gojay.Encoder, f *logging.CryptoFrame) {
-       enc.StringKey("frame_type", "crypto")
-       enc.Int64Key("offset", int64(f.Offset))
-       enc.Int64Key("length", int64(f.Length))
+func encodeCryptoFrame(enc *jsontext.Encoder, f *logging.CryptoFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("crypto"))
+       h.WriteToken(jsontext.String("offset"))
+       h.WriteToken(jsontext.Uint(uint64(f.Offset)))
+       h.WriteToken(jsontext.String("length"))
+       h.WriteToken(jsontext.Uint(uint64(f.Length)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalNewTokenFrame(enc *gojay.Encoder, f *logging.NewTokenFrame) {
-       enc.StringKey("frame_type", "new_token")
-       enc.ObjectKey("token", &token{Raw: f.Token})
+func encodeNewTokenFrame(enc *jsontext.Encoder, f *logging.NewTokenFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("new_token"))
+       h.WriteToken(jsontext.String("token"))
+       if err := (token{Raw: f.Token}).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalStreamFrame(enc *gojay.Encoder, f *logging.StreamFrame) {
-       enc.StringKey("frame_type", "stream")
-       enc.Int64Key("stream_id", int64(f.StreamID))
-       enc.Int64Key("offset", int64(f.Offset))
-       enc.IntKey("length", int(f.Length))
-       enc.BoolKeyOmitEmpty("fin", f.Fin)
+func encodeStreamFrame(enc *jsontext.Encoder, f *logging.StreamFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("stream"))
+       h.WriteToken(jsontext.String("stream_id"))
+       h.WriteToken(jsontext.Uint(uint64(f.StreamID)))
+       h.WriteToken(jsontext.String("offset"))
+       h.WriteToken(jsontext.Uint(uint64(f.Offset)))
+       h.WriteToken(jsontext.String("length"))
+       h.WriteToken(jsontext.Uint(uint64(f.Length)))
+       if f.Fin {
+               h.WriteToken(jsontext.String("fin"))
+               h.WriteToken(jsontext.True)
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalMaxDataFrame(enc *gojay.Encoder, f *logging.MaxDataFrame) {
-       enc.StringKey("frame_type", "max_data")
-       enc.Int64Key("maximum", int64(f.MaximumData))
+func encodeMaxDataFrame(enc *jsontext.Encoder, f *logging.MaxDataFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("max_data"))
+       h.WriteToken(jsontext.String("maximum"))
+       h.WriteToken(jsontext.Uint(uint64(f.MaximumData)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalMaxStreamDataFrame(enc *gojay.Encoder, f *logging.MaxStreamDataFrame) {
-       enc.StringKey("frame_type", "max_stream_data")
-       enc.Int64Key("stream_id", int64(f.StreamID))
-       enc.Int64Key("maximum", int64(f.MaximumStreamData))
+func encodeMaxStreamDataFrame(enc *jsontext.Encoder, f *logging.MaxStreamDataFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("max_stream_data"))
+       h.WriteToken(jsontext.String("stream_id"))
+       h.WriteToken(jsontext.Uint(uint64(f.StreamID)))
+       h.WriteToken(jsontext.String("maximum"))
+       h.WriteToken(jsontext.Uint(uint64(f.MaximumStreamData)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalMaxStreamsFrame(enc *gojay.Encoder, f *logging.MaxStreamsFrame) {
-       enc.StringKey("frame_type", "max_streams")
-       enc.StringKey("stream_type", streamType(f.Type).String())
-       enc.Int64Key("maximum", int64(f.MaxStreamNum))
+func encodeMaxStreamsFrame(enc *jsontext.Encoder, f *logging.MaxStreamsFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("max_streams"))
+       h.WriteToken(jsontext.String("stream_type"))
+       h.WriteToken(jsontext.String(streamType(f.Type).String()))
+       h.WriteToken(jsontext.String("maximum"))
+       h.WriteToken(jsontext.Uint(uint64(f.MaxStreamNum)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalDataBlockedFrame(enc *gojay.Encoder, f *logging.DataBlockedFrame) {
-       enc.StringKey("frame_type", "data_blocked")
-       enc.Int64Key("limit", int64(f.MaximumData))
+func encodeDataBlockedFrame(enc *jsontext.Encoder, f *logging.DataBlockedFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("data_blocked"))
+       h.WriteToken(jsontext.String("limit"))
+       h.WriteToken(jsontext.Uint(uint64(f.MaximumData)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalStreamDataBlockedFrame(enc *gojay.Encoder, f *logging.StreamDataBlockedFrame) {
-       enc.StringKey("frame_type", "stream_data_blocked")
-       enc.Int64Key("stream_id", int64(f.StreamID))
-       enc.Int64Key("limit", int64(f.MaximumStreamData))
+func encodeStreamDataBlockedFrame(enc *jsontext.Encoder, f *logging.StreamDataBlockedFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("stream_data_blocked"))
+       h.WriteToken(jsontext.String("stream_id"))
+       h.WriteToken(jsontext.Uint(uint64(f.StreamID)))
+       h.WriteToken(jsontext.String("limit"))
+       h.WriteToken(jsontext.Uint(uint64(f.MaximumStreamData)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalStreamsBlockedFrame(enc *gojay.Encoder, f *logging.StreamsBlockedFrame) {
-       enc.StringKey("frame_type", "streams_blocked")
-       enc.StringKey("stream_type", streamType(f.Type).String())
-       enc.Int64Key("limit", int64(f.StreamLimit))
+func encodeStreamsBlockedFrame(enc *jsontext.Encoder, f *logging.StreamsBlockedFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("streams_blocked"))
+       h.WriteToken(jsontext.String("stream_type"))
+       h.WriteToken(jsontext.String(streamType(f.Type).String()))
+       h.WriteToken(jsontext.String("limit"))
+       h.WriteToken(jsontext.Uint(uint64(f.StreamLimit)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalNewConnectionIDFrame(enc *gojay.Encoder, f *logging.NewConnectionIDFrame) {
-       enc.StringKey("frame_type", "new_connection_id")
-       enc.Int64Key("sequence_number", int64(f.SequenceNumber))
-       enc.Int64Key("retire_prior_to", int64(f.RetirePriorTo))
-       enc.IntKey("length", f.ConnectionID.Len())
-       enc.StringKey("connection_id", f.ConnectionID.String())
-       enc.StringKey("stateless_reset_token", fmt.Sprintf("%x", f.StatelessResetToken))
+func encodeNewConnectionIDFrame(enc *jsontext.Encoder, f *logging.NewConnectionIDFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("new_connection_id"))
+       h.WriteToken(jsontext.String("sequence_number"))
+       h.WriteToken(jsontext.Uint(f.SequenceNumber))
+       h.WriteToken(jsontext.String("retire_prior_to"))
+       h.WriteToken(jsontext.Uint(f.RetirePriorTo))
+       h.WriteToken(jsontext.String("length"))
+       h.WriteToken(jsontext.Int(int64(f.ConnectionID.Len())))
+       h.WriteToken(jsontext.String("connection_id"))
+       h.WriteToken(jsontext.String(f.ConnectionID.String()))
+       h.WriteToken(jsontext.String("stateless_reset_token"))
+       h.WriteToken(jsontext.String(fmt.Sprintf("%x", f.StatelessResetToken)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalRetireConnectionIDFrame(enc *gojay.Encoder, f *logging.RetireConnectionIDFrame) {
-       enc.StringKey("frame_type", "retire_connection_id")
-       enc.Int64Key("sequence_number", int64(f.SequenceNumber))
+func encodeRetireConnectionIDFrame(enc *jsontext.Encoder, f *logging.RetireConnectionIDFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("retire_connection_id"))
+       h.WriteToken(jsontext.String("sequence_number"))
+       h.WriteToken(jsontext.Uint(f.SequenceNumber))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalPathChallengeFrame(enc *gojay.Encoder, f *logging.PathChallengeFrame) {
-       enc.StringKey("frame_type", "path_challenge")
-       enc.StringKey("data", fmt.Sprintf("%x", f.Data[:]))
+func encodePathChallengeFrame(enc *jsontext.Encoder, f *logging.PathChallengeFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("path_challenge"))
+       h.WriteToken(jsontext.String("data"))
+       h.WriteToken(jsontext.String(fmt.Sprintf("%x", f.Data[:])))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalPathResponseFrame(enc *gojay.Encoder, f *logging.PathResponseFrame) {
-       enc.StringKey("frame_type", "path_response")
-       enc.StringKey("data", fmt.Sprintf("%x", f.Data[:]))
+func encodePathResponseFrame(enc *jsontext.Encoder, f *logging.PathResponseFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("path_response"))
+       h.WriteToken(jsontext.String("data"))
+       h.WriteToken(jsontext.String(fmt.Sprintf("%x", f.Data[:])))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalConnectionCloseFrame(enc *gojay.Encoder, f *logging.ConnectionCloseFrame) {
+func encodeConnectionCloseFrame(enc *jsontext.Encoder, f *logging.ConnectionCloseFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("connection_close"))
+       h.WriteToken(jsontext.String("error_space"))
        errorSpace := "transport"
        if f.IsApplicationError {
                errorSpace = "application"
        }
-       enc.StringKey("frame_type", "connection_close")
-       enc.StringKey("error_space", errorSpace)
-       if errName := transportError(f.ErrorCode).String(); len(errName) > 0 {
-               enc.StringKey("error_code", errName)
+       h.WriteToken(jsontext.String(errorSpace))
+       errName := transportError(f.ErrorCode).String()
+       if len(errName) > 0 {
+               h.WriteToken(jsontext.String("error_code"))
+               h.WriteToken(jsontext.String(errName))
        } else {
-               enc.Uint64Key("error_code", f.ErrorCode)
+               h.WriteToken(jsontext.String("error_code"))
+               h.WriteToken(jsontext.Uint(f.ErrorCode))
        }
-       enc.Uint64Key("raw_error_code", f.ErrorCode)
-       enc.StringKey("reason", f.ReasonPhrase)
+       h.WriteToken(jsontext.String("raw_error_code"))
+       h.WriteToken(jsontext.Uint(f.ErrorCode))
+       h.WriteToken(jsontext.String("reason"))
+       h.WriteToken(jsontext.String(f.ReasonPhrase))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalHandshakeDoneFrame(enc *gojay.Encoder, _ *logging.HandshakeDoneFrame) {
-       enc.StringKey("frame_type", "handshake_done")
+func encodeHandshakeDoneFrame(enc *jsontext.Encoder, _ *logging.HandshakeDoneFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("handshake_done"))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalDatagramFrame(enc *gojay.Encoder, f *logging.DatagramFrame) {
-       enc.StringKey("frame_type", "datagram")
-       enc.Int64Key("length", int64(f.Length))
+func encodeDatagramFrame(enc *jsontext.Encoder, f *logging.DatagramFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("datagram"))
+       h.WriteToken(jsontext.String("length"))
+       h.WriteToken(jsontext.Uint(uint64(f.Length)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalAckFrequencyFrame(enc *gojay.Encoder, f *logging.AckFrequencyFrame) {
-       enc.StringKey("frame_type", "ack_frequency")
-       enc.Uint64Key("sequence_number", f.SequenceNumber)
-       enc.Uint64Key("ack_eliciting_threshold", f.AckElicitingThreshold)
-       enc.Float64Key("request_max_ack_delay", milliseconds(f.RequestMaxAckDelay))
-       enc.Uint64Key("reordering_threshold", uint64(f.ReorderingThreshold))
+func encodeAckFrequencyFrame(enc *jsontext.Encoder, f *logging.AckFrequencyFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("ack_frequency"))
+       h.WriteToken(jsontext.String("sequence_number"))
+       h.WriteToken(jsontext.Uint(f.SequenceNumber))
+       h.WriteToken(jsontext.String("ack_eliciting_threshold"))
+       h.WriteToken(jsontext.Uint(f.AckElicitingThreshold))
+       h.WriteToken(jsontext.String("request_max_ack_delay"))
+       h.WriteToken(jsontext.Float(milliseconds(f.RequestMaxAckDelay)))
+       h.WriteToken(jsontext.String("reordering_threshold"))
+       h.WriteToken(jsontext.Uint(uint64(f.ReorderingThreshold)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func marshalImmediateAckFrame(enc *gojay.Encoder, _ *logging.ImmediateAckFrame) {
-       enc.StringKey("frame_type", "immediate_ack")
+func encodeImmediateAckFrame(enc *jsontext.Encoder, _ *logging.ImmediateAckFrame) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("frame_type"))
+       h.WriteToken(jsontext.String("immediate_ack"))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
index 00431acaf384e08f2b1a3944dbac1b5037146ab4..aa57fbc36d0bb1eb34d4e88b7938a95e37ba894c 100644 (file)
@@ -6,17 +6,19 @@ import (
        "testing"
        "time"
 
-       "github.com/francoispqt/gojay"
        "github.com/quic-go/quic-go/internal/protocol"
        "github.com/quic-go/quic-go/internal/qerr"
        "github.com/quic-go/quic-go/logging"
+
+       "github.com/quic-go/quic-go/qlog/jsontext"
+
        "github.com/stretchr/testify/require"
 )
 
 func check(t *testing.T, f logging.Frame, expected map[string]any) {
        buf := &bytes.Buffer{}
-       enc := gojay.NewEncoder(buf)
-       err := enc.Encode(frame{Frame: f})
+       enc := jsontext.NewEncoder(buf)
+       err := (frame{Frame: f}).Encode(enc)
        require.NoError(t, err)
        data := buf.Bytes()
        require.True(t, json.Valid(data))
index 8d36b8d6ec10a49feb79a6204cf239f19e63e98f..b45178180d3ec5447563cf015f14ef5bc213e5f4 100644 (file)
@@ -30,6 +30,8 @@ func unmarshal(data []byte, v any) error {
 }
 
 func checkEncoding(t *testing.T, data []byte, expected map[string]any) {
+       t.Helper()
+
        m := make(map[string]any)
        require.NoError(t, json.Unmarshal(data, &m))
        require.Len(t, m, len(expected))
@@ -68,6 +70,8 @@ type entry struct {
 }
 
 func exportAndParse(t *testing.T, buf *bytes.Buffer) []entry {
+       t.Helper()
+
        m := make(map[string]any)
        line, err := buf.ReadBytes('\n')
        require.NoError(t, err)
@@ -100,6 +104,8 @@ func exportAndParse(t *testing.T, buf *bytes.Buffer) []entry {
 }
 
 func exportAndParseSingle(t *testing.T, buf *bytes.Buffer) entry {
+       t.Helper()
+
        entries := exportAndParse(t, buf)
        require.Len(t, entries, 1)
        return entries[0]
diff --git a/qlog/jsontext/encoder.go b/qlog/jsontext/encoder.go
new file mode 100644 (file)
index 0000000..b838e2f
--- /dev/null
@@ -0,0 +1,314 @@
+// Package jsontext provides a fast JSON encoder providing only the necessary features
+// for qlog encoding. No efforts are made to add any features beyond qlog's requirements.
+//
+// The API aims to be compatible with the standard library's encoding/json/jsontext package.
+package jsontext
+
+import (
+       "fmt"
+       "io"
+       "strconv"
+       "unsafe"
+)
+
+type kind uint8
+
+const (
+       kindString kind = iota
+       kindInt
+       kindUint
+       kindFloat
+       kindBool
+       kindObjectStart
+       kindObjectEnd
+       kindArrayStart
+       kindArrayEnd
+)
+
+// Token represents a JSON token.
+type Token struct {
+       kind kind
+       str  string
+       i64  int64
+       u64  uint64
+       f64  float64
+       b    bool
+}
+
+// String creates a string token.
+func String(s string) Token {
+       return Token{kind: kindString, str: s}
+}
+
+// Int creates an int token.
+func Int(i int64) Token {
+       return Token{kind: kindInt, i64: i}
+}
+
+// Uint creates a uint token.
+func Uint(u uint64) Token {
+       return Token{kind: kindUint, u64: u}
+}
+
+// Float creates a float token.
+func Float(f float64) Token {
+       return Token{kind: kindFloat, f64: f}
+}
+
+// Bool creates a bool token.
+func Bool(b bool) Token {
+       return Token{kind: kindBool, b: b}
+}
+
+// BeginObject is the begin object token.
+var BeginObject Token = Token{kind: kindObjectStart}
+
+// EndObject is the end object token.
+var EndObject Token = Token{kind: kindObjectEnd}
+
+// BeginArray is the begin array token.
+var BeginArray Token = Token{kind: kindArrayStart}
+
+// EndArray is the end array token.
+var EndArray Token = Token{kind: kindArrayEnd}
+
+// True is a true token.
+var True Token = Bool(true)
+
+// False is a false token.
+var False Token = Bool(false)
+
+var hexDigits = [16]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
+
+var (
+       commaByte       = []byte(",")
+       quoteByte       = []byte(`"`)
+       colonByte       = []byte(":")
+       trueByte        = []byte("true")
+       falseByte       = []byte("false")
+       openObjectByte  = []byte("{")
+       closeObjectByte = []byte("}")
+       openArrayByte   = []byte("[")
+       closeArrayByte  = []byte("]")
+       newlineByte     = []byte("\n")
+       escapeQuote     = []byte(`\"`)
+       escapeBackslash = []byte(`\\`)
+       escapeBackspace = []byte(`\b`)
+       escapeFormfeed  = []byte(`\f`)
+       escapeNewline   = []byte(`\n`)
+       escapeCarriage  = []byte(`\r`)
+       escapeTab       = []byte(`\t`)
+       escapeUnicode   = []byte(`\u00`)
+)
+
+type context struct {
+       isObject   bool
+       needsComma bool
+       expectKey  bool
+}
+
+// Encoder encodes JSON to an io.Writer.
+type Encoder struct {
+       w     io.Writer
+       buf   [64]byte // scratch buffer for number formatting
+       stack []context
+}
+
+// NewEncoder creates a new Encoder.
+func NewEncoder(w io.Writer) *Encoder {
+       stack := make([]context, 0, 8)
+       stack = append(stack, context{isObject: false, needsComma: false, expectKey: false})
+       return &Encoder{
+               w:     w,
+               stack: stack,
+       }
+}
+
+// WriteToken writes a token to the encoder.
+func (e *Encoder) WriteToken(t Token) error {
+       if len(e.stack) == 0 {
+               return fmt.Errorf("empty stack")
+       }
+       curr := &e.stack[len(e.stack)-1]
+       isClosing := t.kind == kindObjectEnd || t.kind == kindArrayEnd
+       if !isClosing && curr.needsComma {
+               if _, err := e.w.Write(commaByte); err != nil {
+                       return err
+               }
+               curr.needsComma = false
+       }
+       var err error
+       switch t.kind {
+       case kindString:
+               data := stringToBytes(t.str)
+               needsEscape := false
+               for _, b := range data {
+                       if b == '"' || b == '\\' || b < 0x20 {
+                               needsEscape = true
+                               break
+                       }
+               }
+               if !needsEscape {
+                       if _, err = e.w.Write(quoteByte); err != nil {
+                               return err
+                       }
+                       if _, err = e.w.Write(data); err != nil {
+                               return err
+                       }
+                       if _, err = e.w.Write(quoteByte); err != nil {
+                               return err
+                       }
+               } else {
+                       if _, err = e.w.Write(quoteByte); err != nil {
+                               return err
+                       }
+                       for i := 0; i < len(t.str); i++ {
+                               c := t.str[i]
+                               switch c {
+                               case '"':
+                                       if _, err = e.w.Write(escapeQuote); err != nil {
+                                               return err
+                                       }
+                               case '\\':
+                                       if _, err = e.w.Write(escapeBackslash); err != nil {
+                                               return err
+                                       }
+                               case '\b':
+                                       if _, err = e.w.Write(escapeBackspace); err != nil {
+                                               return err
+                                       }
+                               case '\f':
+                                       if _, err = e.w.Write(escapeFormfeed); err != nil {
+                                               return err
+                                       }
+                               case '\n':
+                                       if _, err = e.w.Write(escapeNewline); err != nil {
+                                               return err
+                                       }
+                               case '\r':
+                                       if _, err = e.w.Write(escapeCarriage); err != nil {
+                                               return err
+                                       }
+                               case '\t':
+                                       if _, err = e.w.Write(escapeTab); err != nil {
+                                               return err
+                                       }
+                               default:
+                                       if c < 0x20 {
+                                               if _, err = e.w.Write(escapeUnicode); err != nil {
+                                                       return err
+                                               }
+                                               if _, err = e.w.Write([]byte{hexDigits[c>>4], hexDigits[c&0xf]}); err != nil {
+                                                       return err
+                                               }
+                                       } else {
+                                               if _, err = e.w.Write([]byte{c}); err != nil {
+                                                       return err
+                                               }
+                                       }
+                               }
+                       }
+                       if _, err = e.w.Write(quoteByte); err != nil {
+                               return err
+                       }
+               }
+               if curr.isObject {
+                       if curr.expectKey {
+                               // key
+                               if _, err = e.w.Write(colonByte); err != nil {
+                                       return err
+                               }
+                               curr.expectKey = false
+                               return nil // do not call afterValue for keys
+                       } else {
+                               // value
+                               e.afterValue()
+                       }
+               } else {
+                       e.afterValue()
+               }
+       case kindInt:
+               b := strconv.AppendInt(e.buf[:0], t.i64, 10)
+               if _, err = e.w.Write(b); err != nil {
+                       return err
+               }
+               e.afterValue()
+       case kindUint:
+               b := strconv.AppendUint(e.buf[:0], t.u64, 10)
+               if _, err = e.w.Write(b); err != nil {
+                       return err
+               }
+               e.afterValue()
+       case kindFloat:
+               b := strconv.AppendFloat(e.buf[:0], t.f64, 'g', -1, 64)
+               if _, err = e.w.Write(b); err != nil {
+                       return err
+               }
+               e.afterValue()
+       case kindBool:
+               if t.b {
+                       if _, err = e.w.Write(trueByte); err != nil {
+                               return err
+                       }
+               } else {
+                       if _, err = e.w.Write(falseByte); err != nil {
+                               return err
+                       }
+               }
+               e.afterValue()
+       case kindObjectStart:
+               if _, err = e.w.Write(openObjectByte); err != nil {
+                       return err
+               }
+               e.stack = append(e.stack, context{isObject: true, needsComma: false, expectKey: true})
+               return nil
+       case kindObjectEnd:
+               if _, err = e.w.Write(closeObjectByte); err != nil {
+                       return err
+               }
+               e.stack = e.stack[:len(e.stack)-1]
+               e.afterValue()
+               if len(e.stack) == 1 {
+                       if _, err = e.w.Write(newlineByte); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       case kindArrayStart:
+               if _, err = e.w.Write(openArrayByte); err != nil {
+                       return err
+               }
+               e.stack = append(e.stack, context{isObject: false, needsComma: false, expectKey: false})
+               return nil
+       case kindArrayEnd:
+               if _, err = e.w.Write(closeArrayByte); err != nil {
+                       return err
+               }
+               e.stack = e.stack[:len(e.stack)-1]
+               e.afterValue()
+               if len(e.stack) == 1 {
+                       if _, err = e.w.Write(newlineByte); err != nil {
+                               return err
+                       }
+               }
+               return nil
+       default:
+               return fmt.Errorf("unknown token kind")
+       }
+       return err
+}
+
+// afterValue updates the state after encoding a value
+func (e *Encoder) afterValue() {
+       if len(e.stack) > 1 {
+               curr := &e.stack[len(e.stack)-1]
+               curr.needsComma = true
+               if curr.isObject {
+                       curr.expectKey = true
+               }
+       }
+}
+
+func stringToBytes(s string) []byte {
+       return unsafe.Slice(unsafe.StringData(s), len(s))
+}
diff --git a/qlog/jsontext/encoder_test.go b/qlog/jsontext/encoder_test.go
new file mode 100644 (file)
index 0000000..922b79d
--- /dev/null
@@ -0,0 +1,383 @@
+package jsontext_test
+
+import (
+       "bytes"
+       "encoding/json"
+       "testing"
+
+       "github.com/quic-go/quic-go/qlog/jsontext"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+func TestEncoderSimpleObject(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginObject)
+       enc.WriteToken(jsontext.String("foo"))
+       enc.WriteToken(jsontext.String("bar"))
+       enc.WriteToken(jsontext.String("foo2"))
+       enc.WriteToken(jsontext.String("bar2"))
+       enc.WriteToken(jsontext.EndObject)
+       output := buf.String()
+
+       var got map[string]string
+       require.NoError(t, json.Unmarshal([]byte(output), &got))
+       require.Equal(t, map[string]string{"foo": "bar", "foo2": "bar2"}, got)
+}
+
+func TestEncoderArrayInts(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginArray)
+       enc.WriteToken(jsontext.Int(1))
+       enc.WriteToken(jsontext.Int(2))
+       enc.WriteToken(jsontext.Int(3))
+       enc.WriteToken(jsontext.EndArray)
+       output := buf.String()
+
+       var got []int
+       require.NoError(t, json.Unmarshal([]byte(output), &got))
+       require.Equal(t, []int{1, 2, 3}, got)
+}
+
+func TestEncoderArrayStrings(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginArray)
+       enc.WriteToken(jsontext.String("one"))
+       enc.WriteToken(jsontext.String("two"))
+       enc.WriteToken(jsontext.EndArray)
+       output := buf.String()
+
+       var got []string
+       err := json.Unmarshal([]byte(output), &got)
+       require.NoError(t, err)
+       require.Equal(t, []string{"one", "two"}, got)
+}
+
+func TestEncoderNestedObject(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginObject)
+       enc.WriteToken(jsontext.String("outer"))
+       enc.WriteToken(jsontext.BeginObject)
+       enc.WriteToken(jsontext.String("inner"))
+       enc.WriteToken(jsontext.String("value"))
+       enc.WriteToken(jsontext.EndObject)
+       enc.WriteToken(jsontext.EndObject)
+       output := buf.String()
+
+       var got map[string]map[string]string
+       require.NoError(t, json.Unmarshal([]byte(output), &got))
+       require.Equal(t, map[string]map[string]string{"outer": {"inner": "value"}}, got)
+}
+
+func TestEncoderNumbersAndBool(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginObject)
+       enc.WriteToken(jsontext.String("int"))
+       enc.WriteToken(jsontext.Int(42))
+       enc.WriteToken(jsontext.String("uint"))
+       enc.WriteToken(jsontext.Uint(100))
+       enc.WriteToken(jsontext.String("float"))
+       enc.WriteToken(jsontext.Float(3.14))
+       enc.WriteToken(jsontext.String("true"))
+       enc.WriteToken(jsontext.True)
+       enc.WriteToken(jsontext.String("false"))
+       enc.WriteToken(jsontext.False)
+       enc.WriteToken(jsontext.EndObject)
+       output := buf.String()
+
+       var got map[string]any
+       require.NoError(t, json.Unmarshal([]byte(output), &got))
+       require.Equal(t, map[string]any{
+               "int":   float64(42), // json.Unmarshal decodes numbers as float64
+               "uint":  float64(100),
+               "float": 3.14,
+               "true":  true,
+               "false": false,
+       }, got)
+}
+
+func TestEncoderEmptyObject(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginObject)
+       enc.WriteToken(jsontext.EndObject)
+       output := buf.String()
+
+       var got map[string]any
+       require.NoError(t, json.Unmarshal([]byte(output), &got))
+       require.Equal(t, map[string]any{}, got)
+}
+
+func TestEncoderEmptyArray(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginArray)
+       enc.WriteToken(jsontext.EndArray)
+       output := buf.String()
+
+       var got []any
+       require.NoError(t, json.Unmarshal([]byte(output), &got))
+       require.Equal(t, []any{}, got)
+}
+
+func TestEncoderEscapedStrings(t *testing.T) {
+       t.Run("no escapes", func(t *testing.T) {
+               testEncoderEscapedStrings(t, "simplekey", "simplevalue")
+       })
+
+       t.Run("basic escapes", func(t *testing.T) {
+               key := `key"\/`
+               value := `value"\/`
+               testEncoderEscapedStrings(t, key, value)
+       })
+
+       t.Run("control characters", func(t *testing.T) {
+               key := "key\b\f\n\r\t"
+               value := "value\b\f\n\r\t"
+               testEncoderEscapedStrings(t, key, value)
+       })
+
+       t.Run("unicode low", func(t *testing.T) {
+               key := "key\u0007\u001f"
+               value := "value\u0007\u001f"
+               testEncoderEscapedStrings(t, key, value)
+       })
+
+       t.Run("mixed all", func(t *testing.T) {
+               key := `key"\\\/\b\f\n\r\t\u0007\u001f`
+               value := `value"\\\/\b\f\n\r\t\u0007\u001f`
+               testEncoderEscapedStrings(t, key, value)
+       })
+}
+
+func testEncoderEscapedStrings(t *testing.T, key, value string) {
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       enc.WriteToken(jsontext.BeginObject)
+       enc.WriteToken(jsontext.String(key))
+       enc.WriteToken(jsontext.String(value))
+       enc.WriteToken(jsontext.EndObject)
+       output := buf.String()
+
+       var got map[string]string
+       err := json.Unmarshal([]byte(output), &got)
+       require.NoError(t, err)
+       expected := map[string]string{key: value}
+       require.Equal(t, expected, got)
+}
+
+func encodeValue(t testing.TB, enc *jsontext.Encoder, v any) (isSupported bool) {
+       t.Helper()
+
+       switch val := v.(type) {
+       case map[string]any:
+               require.NoError(t, enc.WriteToken(jsontext.BeginObject))
+               for k, vv := range val {
+                       require.NoError(t, enc.WriteToken(jsontext.String(k)))
+                       if !encodeValue(t, enc, vv) {
+                               return false
+                       }
+               }
+               require.NoError(t, enc.WriteToken(jsontext.EndObject))
+               return true
+       case []any:
+               require.NoError(t, enc.WriteToken(jsontext.BeginArray))
+               for _, vv := range val {
+                       if !encodeValue(t, enc, vv) {
+                               return false // Propagate unsupported if any nested value fails
+                       }
+               }
+               require.NoError(t, enc.WriteToken(jsontext.EndArray))
+               return true
+       case string:
+               require.NoError(t, enc.WriteToken(jsontext.String(val)))
+               return true
+       case int64:
+               require.NoError(t, enc.WriteToken(jsontext.Int(val)))
+               return true
+       case uint64:
+               require.NoError(t, enc.WriteToken(jsontext.Uint(val)))
+               return true
+       case float64:
+               require.NoError(t, enc.WriteToken(jsontext.Float(val)))
+               return true
+       case bool:
+               require.NoError(t, enc.WriteToken(jsontext.Bool(val)))
+               return true
+       default:
+               return false
+       }
+}
+
+type errorWriter struct {
+       N int
+}
+
+func (w *errorWriter) Write(p []byte) (int, error) {
+       n := min(len(p), w.N)
+       w.N -= n
+       if w.N <= 0 {
+               return n, assert.AnError
+       }
+       return n, nil
+}
+
+func TestEncoderComprehensive(t *testing.T) {
+       // encodes an object with all token types and nested structures
+       encode := func(enc *jsontext.Encoder) error {
+               if err := enc.WriteToken(jsontext.BeginObject); err != nil {
+                       return err
+               }
+
+               if err := enc.WriteToken(jsontext.String("simple")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.String("value")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.String("escaped")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.String(`"quoted\"string"`)); err != nil {
+                       return err
+               }
+
+               if err := enc.WriteToken(jsontext.String("int")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.Int(-42)); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.String("uint")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.Uint(100)); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.String("float")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.Float(3.14)); err != nil {
+                       return err
+               }
+
+               if err := enc.WriteToken(jsontext.String("true")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.True); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.String("false")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.False); err != nil {
+                       return err
+               }
+
+               if err := enc.WriteToken(jsontext.String("array")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.BeginArray); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.String("item1")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.Int(1)); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.EndArray); err != nil {
+                       return err
+               }
+
+               if err := enc.WriteToken(jsontext.String("nested")); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.BeginObject); err != nil {
+                       return err
+               }
+               if err := enc.WriteToken(jsontext.EndObject); err != nil {
+                       return err
+               }
+
+               if err := enc.WriteToken(jsontext.EndObject); err != nil {
+                       return err
+               }
+               return nil
+       }
+
+       buf := bytes.NewBuffer(nil)
+       enc := jsontext.NewEncoder(buf)
+       require.NoError(t, encode(enc))
+
+       for i := range buf.Len() {
+               enc := jsontext.NewEncoder(&errorWriter{N: i})
+               require.ErrorIs(t, encode(enc), assert.AnError)
+       }
+}
+
+func FuzzEncoder(f *testing.F) {
+       examples := []string{
+               `{"hello": "world"}`,
+               `{"foo": 123, "bar": [1, 2, 3]}`,
+               `{"nested": {"a": 1, "b": [true, false, "foobar"]}}`,
+               `[{"x": 1}, {"y": "foo"}]`,
+               `["foo", "bar"]`,
+               `["a", {"b": [1, 2, {"c": "d"}]}, 3]`,
+               `{"emptyObj": {}, "emptyArr": []}`,
+               `{"mixed": [1, "two", {"three": 3}]}`,
+       }
+       for _, tc := range examples {
+               // first test that
+               // 1. it's valid JSON
+               d := json.NewDecoder(bytes.NewReader([]byte(tc)))
+               var expected any
+               require.NoError(f, d.Decode(&expected), "corpus entry `%s` is not valid JSON", tc)
+               // 2. the jsontext encoder can handle
+               enc := jsontext.NewEncoder(&bytes.Buffer{})
+               require.True(f, encodeValue(f, enc, expected), "expected `%s` to be supported", tc)
+
+               f.Add([]byte(tc))
+       }
+
+       var stdlibBuf, ourBuf bytes.Buffer
+
+       f.Fuzz(func(t *testing.T, b []byte) {
+               stdlibBuf.Truncate(0)
+               ourBuf.Truncate(0)
+               stdlibBuf.Grow(len(b))
+               ourBuf.Grow(len(b))
+
+               d := json.NewDecoder(bytes.NewReader(b))
+               var expected any
+               if err := d.Decode(&expected); err != nil {
+                       return // invalid JSON
+               }
+
+               // only attempt to handle inputs that the standard library can handle
+               stdlibEnc := json.NewEncoder(&stdlibBuf)
+               require.NoError(t, stdlibEnc.Encode(expected))
+               if !json.Valid(stdlibBuf.Bytes()) {
+                       return
+               }
+
+               // then encode using the jsontext encoder
+               enc := jsontext.NewEncoder(&ourBuf)
+               if isSupported := encodeValue(t, enc, expected); !isSupported {
+                       return
+               }
+
+               output := ourBuf.Bytes()
+               require.Truef(t, json.Valid(output), "produced invalid JSON: %s", output)
+
+               var got any
+               require.NoError(t, json.Unmarshal(output, &got))
+               require.JSONEq(t, ourBuf.String(), stdlibBuf.String())
+       })
+}
index d2c57ba605a0758f9269785d7a6ecf515592cba0..cf8dbc3717dd8b40c63822d1ce141f4c10d24ea1 100644 (file)
@@ -5,8 +5,7 @@ import (
 
        "github.com/quic-go/quic-go/internal/protocol"
        "github.com/quic-go/quic-go/logging"
-
-       "github.com/francoispqt/gojay"
+       "github.com/quic-go/quic-go/qlog/jsontext"
 )
 
 func getPacketTypeFromEncryptionLevel(encLevel protocol.EncryptionLevel) logging.PacketType {
@@ -28,72 +27,86 @@ type token struct {
        Raw []byte
 }
 
-var _ gojay.MarshalerJSONObject = &token{}
-
-func (t token) IsNil() bool { return false }
-func (t token) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("data", fmt.Sprintf("%x", t.Raw))
+func (t token) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("data"))
+       h.WriteToken(jsontext.String(fmt.Sprintf("%x", t.Raw)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 // PacketHeader is a QUIC packet header.
 // TODO: make this a long header
 type packetHeader struct {
-       PacketType logging.PacketType
-
-       KeyPhaseBit  logging.KeyPhaseBit
-       PacketNumber logging.PacketNumber
-
+       PacketType       logging.PacketType
+       KeyPhaseBit      logging.KeyPhaseBit
+       PacketNumber     logging.PacketNumber
        Version          logging.Version
        SrcConnectionID  logging.ConnectionID
        DestConnectionID logging.ConnectionID
-
-       Token *token
+       Token            *token
 }
 
 func transformHeader(hdr *logging.Header) *packetHeader {
-       h := &packetHeader{
+       ph := &packetHeader{
                PacketType:       logging.PacketTypeFromHeader(hdr),
                SrcConnectionID:  hdr.SrcConnectionID,
                DestConnectionID: hdr.DestConnectionID,
                Version:          hdr.Version,
        }
        if len(hdr.Token) > 0 {
-               h.Token = &token{Raw: hdr.Token}
+               ph.Token = &token{Raw: hdr.Token}
        }
-       return h
+       return ph
 }
 
 func transformLongHeader(hdr *logging.ExtendedHeader) *packetHeader {
-       h := transformHeader(&hdr.Header)
-       h.PacketNumber = hdr.PacketNumber
-       h.KeyPhaseBit = hdr.KeyPhase
-       return h
-}
-
-func (h packetHeader) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("packet_type", packetType(h.PacketType).String())
-       if h.PacketType != logging.PacketTypeRetry {
-               enc.Int64Key("packet_number", int64(h.PacketNumber))
+       ph := transformHeader(&hdr.Header)
+       ph.PacketNumber = hdr.PacketNumber
+       ph.KeyPhaseBit = hdr.KeyPhase
+       return ph
+}
+
+func (ph packetHeader) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("packet_type"))
+       h.WriteToken(jsontext.String(packetType(ph.PacketType).String()))
+       if ph.PacketType != logging.PacketTypeRetry {
+               h.WriteToken(jsontext.String("packet_number"))
+               h.WriteToken(jsontext.Int(int64(ph.PacketNumber)))
        }
-       if h.Version != 0 {
-               enc.StringKey("version", version(h.Version).String())
+       if ph.Version != 0 {
+               h.WriteToken(jsontext.String("version"))
+               h.WriteToken(jsontext.String(version(ph.Version).String()))
        }
-       if h.PacketType != logging.PacketType1RTT {
-               enc.IntKey("scil", h.SrcConnectionID.Len())
-               if h.SrcConnectionID.Len() > 0 {
-                       enc.StringKey("scid", h.SrcConnectionID.String())
+       if ph.PacketType != logging.PacketType1RTT {
+               h.WriteToken(jsontext.String("scil"))
+               h.WriteToken(jsontext.Int(int64(ph.SrcConnectionID.Len())))
+               if ph.SrcConnectionID.Len() > 0 {
+                       h.WriteToken(jsontext.String("scid"))
+                       h.WriteToken(jsontext.String(ph.SrcConnectionID.String()))
                }
        }
-       enc.IntKey("dcil", h.DestConnectionID.Len())
-       if h.DestConnectionID.Len() > 0 {
-               enc.StringKey("dcid", h.DestConnectionID.String())
+       h.WriteToken(jsontext.String("dcil"))
+       h.WriteToken(jsontext.Int(int64(ph.DestConnectionID.Len())))
+       if ph.DestConnectionID.Len() > 0 {
+               h.WriteToken(jsontext.String("dcid"))
+               h.WriteToken(jsontext.String(ph.DestConnectionID.String()))
        }
-       if h.KeyPhaseBit == logging.KeyPhaseZero || h.KeyPhaseBit == logging.KeyPhaseOne {
-               enc.StringKey("key_phase_bit", h.KeyPhaseBit.String())
+       if ph.KeyPhaseBit == logging.KeyPhaseZero || ph.KeyPhaseBit == logging.KeyPhaseOne {
+               h.WriteToken(jsontext.String("key_phase_bit"))
+               h.WriteToken(jsontext.String(ph.KeyPhaseBit.String()))
        }
-       if h.Token != nil {
-               enc.ObjectKey("token", h.Token)
+       if ph.Token != nil {
+               h.WriteToken(jsontext.String("token"))
+               if err := ph.Token.Encode(enc); err != nil {
+                       return err
+               }
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type packetHeaderVersionNegotiation struct {
@@ -101,13 +114,21 @@ type packetHeaderVersionNegotiation struct {
        DestConnectionID logging.ArbitraryLenConnectionID
 }
 
-func (h packetHeaderVersionNegotiation) IsNil() bool { return false }
-func (h packetHeaderVersionNegotiation) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("packet_type", "version_negotiation")
-       enc.IntKey("scil", h.SrcConnectionID.Len())
-       enc.StringKey("scid", h.SrcConnectionID.String())
-       enc.IntKey("dcil", h.DestConnectionID.Len())
-       enc.StringKey("dcid", h.DestConnectionID.String())
+func (phvn packetHeaderVersionNegotiation) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("packet_type"))
+       h.WriteToken(jsontext.String("version_negotiation"))
+       h.WriteToken(jsontext.String("scil"))
+       h.WriteToken(jsontext.Int(int64(phvn.SrcConnectionID.Len())))
+       h.WriteToken(jsontext.String("scid"))
+       h.WriteToken(jsontext.String(phvn.SrcConnectionID.String()))
+       h.WriteToken(jsontext.String("dcil"))
+       h.WriteToken(jsontext.Int(int64(phvn.DestConnectionID.Len())))
+       h.WriteToken(jsontext.String("dcid"))
+       h.WriteToken(jsontext.String(phvn.DestConnectionID.String()))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 // a minimal header that only outputs the packet type, and potentially a packet number
@@ -116,12 +137,17 @@ type packetHeaderWithType struct {
        PacketNumber logging.PacketNumber
 }
 
-func (h packetHeaderWithType) IsNil() bool { return false }
-func (h packetHeaderWithType) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("packet_type", packetType(h.PacketType).String())
-       if h.PacketNumber != protocol.InvalidPacketNumber {
-               enc.Int64Key("packet_number", int64(h.PacketNumber))
+func (phwt packetHeaderWithType) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("packet_type"))
+       h.WriteToken(jsontext.String(packetType(phwt.PacketType).String()))
+       if phwt.PacketNumber != protocol.InvalidPacketNumber {
+               h.WriteToken(jsontext.String("packet_number"))
+               h.WriteToken(jsontext.Int(int64(phwt.PacketNumber)))
        }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 // a minimal header that only outputs the packet type
@@ -130,10 +156,15 @@ type packetHeaderWithTypeAndPacketNumber struct {
        PacketNumber logging.PacketNumber
 }
 
-func (h packetHeaderWithTypeAndPacketNumber) IsNil() bool { return false }
-func (h packetHeaderWithTypeAndPacketNumber) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("packet_type", packetType(h.PacketType).String())
-       enc.Int64Key("packet_number", int64(h.PacketNumber))
+func (phwtpn packetHeaderWithTypeAndPacketNumber) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("packet_type"))
+       h.WriteToken(jsontext.String(packetType(phwtpn.PacketType).String()))
+       h.WriteToken(jsontext.String("packet_number"))
+       h.WriteToken(jsontext.Int(int64(phwtpn.PacketNumber)))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type shortHeader struct {
@@ -150,12 +181,19 @@ func transformShortHeader(hdr *logging.ShortHeader) *shortHeader {
        }
 }
 
-func (h shortHeader) IsNil() bool { return false }
-func (h shortHeader) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("packet_type", packetType(logging.PacketType1RTT).String())
-       if h.DestConnectionID.Len() > 0 {
-               enc.StringKey("dcid", h.DestConnectionID.String())
+func (sh shortHeader) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("packet_type"))
+       h.WriteToken(jsontext.String(packetType(logging.PacketType1RTT).String()))
+       if sh.DestConnectionID.Len() > 0 {
+               h.WriteToken(jsontext.String("dcid"))
+               h.WriteToken(jsontext.String(sh.DestConnectionID.String()))
        }
-       enc.Int64Key("packet_number", int64(h.PacketNumber))
-       enc.StringKey("key_phase_bit", h.KeyPhaseBit.String())
+       h.WriteToken(jsontext.String("packet_number"))
+       h.WriteToken(jsontext.Int(int64(sh.PacketNumber)))
+       h.WriteToken(jsontext.String("key_phase_bit"))
+       h.WriteToken(jsontext.String(sh.KeyPhaseBit.String()))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
index 1bcb9f5cad9781060a5f01f73bf8eddc0afafbb9..b5d3f2bc5829b09e8ba08161cdf8549fc280fc81 100644 (file)
@@ -5,10 +5,12 @@ import (
        "encoding/json"
        "testing"
 
-       "github.com/francoispqt/gojay"
        "github.com/quic-go/quic-go/internal/protocol"
        "github.com/quic-go/quic-go/internal/wire"
        "github.com/quic-go/quic-go/logging"
+
+       "github.com/quic-go/quic-go/qlog/jsontext"
+
        "github.com/stretchr/testify/require"
 )
 
@@ -34,8 +36,8 @@ func TestPacketTypeFromEncryptionLevel(t *testing.T) {
 
 func checkHeader(t *testing.T, hdr *wire.ExtendedHeader, expected map[string]any) {
        buf := &bytes.Buffer{}
-       enc := gojay.NewEncoder(buf)
-       require.NoError(t, enc.Encode(transformLongHeader(hdr)))
+       enc := jsontext.NewEncoder(buf)
+       require.NoError(t, transformLongHeader(hdr).Encode(enc))
        data := buf.Bytes()
        require.True(t, json.Valid(data))
        checkEncoding(t, data, expected)
index 4c3d33c6fc2965ed25fcdd3184066e0ce759442b..0ed454fb677774dce592a38bac8a49bdbdb66b32 100644 (file)
@@ -5,8 +5,7 @@ import (
        "time"
 
        "github.com/quic-go/quic-go/logging"
-
-       "github.com/francoispqt/gojay"
+       "github.com/quic-go/quic-go/qlog/jsontext"
 )
 
 // Setting of this only works when quic-go is used as a library.
@@ -41,22 +40,38 @@ type topLevel struct {
        trace trace
 }
 
-func (topLevel) IsNil() bool { return false }
-func (l topLevel) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("qlog_format", "JSON-SEQ")
-       enc.StringKey("qlog_version", "0.3")
-       enc.StringKeyOmitEmpty("title", "quic-go qlog")
-       enc.ObjectKey("configuration", configuration{Version: quicGoVersion})
-       enc.ObjectKey("trace", l.trace)
+func (l topLevel) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("qlog_format"))
+       h.WriteToken(jsontext.String("JSON-SEQ"))
+       h.WriteToken(jsontext.String("qlog_version"))
+       h.WriteToken(jsontext.String("0.3"))
+       h.WriteToken(jsontext.String("title"))
+       h.WriteToken(jsontext.String("quic-go qlog"))
+       h.WriteToken(jsontext.String("configuration"))
+       if err := (configuration{Version: quicGoVersion}).Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("trace"))
+       if err := l.trace.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type configuration struct {
        Version string
 }
 
-func (c configuration) IsNil() bool { return false }
-func (c configuration) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKey("code_version", c.Version)
+func (c configuration) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("code_version"))
+       h.WriteToken(jsontext.String(c.Version))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type vantagePoint struct {
@@ -64,10 +79,19 @@ type vantagePoint struct {
        Type string
 }
 
-func (p vantagePoint) IsNil() bool { return false }
-func (p vantagePoint) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.StringKeyOmitEmpty("name", p.Name)
-       enc.StringKeyOmitEmpty("type", p.Type)
+func (p vantagePoint) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       if p.Name != "" {
+               h.WriteToken(jsontext.String("name"))
+               h.WriteToken(jsontext.String(p.Name))
+       }
+       if p.Type != "" {
+               h.WriteToken(jsontext.String("type"))
+               h.WriteToken(jsontext.String(p.Type))
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
 type commonFields struct {
@@ -77,25 +101,43 @@ type commonFields struct {
        ReferenceTime time.Time
 }
 
-func (f commonFields) MarshalJSONObject(enc *gojay.Encoder) {
+func (f commonFields) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
        if f.ODCID != nil {
-               enc.StringKey("ODCID", f.ODCID.String())
-               enc.StringKey("group_id", f.ODCID.String())
+               h.WriteToken(jsontext.String("ODCID"))
+               h.WriteToken(jsontext.String(f.ODCID.String()))
+               h.WriteToken(jsontext.String("group_id"))
+               h.WriteToken(jsontext.String(f.ODCID.String()))
        }
-       enc.StringKeyOmitEmpty("protocol_type", f.ProtocolType)
-       enc.Float64Key("reference_time", float64(f.ReferenceTime.UnixNano())/1e6)
-       enc.StringKey("time_format", "relative")
+       if f.ProtocolType != "" {
+               h.WriteToken(jsontext.String("protocol_type"))
+               h.WriteToken(jsontext.String(f.ProtocolType))
+       }
+       h.WriteToken(jsontext.String("reference_time"))
+       h.WriteToken(jsontext.Float(float64(f.ReferenceTime.UnixNano()) / 1e6))
+       h.WriteToken(jsontext.String("time_format"))
+       h.WriteToken(jsontext.String("relative"))
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
 
-func (f commonFields) IsNil() bool { return false }
-
 type trace struct {
        VantagePoint vantagePoint
        CommonFields commonFields
 }
 
-func (trace) IsNil() bool { return false }
-func (t trace) MarshalJSONObject(enc *gojay.Encoder) {
-       enc.ObjectKey("vantage_point", t.VantagePoint)
-       enc.ObjectKey("common_fields", t.CommonFields)
+func (t trace) Encode(enc *jsontext.Encoder) error {
+       h := encoderHelper{enc: enc}
+       h.WriteToken(jsontext.BeginObject)
+       h.WriteToken(jsontext.String("vantage_point"))
+       if err := t.VantagePoint.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.String("common_fields"))
+       if err := t.CommonFields.Encode(enc); err != nil {
+               return err
+       }
+       h.WriteToken(jsontext.EndObject)
+       return h.err
 }
index 661c54e8b1ab5e64109fcbdada03c134da4e184b..b7fb7cc0236e05c3c1401aa8561d05592cb21ca0 100644 (file)
@@ -7,7 +7,7 @@ import (
        "log"
        "time"
 
-       "github.com/francoispqt/gojay"
+       "github.com/quic-go/quic-go/qlog/jsontext"
 )
 
 const eventChanSize = 50
@@ -50,20 +50,17 @@ func (w *writer) RecordEvent(eventTime time.Time, details eventDetails) {
 func (w *writer) Run() {
        defer close(w.runStopped)
        buf := &bytes.Buffer{}
-       enc := gojay.NewEncoder(buf)
+       enc := jsontext.NewEncoder(buf)
        if err := writeRecordSeparator(buf); err != nil {
                panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
        }
-       if err := enc.Encode(&topLevel{trace: *w.tr}); err != nil {
-               panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
-       }
-       if err := buf.WriteByte('\n'); err != nil {
+       if err := (&topLevel{trace: *w.tr}).Encode(enc); err != nil {
                panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
        }
        if _, err := w.w.Write(buf.Bytes()); err != nil {
                w.encodeErr = err
        }
-       enc = gojay.NewEncoder(w.w)
+       enc = jsontext.NewEncoder(w.w)
        for ev := range w.events {
                if w.encodeErr != nil { // if encoding failed, just continue draining the event channel
                        continue
@@ -72,13 +69,10 @@ func (w *writer) Run() {
                        w.encodeErr = err
                        continue
                }
-               if err := enc.Encode(ev); err != nil {
+               if err := ev.Encode(enc); err != nil {
                        w.encodeErr = err
                        continue
                }
-               if _, err := w.w.Write([]byte{'\n'}); err != nil {
-                       w.encodeErr = err
-               }
        }
 }