qlog: implement a minimal jsontext-like JSON encoder (#5353)

* 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
This commit is contained in:
Marten Seemann
2025-10-06 12:48:40 +08:00
committed by GitHub
parent 7c1ce0efe2
commit e6d5d960e3
14 changed files with 1714 additions and 637 deletions

2
go.mod
View File

@@ -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

157
go.sum
View File

@@ -1,209 +1,52 @@
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=

View 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,

File diff suppressed because it is too large Load Diff

View 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

View 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
}

View 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))

View 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]

314
qlog/jsontext/encoder.go Normal file
View File

@@ -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))
}

View File

@@ -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())
})
}

View 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
ph := transformHeader(&hdr.Header)
ph.PacketNumber = hdr.PacketNumber
ph.KeyPhaseBit = hdr.KeyPhase
return ph
}
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))
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
}

View 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)

View 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
}

View 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
}
}
}