do not send greetings on profile change, fixes #16; update deps

This commit is contained in:
Aine 2025-12-21 16:51:14 +00:00
parent 4f33e7eb8d
commit bf101265f6
No known key found for this signature in database
GPG key ID: 34969C908CCA2804
110 changed files with 4404 additions and 2536 deletions

14
go.mod
View file

@ -17,7 +17,7 @@ require (
github.com/etkecc/go-secgen v1.4.0 github.com/etkecc/go-secgen v1.4.0
github.com/etkecc/go-validator/v2 v2.2.6 github.com/etkecc/go-validator/v2 v2.2.6
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/gabriel-vasile/mimetype v1.4.11 github.com/gabriel-vasile/mimetype v1.4.12
github.com/getsentry/sentry-go v0.40.0 github.com/getsentry/sentry-go v0.40.0
github.com/jhillyerd/enmime/v2 v2.2.0 github.com/jhillyerd/enmime/v2 v2.2.0
github.com/kvannotten/mailstrip v0.0.0-20200711213611-0002f5c0467e github.com/kvannotten/mailstrip v0.0.0-20200711213611-0002f5c0467e
@ -26,9 +26,9 @@ require (
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39 github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
github.com/rs/zerolog v1.34.0 github.com/rs/zerolog v1.34.0
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
maunium.net/go/mautrix v0.26.0 maunium.net/go/mautrix v0.26.1
modernc.org/sqlite v1.40.1 modernc.org/sqlite v1.41.0
) )
require ( require (
@ -39,7 +39,7 @@ require (
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/clipperhouse/displaywidth v0.6.1 // indirect github.com/clipperhouse/displaywidth v0.6.2 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
@ -74,12 +74,12 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/goldmark v1.7.13 // indirect github.com/yuin/goldmark v1.7.13 // indirect
go.mau.fi/util v0.9.3 // indirect go.mau.fi/util v0.9.4 // indirect
golang.org/x/crypto v0.46.0 // indirect golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect golang.org/x/text v0.32.0 // indirect
golang.org/x/tools v0.39.0 // indirect golang.org/x/tools v0.40.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/libc v1.67.1 // indirect modernc.org/libc v1.67.1 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect

32
go.sum
View file

@ -16,8 +16,8 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
github.com/clipperhouse/displaywidth v0.6.1 h1:/zMlAezfDzT2xy6acHBzwIfyu2ic0hgkT83UX5EY2gY= github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
github.com/clipperhouse/displaywidth v0.6.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
@ -59,8 +59,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo= github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo=
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@ -175,16 +175,16 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU= go.mau.fi/util v0.9.4 h1:gWdUff+K2rCynRPysXalqqQyr2ahkSWaestH6YhSpso=
go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ= go.mau.fi/util v0.9.4/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
@ -206,8 +206,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -220,8 +220,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/mautrix v0.26.0 h1:valc2VmZF+oIY4bMq4Cd5H9cEKMRe8eP4FM7iiaYLxI= maunium.net/go/mautrix v0.26.1 h1:FWCC1xY5vwJ5ou3duEBjB6w9IIlwfc9el3q3Mju3Dlg=
maunium.net/go/mautrix v0.26.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q= maunium.net/go/mautrix v0.26.1/go.mod h1:UySSpb8OqXG1sMJ6dDqyzmfcqr2ayZK+KzwqOTAkAOM=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
@ -244,8 +244,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY= modernc.org/sqlite v1.41.0 h1:bJXddp4ZpsqMsNN1vS0jWo4IJTZzb8nWpcgvyCFG9Ck=
modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= modernc.org/sqlite v1.41.0/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View file

@ -91,6 +91,13 @@ func (b *Bot) onReaction(ctx context.Context, evt *event.Event) {
// onBotJoin handles the "bot joined the room" event // onBotJoin handles the "bot joined the room" event
func (b *Bot) onBotJoin(ctx context.Context) { func (b *Bot) onBotJoin(ctx context.Context) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
// do not send introduction/help messages to mailbox rooms where just a profile change occurred
cfg, err := b.cfg.GetRoom(ctx, evt.RoomID)
if err == nil && cfg.Mailbox() != "" {
return
}
// Workaround for membership=join events which are delivered to us twice, // Workaround for membership=join events which are delivered to us twice,
// as described in this bug report: https://github.com/matrix-org/synapse/issues/9768 // as described in this bug report: https://github.com/matrix-org/synapse/issues/9768
_, ok := b.handledMembershipEvents.LoadOrStore(evt.ID, true) _, ok := b.handledMembershipEvents.LoadOrStore(evt.ID, true)

View file

@ -10,14 +10,10 @@ type property uint8
const ( const (
// Always 0 width, includes combining marks, control characters, non-printable, etc // Always 0 width, includes combining marks, control characters, non-printable, etc
_Zero_Width property = iota + 1 _Zero_Width property = iota + 1
// Always 2 wide (East Asian Wide F/W) // Always 2 wide (East Asian Wide F/W, Emoji, Regional Indicator)
_East_Asian_Wide _Wide
// Width depends on EastAsianWidth option // Width depends on EastAsianWidth option
_East_Asian_Ambiguous _East_Asian_Ambiguous
// Extended_Pictographic + Emoji_Presentation
_Emoji
// Regional Indicator symbols (used in flag emoji pairs)
_Regional_Indicator
) )
// lookup returns the trie value for the first UTF-8 encoding in s and // lookup returns the trie value for the first UTF-8 encoding in s and
@ -83,7 +79,7 @@ func lookup[T stringish.Interface](s T) (v uint8, sz int) {
return 0, 1 return 0, 1
} }
// stringWidthTrie. Total size: 17792 bytes (17.38 KiB). Checksum: a03a02693a639d4a. // stringWidthTrie. Total size: 17664 bytes (17.25 KiB). Checksum: c77d82ff2d69f0d2.
// type stringWidthTrie struct { } // type stringWidthTrie struct { }
// func newStringWidthTrie(i int) *stringWidthTrie { // func newStringWidthTrie(i int) *stringWidthTrie {
@ -98,9 +94,9 @@ func lookupValue(n uint32, b byte) uint8 {
} }
} }
// stringWidthValues: 248 blocks, 15872 entries, 15872 bytes // stringWidthValues: 246 blocks, 15744 entries, 15744 bytes
// The third block is the zero block. // The third block is the zero block.
var stringWidthValues = [15872]uint8{ var stringWidthValues = [15744]uint8{
// Block 0x0, offset 0x0 // Block 0x0, offset 0x0
// Block 0x1, offset 0x40 // Block 0x1, offset 0x40
// Block 0x2, offset 0x80 // Block 0x2, offset 0x80
@ -579,13 +575,13 @@ var stringWidthValues = [15872]uint8{
0x167f: 0x0003, 0x167f: 0x0003,
// Block 0x5a, offset 0x1680 // Block 0x5a, offset 0x1680
0x1692: 0x0003, 0x1692: 0x0003,
0x169a: 0x0004, 0x169b: 0x0004, 0x169a: 0x0002, 0x169b: 0x0002,
0x16a9: 0x0002, 0x16a9: 0x0002,
0x16aa: 0x0002, 0x16aa: 0x0002,
// Block 0x5b, offset 0x16c0 // Block 0x5b, offset 0x16c0
0x16e9: 0x0004, 0x16e9: 0x0002,
0x16ea: 0x0004, 0x16eb: 0x0004, 0x16ec: 0x0004, 0x16ea: 0x0002, 0x16eb: 0x0002, 0x16ec: 0x0002,
0x16f0: 0x0004, 0x16f3: 0x0004, 0x16f0: 0x0002, 0x16f3: 0x0002,
// Block 0x5c, offset 0x1700 // Block 0x5c, offset 0x1700
0x1720: 0x0003, 0x1721: 0x0003, 0x1722: 0x0003, 0x1723: 0x0003, 0x1720: 0x0003, 0x1721: 0x0003, 0x1722: 0x0003, 0x1723: 0x0003,
0x1724: 0x0003, 0x1725: 0x0003, 0x1726: 0x0003, 0x1727: 0x0003, 0x1728: 0x0003, 0x1729: 0x0003, 0x1724: 0x0003, 0x1725: 0x0003, 0x1726: 0x0003, 0x1727: 0x0003, 0x1728: 0x0003, 0x1729: 0x0003,
@ -644,63 +640,63 @@ var stringWidthValues = [15872]uint8{
0x1862: 0x0003, 0x1863: 0x0003, 0x1862: 0x0003, 0x1863: 0x0003,
0x1864: 0x0003, 0x1865: 0x0003, 0x1864: 0x0003, 0x1865: 0x0003,
0x186f: 0x0003, 0x186f: 0x0003,
0x187d: 0x0004, 0x187e: 0x0004, 0x187d: 0x0002, 0x187e: 0x0002,
// Block 0x62, offset 0x1880 // Block 0x62, offset 0x1880
0x1885: 0x0003, 0x1885: 0x0003,
0x1886: 0x0003, 0x1889: 0x0003, 0x1886: 0x0003, 0x1889: 0x0003,
0x188e: 0x0003, 0x188f: 0x0003, 0x188e: 0x0003, 0x188f: 0x0003,
0x1894: 0x0004, 0x1895: 0x0004, 0x1894: 0x0002, 0x1895: 0x0002,
0x189c: 0x0003, 0x189c: 0x0003,
0x189e: 0x0003, 0x189e: 0x0003,
0x18b0: 0x0002, 0x18b1: 0x0002, 0x18b2: 0x0002, 0x18b3: 0x0002, 0x18b4: 0x0002, 0x18b5: 0x0002, 0x18b0: 0x0002, 0x18b1: 0x0002, 0x18b2: 0x0002, 0x18b3: 0x0002, 0x18b4: 0x0002, 0x18b5: 0x0002,
0x18b6: 0x0002, 0x18b7: 0x0002, 0x18b6: 0x0002, 0x18b7: 0x0002,
// Block 0x63, offset 0x18c0 // Block 0x63, offset 0x18c0
0x18c0: 0x0003, 0x18c2: 0x0003, 0x18c0: 0x0003, 0x18c2: 0x0003,
0x18c8: 0x0004, 0x18c9: 0x0004, 0x18ca: 0x0004, 0x18cb: 0x0004, 0x18c8: 0x0002, 0x18c9: 0x0002, 0x18ca: 0x0002, 0x18cb: 0x0002,
0x18cc: 0x0004, 0x18cd: 0x0004, 0x18ce: 0x0004, 0x18cf: 0x0004, 0x18d0: 0x0004, 0x18d1: 0x0004, 0x18cc: 0x0002, 0x18cd: 0x0002, 0x18ce: 0x0002, 0x18cf: 0x0002, 0x18d0: 0x0002, 0x18d1: 0x0002,
0x18d2: 0x0004, 0x18d3: 0x0004, 0x18d2: 0x0002, 0x18d3: 0x0002,
0x18e0: 0x0003, 0x18e1: 0x0003, 0x18e3: 0x0003, 0x18e0: 0x0003, 0x18e1: 0x0003, 0x18e3: 0x0003,
0x18e4: 0x0003, 0x18e5: 0x0003, 0x18e7: 0x0003, 0x18e8: 0x0003, 0x18e9: 0x0003, 0x18e4: 0x0003, 0x18e5: 0x0003, 0x18e7: 0x0003, 0x18e8: 0x0003, 0x18e9: 0x0003,
0x18ea: 0x0003, 0x18ec: 0x0003, 0x18ed: 0x0003, 0x18ef: 0x0003, 0x18ea: 0x0003, 0x18ec: 0x0003, 0x18ed: 0x0003, 0x18ef: 0x0003,
0x18ff: 0x0004, 0x18ff: 0x0002,
// Block 0x64, offset 0x1900 // Block 0x64, offset 0x1900
0x190a: 0x0002, 0x190b: 0x0002, 0x190a: 0x0002, 0x190b: 0x0002,
0x190c: 0x0002, 0x190d: 0x0002, 0x190e: 0x0002, 0x190f: 0x0002, 0x190c: 0x0002, 0x190d: 0x0002, 0x190e: 0x0002, 0x190f: 0x0002,
0x1913: 0x0004, 0x1913: 0x0002,
0x191e: 0x0003, 0x191f: 0x0003, 0x1921: 0x0004, 0x191e: 0x0003, 0x191f: 0x0003, 0x1921: 0x0002,
0x192a: 0x0004, 0x192b: 0x0004, 0x192a: 0x0002, 0x192b: 0x0002,
0x193d: 0x0004, 0x193e: 0x0004, 0x193f: 0x0003, 0x193d: 0x0002, 0x193e: 0x0002, 0x193f: 0x0003,
// Block 0x65, offset 0x1940 // Block 0x65, offset 0x1940
0x1944: 0x0004, 0x1945: 0x0004, 0x1944: 0x0002, 0x1945: 0x0002,
0x1946: 0x0003, 0x1947: 0x0003, 0x1948: 0x0003, 0x1949: 0x0003, 0x194a: 0x0003, 0x194b: 0x0003, 0x1946: 0x0003, 0x1947: 0x0003, 0x1948: 0x0003, 0x1949: 0x0003, 0x194a: 0x0003, 0x194b: 0x0003,
0x194c: 0x0003, 0x194d: 0x0003, 0x194e: 0x0004, 0x194f: 0x0003, 0x1950: 0x0003, 0x1951: 0x0003, 0x194c: 0x0003, 0x194d: 0x0003, 0x194e: 0x0002, 0x194f: 0x0003, 0x1950: 0x0003, 0x1951: 0x0003,
0x1952: 0x0003, 0x1953: 0x0003, 0x1954: 0x0004, 0x1955: 0x0003, 0x1956: 0x0003, 0x1957: 0x0003, 0x1952: 0x0003, 0x1953: 0x0003, 0x1954: 0x0002, 0x1955: 0x0003, 0x1956: 0x0003, 0x1957: 0x0003,
0x1958: 0x0003, 0x1959: 0x0003, 0x195a: 0x0003, 0x195b: 0x0003, 0x195c: 0x0003, 0x195d: 0x0003, 0x1958: 0x0003, 0x1959: 0x0003, 0x195a: 0x0003, 0x195b: 0x0003, 0x195c: 0x0003, 0x195d: 0x0003,
0x195e: 0x0003, 0x195f: 0x0003, 0x1960: 0x0003, 0x1961: 0x0003, 0x1963: 0x0003, 0x195e: 0x0003, 0x195f: 0x0003, 0x1960: 0x0003, 0x1961: 0x0003, 0x1963: 0x0003,
0x1968: 0x0003, 0x1969: 0x0003, 0x1968: 0x0003, 0x1969: 0x0003,
0x196a: 0x0004, 0x196b: 0x0003, 0x196c: 0x0003, 0x196d: 0x0003, 0x196e: 0x0003, 0x196f: 0x0003, 0x196a: 0x0002, 0x196b: 0x0003, 0x196c: 0x0003, 0x196d: 0x0003, 0x196e: 0x0003, 0x196f: 0x0003,
0x1970: 0x0003, 0x1971: 0x0003, 0x1972: 0x0004, 0x1973: 0x0004, 0x1974: 0x0003, 0x1975: 0x0004, 0x1970: 0x0003, 0x1971: 0x0003, 0x1972: 0x0002, 0x1973: 0x0002, 0x1974: 0x0003, 0x1975: 0x0002,
0x1976: 0x0003, 0x1977: 0x0003, 0x1978: 0x0003, 0x1979: 0x0003, 0x197a: 0x0004, 0x197b: 0x0003, 0x1976: 0x0003, 0x1977: 0x0003, 0x1978: 0x0003, 0x1979: 0x0003, 0x197a: 0x0002, 0x197b: 0x0003,
0x197c: 0x0003, 0x197d: 0x0004, 0x197e: 0x0003, 0x197f: 0x0003, 0x197c: 0x0003, 0x197d: 0x0002, 0x197e: 0x0003, 0x197f: 0x0003,
// Block 0x66, offset 0x1980 // Block 0x66, offset 0x1980
0x1985: 0x0004, 0x1985: 0x0002,
0x198a: 0x0004, 0x198b: 0x0004, 0x198a: 0x0002, 0x198b: 0x0002,
0x19a8: 0x0004, 0x19a8: 0x0002,
0x19bd: 0x0003, 0x19bd: 0x0003,
// Block 0x67, offset 0x19c0 // Block 0x67, offset 0x19c0
0x19cc: 0x0004, 0x19ce: 0x0004, 0x19cc: 0x0002, 0x19ce: 0x0002,
0x19d3: 0x0004, 0x19d4: 0x0004, 0x19d5: 0x0004, 0x19d7: 0x0004, 0x19d3: 0x0002, 0x19d4: 0x0002, 0x19d5: 0x0002, 0x19d7: 0x0002,
0x19f6: 0x0003, 0x19f7: 0x0003, 0x19f8: 0x0003, 0x19f9: 0x0003, 0x19fa: 0x0003, 0x19fb: 0x0003, 0x19f6: 0x0003, 0x19f7: 0x0003, 0x19f8: 0x0003, 0x19f9: 0x0003, 0x19fa: 0x0003, 0x19fb: 0x0003,
0x19fc: 0x0003, 0x19fd: 0x0003, 0x19fe: 0x0003, 0x19ff: 0x0003, 0x19fc: 0x0003, 0x19fd: 0x0003, 0x19fe: 0x0003, 0x19ff: 0x0003,
// Block 0x68, offset 0x1a00 // Block 0x68, offset 0x1a00
0x1a15: 0x0004, 0x1a16: 0x0004, 0x1a17: 0x0004, 0x1a15: 0x0002, 0x1a16: 0x0002, 0x1a17: 0x0002,
0x1a30: 0x0004, 0x1a30: 0x0002,
0x1a3f: 0x0004, 0x1a3f: 0x0002,
// Block 0x69, offset 0x1a40 // Block 0x69, offset 0x1a40
0x1a5b: 0x0004, 0x1a5c: 0x0004, 0x1a5b: 0x0002, 0x1a5c: 0x0002,
// Block 0x6a, offset 0x1a80 // Block 0x6a, offset 0x1a80
0x1a90: 0x0004, 0x1a90: 0x0002,
0x1a95: 0x0004, 0x1a96: 0x0003, 0x1a97: 0x0003, 0x1a95: 0x0002, 0x1a96: 0x0003, 0x1a97: 0x0003,
0x1a98: 0x0003, 0x1a99: 0x0003, 0x1a98: 0x0003, 0x1a99: 0x0003,
// Block 0x6b, offset 0x1ac0 // Block 0x6b, offset 0x1ac0
0x1aef: 0x0001, 0x1aef: 0x0001,
@ -1275,9 +1271,9 @@ var stringWidthValues = [15872]uint8{
0x3604: 0x0001, 0x3605: 0x0001, 0x3604: 0x0001, 0x3605: 0x0001,
0x3606: 0x0001, 0x3607: 0x0001, 0x3608: 0x0001, 0x3609: 0x0001, 0x360a: 0x0001, 0x3606: 0x0001, 0x3607: 0x0001, 0x3608: 0x0001, 0x3609: 0x0001, 0x360a: 0x0001,
// Block 0xd9, offset 0x3640 // Block 0xd9, offset 0x3640
0x3644: 0x0004, 0x3644: 0x0002,
// Block 0xda, offset 0x3680 // Block 0xda, offset 0x3680
0x368f: 0x0004, 0x368f: 0x0002,
// Block 0xdb, offset 0x36c0 // Block 0xdb, offset 0x36c0
0x36c0: 0x0003, 0x36c1: 0x0003, 0x36c2: 0x0003, 0x36c3: 0x0003, 0x36c4: 0x0003, 0x36c5: 0x0003, 0x36c0: 0x0003, 0x36c1: 0x0003, 0x36c2: 0x0003, 0x36c3: 0x0003, 0x36c4: 0x0003, 0x36c5: 0x0003,
0x36c6: 0x0003, 0x36c7: 0x0003, 0x36c8: 0x0003, 0x36c9: 0x0003, 0x36ca: 0x0003, 0x36c6: 0x0003, 0x36c7: 0x0003, 0x36c8: 0x0003, 0x36c9: 0x0003, 0x36ca: 0x0003,
@ -1304,252 +1300,228 @@ var stringWidthValues = [15872]uint8{
// Block 0xdd, offset 0x3740 // Block 0xdd, offset 0x3740
0x3740: 0x0003, 0x3741: 0x0003, 0x3742: 0x0003, 0x3743: 0x0003, 0x3744: 0x0003, 0x3745: 0x0003, 0x3740: 0x0003, 0x3741: 0x0003, 0x3742: 0x0003, 0x3743: 0x0003, 0x3744: 0x0003, 0x3745: 0x0003,
0x3746: 0x0003, 0x3747: 0x0003, 0x3748: 0x0003, 0x3749: 0x0003, 0x374a: 0x0003, 0x374b: 0x0003, 0x3746: 0x0003, 0x3747: 0x0003, 0x3748: 0x0003, 0x3749: 0x0003, 0x374a: 0x0003, 0x374b: 0x0003,
0x374c: 0x0003, 0x374d: 0x0003, 0x374e: 0x0004, 0x374f: 0x0003, 0x3750: 0x0003, 0x3751: 0x0004, 0x374c: 0x0003, 0x374d: 0x0003, 0x374e: 0x0002, 0x374f: 0x0003, 0x3750: 0x0003, 0x3751: 0x0002,
0x3752: 0x0004, 0x3753: 0x0004, 0x3754: 0x0004, 0x3755: 0x0004, 0x3756: 0x0004, 0x3757: 0x0004, 0x3752: 0x0002, 0x3753: 0x0002, 0x3754: 0x0002, 0x3755: 0x0002, 0x3756: 0x0002, 0x3757: 0x0002,
0x3758: 0x0004, 0x3759: 0x0004, 0x375a: 0x0004, 0x375b: 0x0003, 0x375c: 0x0003, 0x375d: 0x0003, 0x3758: 0x0002, 0x3759: 0x0002, 0x375a: 0x0002, 0x375b: 0x0003, 0x375c: 0x0003, 0x375d: 0x0003,
0x375e: 0x0003, 0x375f: 0x0003, 0x3760: 0x0003, 0x3761: 0x0003, 0x3762: 0x0003, 0x3763: 0x0003, 0x375e: 0x0003, 0x375f: 0x0003, 0x3760: 0x0003, 0x3761: 0x0003, 0x3762: 0x0003, 0x3763: 0x0003,
0x3764: 0x0003, 0x3765: 0x0003, 0x3766: 0x0003, 0x3767: 0x0003, 0x3768: 0x0003, 0x3769: 0x0003, 0x3764: 0x0003, 0x3765: 0x0003, 0x3766: 0x0003, 0x3767: 0x0003, 0x3768: 0x0003, 0x3769: 0x0003,
0x376a: 0x0003, 0x376b: 0x0003, 0x376c: 0x0003, 0x376a: 0x0003, 0x376b: 0x0003, 0x376c: 0x0003,
// Block 0xde, offset 0x3780 // Block 0xde, offset 0x3780
0x37a6: 0x0005, 0x37a7: 0x0005, 0x37a8: 0x0005, 0x37a9: 0x0005, 0x37a6: 0x0002, 0x37a7: 0x0002, 0x37a8: 0x0002, 0x37a9: 0x0002,
0x37aa: 0x0005, 0x37ab: 0x0005, 0x37ac: 0x0005, 0x37ad: 0x0005, 0x37ae: 0x0005, 0x37af: 0x0005, 0x37aa: 0x0002, 0x37ab: 0x0002, 0x37ac: 0x0002, 0x37ad: 0x0002, 0x37ae: 0x0002, 0x37af: 0x0002,
0x37b0: 0x0005, 0x37b1: 0x0005, 0x37b2: 0x0005, 0x37b3: 0x0005, 0x37b4: 0x0005, 0x37b5: 0x0005, 0x37b0: 0x0002, 0x37b1: 0x0002, 0x37b2: 0x0002, 0x37b3: 0x0002, 0x37b4: 0x0002, 0x37b5: 0x0002,
0x37b6: 0x0005, 0x37b7: 0x0005, 0x37b8: 0x0005, 0x37b9: 0x0005, 0x37ba: 0x0005, 0x37bb: 0x0005, 0x37b6: 0x0002, 0x37b7: 0x0002, 0x37b8: 0x0002, 0x37b9: 0x0002, 0x37ba: 0x0002, 0x37bb: 0x0002,
0x37bc: 0x0005, 0x37bd: 0x0005, 0x37be: 0x0005, 0x37bf: 0x0005, 0x37bc: 0x0002, 0x37bd: 0x0002, 0x37be: 0x0002, 0x37bf: 0x0002,
// Block 0xdf, offset 0x37c0 // Block 0xdf, offset 0x37c0
0x37c0: 0x0002, 0x37c1: 0x0004, 0x37c2: 0x0002, 0x37c0: 0x0002, 0x37c1: 0x0002, 0x37c2: 0x0002,
0x37d0: 0x0002, 0x37d1: 0x0002, 0x37d0: 0x0002, 0x37d1: 0x0002,
0x37d2: 0x0002, 0x37d3: 0x0002, 0x37d4: 0x0002, 0x37d5: 0x0002, 0x37d6: 0x0002, 0x37d7: 0x0002, 0x37d2: 0x0002, 0x37d3: 0x0002, 0x37d4: 0x0002, 0x37d5: 0x0002, 0x37d6: 0x0002, 0x37d7: 0x0002,
0x37d8: 0x0002, 0x37d9: 0x0002, 0x37da: 0x0004, 0x37db: 0x0002, 0x37dc: 0x0002, 0x37dd: 0x0002, 0x37d8: 0x0002, 0x37d9: 0x0002, 0x37da: 0x0002, 0x37db: 0x0002, 0x37dc: 0x0002, 0x37dd: 0x0002,
0x37de: 0x0002, 0x37df: 0x0002, 0x37e0: 0x0002, 0x37e1: 0x0002, 0x37e2: 0x0002, 0x37e3: 0x0002, 0x37de: 0x0002, 0x37df: 0x0002, 0x37e0: 0x0002, 0x37e1: 0x0002, 0x37e2: 0x0002, 0x37e3: 0x0002,
0x37e4: 0x0002, 0x37e5: 0x0002, 0x37e6: 0x0002, 0x37e7: 0x0002, 0x37e8: 0x0002, 0x37e9: 0x0002, 0x37e4: 0x0002, 0x37e5: 0x0002, 0x37e6: 0x0002, 0x37e7: 0x0002, 0x37e8: 0x0002, 0x37e9: 0x0002,
0x37ea: 0x0002, 0x37eb: 0x0002, 0x37ec: 0x0002, 0x37ed: 0x0002, 0x37ee: 0x0002, 0x37ef: 0x0004, 0x37ea: 0x0002, 0x37eb: 0x0002, 0x37ec: 0x0002, 0x37ed: 0x0002, 0x37ee: 0x0002, 0x37ef: 0x0002,
0x37f0: 0x0002, 0x37f1: 0x0002, 0x37f2: 0x0004, 0x37f3: 0x0004, 0x37f4: 0x0004, 0x37f5: 0x0004, 0x37f0: 0x0002, 0x37f1: 0x0002, 0x37f2: 0x0002, 0x37f3: 0x0002, 0x37f4: 0x0002, 0x37f5: 0x0002,
0x37f6: 0x0004, 0x37f7: 0x0002, 0x37f8: 0x0004, 0x37f9: 0x0004, 0x37fa: 0x0004, 0x37fb: 0x0002, 0x37f6: 0x0002, 0x37f7: 0x0002, 0x37f8: 0x0002, 0x37f9: 0x0002, 0x37fa: 0x0002, 0x37fb: 0x0002,
// Block 0xe0, offset 0x3800 // Block 0xe0, offset 0x3800
0x3800: 0x0002, 0x3801: 0x0002, 0x3802: 0x0002, 0x3803: 0x0002, 0x3804: 0x0002, 0x3805: 0x0002, 0x3800: 0x0002, 0x3801: 0x0002, 0x3802: 0x0002, 0x3803: 0x0002, 0x3804: 0x0002, 0x3805: 0x0002,
0x3806: 0x0002, 0x3807: 0x0002, 0x3808: 0x0002, 0x3806: 0x0002, 0x3807: 0x0002, 0x3808: 0x0002,
0x3810: 0x0004, 0x3811: 0x0004, 0x3810: 0x0002, 0x3811: 0x0002,
0x3820: 0x0002, 0x3821: 0x0002, 0x3822: 0x0002, 0x3823: 0x0002, 0x3820: 0x0002, 0x3821: 0x0002, 0x3822: 0x0002, 0x3823: 0x0002,
0x3824: 0x0002, 0x3825: 0x0002, 0x3824: 0x0002, 0x3825: 0x0002,
// Block 0xe1, offset 0x3840 // Block 0xe1, offset 0x3840
0x3840: 0x0004, 0x3841: 0x0004, 0x3842: 0x0004, 0x3843: 0x0004, 0x3844: 0x0004, 0x3845: 0x0004, 0x3840: 0x0002, 0x3841: 0x0002, 0x3842: 0x0002, 0x3843: 0x0002, 0x3844: 0x0002, 0x3845: 0x0002,
0x3846: 0x0004, 0x3847: 0x0004, 0x3848: 0x0004, 0x3849: 0x0004, 0x384a: 0x0004, 0x384b: 0x0004, 0x3846: 0x0002, 0x3847: 0x0002, 0x3848: 0x0002, 0x3849: 0x0002, 0x384a: 0x0002, 0x384b: 0x0002,
0x384c: 0x0004, 0x384d: 0x0004, 0x384e: 0x0004, 0x384f: 0x0004, 0x3850: 0x0004, 0x3851: 0x0004, 0x384c: 0x0002, 0x384d: 0x0002, 0x384e: 0x0002, 0x384f: 0x0002, 0x3850: 0x0002, 0x3851: 0x0002,
0x3852: 0x0004, 0x3853: 0x0004, 0x3854: 0x0004, 0x3855: 0x0004, 0x3856: 0x0004, 0x3857: 0x0004, 0x3852: 0x0002, 0x3853: 0x0002, 0x3854: 0x0002, 0x3855: 0x0002, 0x3856: 0x0002, 0x3857: 0x0002,
0x3858: 0x0004, 0x3859: 0x0004, 0x385a: 0x0004, 0x385b: 0x0004, 0x385c: 0x0004, 0x385d: 0x0004, 0x3858: 0x0002, 0x3859: 0x0002, 0x385a: 0x0002, 0x385b: 0x0002, 0x385c: 0x0002, 0x385d: 0x0002,
0x385e: 0x0004, 0x385f: 0x0004, 0x3860: 0x0004, 0x385e: 0x0002, 0x385f: 0x0002, 0x3860: 0x0002,
0x386d: 0x0004, 0x386e: 0x0004, 0x386f: 0x0004, 0x386d: 0x0002, 0x386e: 0x0002, 0x386f: 0x0002,
0x3870: 0x0004, 0x3871: 0x0004, 0x3872: 0x0004, 0x3873: 0x0004, 0x3874: 0x0004, 0x3875: 0x0004, 0x3870: 0x0002, 0x3871: 0x0002, 0x3872: 0x0002, 0x3873: 0x0002, 0x3874: 0x0002, 0x3875: 0x0002,
0x3877: 0x0004, 0x3878: 0x0004, 0x3879: 0x0004, 0x387a: 0x0004, 0x387b: 0x0004, 0x3877: 0x0002, 0x3878: 0x0002, 0x3879: 0x0002, 0x387a: 0x0002, 0x387b: 0x0002,
0x387c: 0x0004, 0x387d: 0x0004, 0x387e: 0x0004, 0x387f: 0x0004, 0x387c: 0x0002, 0x387d: 0x0002, 0x387e: 0x0002, 0x387f: 0x0002,
// Block 0xe2, offset 0x3880 // Block 0xe2, offset 0x3880
0x3880: 0x0004, 0x3881: 0x0004, 0x3882: 0x0004, 0x3883: 0x0004, 0x3884: 0x0004, 0x3885: 0x0004, 0x3880: 0x0002, 0x3881: 0x0002, 0x3882: 0x0002, 0x3883: 0x0002, 0x3884: 0x0002, 0x3885: 0x0002,
0x3886: 0x0004, 0x3887: 0x0004, 0x3888: 0x0004, 0x3889: 0x0004, 0x388a: 0x0004, 0x388b: 0x0004, 0x3886: 0x0002, 0x3887: 0x0002, 0x3888: 0x0002, 0x3889: 0x0002, 0x388a: 0x0002, 0x388b: 0x0002,
0x388c: 0x0004, 0x388d: 0x0004, 0x388e: 0x0004, 0x388f: 0x0004, 0x3890: 0x0004, 0x3891: 0x0004, 0x388c: 0x0002, 0x388d: 0x0002, 0x388e: 0x0002, 0x388f: 0x0002, 0x3890: 0x0002, 0x3891: 0x0002,
0x3892: 0x0004, 0x3893: 0x0004, 0x3894: 0x0004, 0x3895: 0x0004, 0x3896: 0x0004, 0x3897: 0x0004, 0x3892: 0x0002, 0x3893: 0x0002, 0x3894: 0x0002, 0x3895: 0x0002, 0x3896: 0x0002, 0x3897: 0x0002,
0x3898: 0x0004, 0x3899: 0x0004, 0x389a: 0x0004, 0x389b: 0x0004, 0x389c: 0x0004, 0x389d: 0x0004, 0x3898: 0x0002, 0x3899: 0x0002, 0x389a: 0x0002, 0x389b: 0x0002, 0x389c: 0x0002, 0x389d: 0x0002,
0x389e: 0x0004, 0x389f: 0x0004, 0x38a0: 0x0004, 0x38a1: 0x0004, 0x38a2: 0x0004, 0x38a3: 0x0004, 0x389e: 0x0002, 0x389f: 0x0002, 0x38a0: 0x0002, 0x38a1: 0x0002, 0x38a2: 0x0002, 0x38a3: 0x0002,
0x38a4: 0x0004, 0x38a5: 0x0004, 0x38a6: 0x0004, 0x38a7: 0x0004, 0x38a8: 0x0004, 0x38a9: 0x0004, 0x38a4: 0x0002, 0x38a5: 0x0002, 0x38a6: 0x0002, 0x38a7: 0x0002, 0x38a8: 0x0002, 0x38a9: 0x0002,
0x38aa: 0x0004, 0x38ab: 0x0004, 0x38ac: 0x0004, 0x38ad: 0x0004, 0x38ae: 0x0004, 0x38af: 0x0004, 0x38aa: 0x0002, 0x38ab: 0x0002, 0x38ac: 0x0002, 0x38ad: 0x0002, 0x38ae: 0x0002, 0x38af: 0x0002,
0x38b0: 0x0004, 0x38b1: 0x0004, 0x38b2: 0x0004, 0x38b3: 0x0004, 0x38b4: 0x0004, 0x38b5: 0x0004, 0x38b0: 0x0002, 0x38b1: 0x0002, 0x38b2: 0x0002, 0x38b3: 0x0002, 0x38b4: 0x0002, 0x38b5: 0x0002,
0x38b6: 0x0004, 0x38b7: 0x0004, 0x38b8: 0x0004, 0x38b9: 0x0004, 0x38ba: 0x0004, 0x38bb: 0x0004, 0x38b6: 0x0002, 0x38b7: 0x0002, 0x38b8: 0x0002, 0x38b9: 0x0002, 0x38ba: 0x0002, 0x38bb: 0x0002,
0x38bc: 0x0004, 0x38be: 0x0004, 0x38bf: 0x0004, 0x38bc: 0x0002, 0x38be: 0x0002, 0x38bf: 0x0002,
// Block 0xe3, offset 0x38c0 // Block 0xe3, offset 0x38c0
0x38c0: 0x0004, 0x38c1: 0x0004, 0x38c2: 0x0004, 0x38c3: 0x0004, 0x38c4: 0x0004, 0x38c5: 0x0004, 0x38c0: 0x0002, 0x38c1: 0x0002, 0x38c2: 0x0002, 0x38c3: 0x0002, 0x38c4: 0x0002, 0x38c5: 0x0002,
0x38c6: 0x0004, 0x38c7: 0x0004, 0x38c8: 0x0004, 0x38c9: 0x0004, 0x38ca: 0x0004, 0x38cb: 0x0004, 0x38c6: 0x0002, 0x38c7: 0x0002, 0x38c8: 0x0002, 0x38c9: 0x0002, 0x38ca: 0x0002, 0x38cb: 0x0002,
0x38cc: 0x0004, 0x38cd: 0x0004, 0x38ce: 0x0004, 0x38cf: 0x0004, 0x38d0: 0x0004, 0x38d1: 0x0004, 0x38cc: 0x0002, 0x38cd: 0x0002, 0x38ce: 0x0002, 0x38cf: 0x0002, 0x38d0: 0x0002, 0x38d1: 0x0002,
0x38d2: 0x0004, 0x38d3: 0x0004, 0x38d2: 0x0002, 0x38d3: 0x0002,
0x38e0: 0x0004, 0x38e1: 0x0004, 0x38e2: 0x0004, 0x38e3: 0x0004, 0x38e0: 0x0002, 0x38e1: 0x0002, 0x38e2: 0x0002, 0x38e3: 0x0002,
0x38e4: 0x0004, 0x38e5: 0x0004, 0x38e6: 0x0004, 0x38e7: 0x0004, 0x38e8: 0x0004, 0x38e9: 0x0004, 0x38e4: 0x0002, 0x38e5: 0x0002, 0x38e6: 0x0002, 0x38e7: 0x0002, 0x38e8: 0x0002, 0x38e9: 0x0002,
0x38ea: 0x0004, 0x38eb: 0x0004, 0x38ec: 0x0004, 0x38ed: 0x0004, 0x38ee: 0x0004, 0x38ef: 0x0004, 0x38ea: 0x0002, 0x38eb: 0x0002, 0x38ec: 0x0002, 0x38ed: 0x0002, 0x38ee: 0x0002, 0x38ef: 0x0002,
0x38f0: 0x0004, 0x38f1: 0x0004, 0x38f2: 0x0004, 0x38f3: 0x0004, 0x38f4: 0x0004, 0x38f5: 0x0004, 0x38f0: 0x0002, 0x38f1: 0x0002, 0x38f2: 0x0002, 0x38f3: 0x0002, 0x38f4: 0x0002, 0x38f5: 0x0002,
0x38f6: 0x0004, 0x38f7: 0x0004, 0x38f8: 0x0004, 0x38f9: 0x0004, 0x38fa: 0x0004, 0x38fb: 0x0004, 0x38f6: 0x0002, 0x38f7: 0x0002, 0x38f8: 0x0002, 0x38f9: 0x0002, 0x38fa: 0x0002, 0x38fb: 0x0002,
0x38fc: 0x0004, 0x38fd: 0x0004, 0x38fe: 0x0004, 0x38ff: 0x0004, 0x38fc: 0x0002, 0x38fd: 0x0002, 0x38fe: 0x0002, 0x38ff: 0x0002,
// Block 0xe4, offset 0x3900 // Block 0xe4, offset 0x3900
0x3900: 0x0004, 0x3901: 0x0004, 0x3902: 0x0004, 0x3903: 0x0004, 0x3904: 0x0004, 0x3905: 0x0004, 0x3900: 0x0002, 0x3901: 0x0002, 0x3902: 0x0002, 0x3903: 0x0002, 0x3904: 0x0002, 0x3905: 0x0002,
0x3906: 0x0004, 0x3907: 0x0004, 0x3908: 0x0004, 0x3909: 0x0004, 0x390a: 0x0004, 0x3906: 0x0002, 0x3907: 0x0002, 0x3908: 0x0002, 0x3909: 0x0002, 0x390a: 0x0002,
0x390f: 0x0004, 0x3910: 0x0004, 0x3911: 0x0004, 0x390f: 0x0002, 0x3910: 0x0002, 0x3911: 0x0002,
0x3912: 0x0004, 0x3913: 0x0004, 0x3912: 0x0002, 0x3913: 0x0002,
0x3920: 0x0004, 0x3921: 0x0004, 0x3922: 0x0004, 0x3923: 0x0004, 0x3920: 0x0002, 0x3921: 0x0002, 0x3922: 0x0002, 0x3923: 0x0002,
0x3924: 0x0004, 0x3925: 0x0004, 0x3926: 0x0004, 0x3927: 0x0004, 0x3928: 0x0004, 0x3929: 0x0004, 0x3924: 0x0002, 0x3925: 0x0002, 0x3926: 0x0002, 0x3927: 0x0002, 0x3928: 0x0002, 0x3929: 0x0002,
0x392a: 0x0004, 0x392b: 0x0004, 0x392c: 0x0004, 0x392d: 0x0004, 0x392e: 0x0004, 0x392f: 0x0004, 0x392a: 0x0002, 0x392b: 0x0002, 0x392c: 0x0002, 0x392d: 0x0002, 0x392e: 0x0002, 0x392f: 0x0002,
0x3930: 0x0004, 0x3934: 0x0004, 0x3930: 0x0002, 0x3934: 0x0002,
0x3938: 0x0004, 0x3939: 0x0004, 0x393a: 0x0004, 0x393b: 0x0002, 0x3938: 0x0002, 0x3939: 0x0002, 0x393a: 0x0002, 0x393b: 0x0002,
0x393c: 0x0002, 0x393d: 0x0002, 0x393e: 0x0002, 0x393f: 0x0002, 0x393c: 0x0002, 0x393d: 0x0002, 0x393e: 0x0002, 0x393f: 0x0002,
// Block 0xe5, offset 0x3940 // Block 0xe5, offset 0x3940
0x3940: 0x0004, 0x3941: 0x0004, 0x3942: 0x0004, 0x3943: 0x0004, 0x3944: 0x0004, 0x3945: 0x0004, 0x3940: 0x0002, 0x3941: 0x0002, 0x3942: 0x0002, 0x3943: 0x0002, 0x3944: 0x0002, 0x3945: 0x0002,
0x3946: 0x0004, 0x3947: 0x0004, 0x3948: 0x0004, 0x3949: 0x0004, 0x394a: 0x0004, 0x394b: 0x0004, 0x3946: 0x0002, 0x3947: 0x0002, 0x3948: 0x0002, 0x3949: 0x0002, 0x394a: 0x0002, 0x394b: 0x0002,
0x394c: 0x0004, 0x394d: 0x0004, 0x394e: 0x0004, 0x394f: 0x0004, 0x3950: 0x0004, 0x3951: 0x0004, 0x394c: 0x0002, 0x394d: 0x0002, 0x394e: 0x0002, 0x394f: 0x0002, 0x3950: 0x0002, 0x3951: 0x0002,
0x3952: 0x0004, 0x3953: 0x0004, 0x3954: 0x0004, 0x3955: 0x0004, 0x3956: 0x0004, 0x3957: 0x0004, 0x3952: 0x0002, 0x3953: 0x0002, 0x3954: 0x0002, 0x3955: 0x0002, 0x3956: 0x0002, 0x3957: 0x0002,
0x3958: 0x0004, 0x3959: 0x0004, 0x395a: 0x0004, 0x395b: 0x0004, 0x395c: 0x0004, 0x395d: 0x0004, 0x3958: 0x0002, 0x3959: 0x0002, 0x395a: 0x0002, 0x395b: 0x0002, 0x395c: 0x0002, 0x395d: 0x0002,
0x395e: 0x0004, 0x395f: 0x0004, 0x3960: 0x0004, 0x3961: 0x0004, 0x3962: 0x0004, 0x3963: 0x0004, 0x395e: 0x0002, 0x395f: 0x0002, 0x3960: 0x0002, 0x3961: 0x0002, 0x3962: 0x0002, 0x3963: 0x0002,
0x3964: 0x0004, 0x3965: 0x0004, 0x3966: 0x0004, 0x3967: 0x0004, 0x3968: 0x0004, 0x3969: 0x0004, 0x3964: 0x0002, 0x3965: 0x0002, 0x3966: 0x0002, 0x3967: 0x0002, 0x3968: 0x0002, 0x3969: 0x0002,
0x396a: 0x0004, 0x396b: 0x0004, 0x396c: 0x0004, 0x396d: 0x0004, 0x396e: 0x0004, 0x396f: 0x0004, 0x396a: 0x0002, 0x396b: 0x0002, 0x396c: 0x0002, 0x396d: 0x0002, 0x396e: 0x0002, 0x396f: 0x0002,
0x3970: 0x0004, 0x3971: 0x0004, 0x3972: 0x0004, 0x3973: 0x0004, 0x3974: 0x0004, 0x3975: 0x0004, 0x3970: 0x0002, 0x3971: 0x0002, 0x3972: 0x0002, 0x3973: 0x0002, 0x3974: 0x0002, 0x3975: 0x0002,
0x3976: 0x0004, 0x3977: 0x0004, 0x3978: 0x0004, 0x3979: 0x0004, 0x397a: 0x0004, 0x397b: 0x0004, 0x3976: 0x0002, 0x3977: 0x0002, 0x3978: 0x0002, 0x3979: 0x0002, 0x397a: 0x0002, 0x397b: 0x0002,
0x397c: 0x0004, 0x397d: 0x0004, 0x397e: 0x0004, 0x397c: 0x0002, 0x397d: 0x0002, 0x397e: 0x0002,
// Block 0xe6, offset 0x3980 // Block 0xe6, offset 0x3980
0x3980: 0x0004, 0x3982: 0x0004, 0x3983: 0x0004, 0x3984: 0x0004, 0x3985: 0x0004, 0x3980: 0x0002, 0x3982: 0x0002, 0x3983: 0x0002, 0x3984: 0x0002, 0x3985: 0x0002,
0x3986: 0x0004, 0x3987: 0x0004, 0x3988: 0x0004, 0x3989: 0x0004, 0x398a: 0x0004, 0x398b: 0x0004, 0x3986: 0x0002, 0x3987: 0x0002, 0x3988: 0x0002, 0x3989: 0x0002, 0x398a: 0x0002, 0x398b: 0x0002,
0x398c: 0x0004, 0x398d: 0x0004, 0x398e: 0x0004, 0x398f: 0x0004, 0x3990: 0x0004, 0x3991: 0x0004, 0x398c: 0x0002, 0x398d: 0x0002, 0x398e: 0x0002, 0x398f: 0x0002, 0x3990: 0x0002, 0x3991: 0x0002,
0x3992: 0x0004, 0x3993: 0x0004, 0x3994: 0x0004, 0x3995: 0x0004, 0x3996: 0x0004, 0x3997: 0x0004, 0x3992: 0x0002, 0x3993: 0x0002, 0x3994: 0x0002, 0x3995: 0x0002, 0x3996: 0x0002, 0x3997: 0x0002,
0x3998: 0x0004, 0x3999: 0x0004, 0x399a: 0x0004, 0x399b: 0x0004, 0x399c: 0x0004, 0x399d: 0x0004, 0x3998: 0x0002, 0x3999: 0x0002, 0x399a: 0x0002, 0x399b: 0x0002, 0x399c: 0x0002, 0x399d: 0x0002,
0x399e: 0x0004, 0x399f: 0x0004, 0x39a0: 0x0004, 0x39a1: 0x0004, 0x39a2: 0x0004, 0x39a3: 0x0004, 0x399e: 0x0002, 0x399f: 0x0002, 0x39a0: 0x0002, 0x39a1: 0x0002, 0x39a2: 0x0002, 0x39a3: 0x0002,
0x39a4: 0x0004, 0x39a5: 0x0004, 0x39a6: 0x0004, 0x39a7: 0x0004, 0x39a8: 0x0004, 0x39a9: 0x0004, 0x39a4: 0x0002, 0x39a5: 0x0002, 0x39a6: 0x0002, 0x39a7: 0x0002, 0x39a8: 0x0002, 0x39a9: 0x0002,
0x39aa: 0x0004, 0x39ab: 0x0004, 0x39ac: 0x0004, 0x39ad: 0x0004, 0x39ae: 0x0004, 0x39af: 0x0004, 0x39aa: 0x0002, 0x39ab: 0x0002, 0x39ac: 0x0002, 0x39ad: 0x0002, 0x39ae: 0x0002, 0x39af: 0x0002,
0x39b0: 0x0004, 0x39b1: 0x0004, 0x39b2: 0x0004, 0x39b3: 0x0004, 0x39b4: 0x0004, 0x39b5: 0x0004, 0x39b0: 0x0002, 0x39b1: 0x0002, 0x39b2: 0x0002, 0x39b3: 0x0002, 0x39b4: 0x0002, 0x39b5: 0x0002,
0x39b6: 0x0004, 0x39b7: 0x0004, 0x39b8: 0x0004, 0x39b9: 0x0004, 0x39ba: 0x0004, 0x39bb: 0x0004, 0x39b6: 0x0002, 0x39b7: 0x0002, 0x39b8: 0x0002, 0x39b9: 0x0002, 0x39ba: 0x0002, 0x39bb: 0x0002,
0x39bc: 0x0004, 0x39bd: 0x0004, 0x39be: 0x0004, 0x39bf: 0x0004, 0x39bc: 0x0002, 0x39bd: 0x0002, 0x39be: 0x0002, 0x39bf: 0x0002,
// Block 0xe7, offset 0x39c0 // Block 0xe7, offset 0x39c0
0x39c0: 0x0004, 0x39c1: 0x0004, 0x39c2: 0x0004, 0x39c3: 0x0004, 0x39c4: 0x0004, 0x39c5: 0x0004, 0x39c0: 0x0002, 0x39c1: 0x0002, 0x39c2: 0x0002, 0x39c3: 0x0002, 0x39c4: 0x0002, 0x39c5: 0x0002,
0x39c6: 0x0004, 0x39c7: 0x0004, 0x39c8: 0x0004, 0x39c9: 0x0004, 0x39ca: 0x0004, 0x39cb: 0x0004, 0x39c6: 0x0002, 0x39c7: 0x0002, 0x39c8: 0x0002, 0x39c9: 0x0002, 0x39ca: 0x0002, 0x39cb: 0x0002,
0x39cc: 0x0004, 0x39cd: 0x0004, 0x39ce: 0x0004, 0x39cf: 0x0004, 0x39d0: 0x0004, 0x39d1: 0x0004, 0x39cc: 0x0002, 0x39cd: 0x0002, 0x39ce: 0x0002, 0x39cf: 0x0002, 0x39d0: 0x0002, 0x39d1: 0x0002,
0x39d2: 0x0004, 0x39d3: 0x0004, 0x39d4: 0x0004, 0x39d5: 0x0004, 0x39d6: 0x0004, 0x39d7: 0x0004, 0x39d2: 0x0002, 0x39d3: 0x0002, 0x39d4: 0x0002, 0x39d5: 0x0002, 0x39d6: 0x0002, 0x39d7: 0x0002,
0x39d8: 0x0004, 0x39d9: 0x0004, 0x39da: 0x0004, 0x39db: 0x0004, 0x39dc: 0x0004, 0x39dd: 0x0004, 0x39d8: 0x0002, 0x39d9: 0x0002, 0x39da: 0x0002, 0x39db: 0x0002, 0x39dc: 0x0002, 0x39dd: 0x0002,
0x39de: 0x0004, 0x39df: 0x0004, 0x39e0: 0x0004, 0x39e1: 0x0004, 0x39e2: 0x0004, 0x39e3: 0x0004, 0x39de: 0x0002, 0x39df: 0x0002, 0x39e0: 0x0002, 0x39e1: 0x0002, 0x39e2: 0x0002, 0x39e3: 0x0002,
0x39e4: 0x0004, 0x39e5: 0x0004, 0x39e6: 0x0004, 0x39e7: 0x0004, 0x39e8: 0x0004, 0x39e9: 0x0004, 0x39e4: 0x0002, 0x39e5: 0x0002, 0x39e6: 0x0002, 0x39e7: 0x0002, 0x39e8: 0x0002, 0x39e9: 0x0002,
0x39ea: 0x0004, 0x39eb: 0x0004, 0x39ec: 0x0004, 0x39ed: 0x0004, 0x39ee: 0x0004, 0x39ef: 0x0004, 0x39ea: 0x0002, 0x39eb: 0x0002, 0x39ec: 0x0002, 0x39ed: 0x0002, 0x39ee: 0x0002, 0x39ef: 0x0002,
0x39f0: 0x0004, 0x39f1: 0x0004, 0x39f2: 0x0004, 0x39f3: 0x0004, 0x39f4: 0x0004, 0x39f5: 0x0004, 0x39f0: 0x0002, 0x39f1: 0x0002, 0x39f2: 0x0002, 0x39f3: 0x0002, 0x39f4: 0x0002, 0x39f5: 0x0002,
0x39f6: 0x0004, 0x39f7: 0x0004, 0x39f8: 0x0004, 0x39f9: 0x0004, 0x39fa: 0x0004, 0x39fb: 0x0004, 0x39f6: 0x0002, 0x39f7: 0x0002, 0x39f8: 0x0002, 0x39f9: 0x0002, 0x39fa: 0x0002, 0x39fb: 0x0002,
0x39fc: 0x0004, 0x39fd: 0x0004, 0x39fe: 0x0004, 0x39ff: 0x0004, 0x39fc: 0x0002, 0x39ff: 0x0002,
// Block 0xe8, offset 0x3a00 // Block 0xe8, offset 0x3a00
0x3a00: 0x0004, 0x3a01: 0x0004, 0x3a02: 0x0004, 0x3a03: 0x0004, 0x3a04: 0x0004, 0x3a05: 0x0004, 0x3a00: 0x0002, 0x3a01: 0x0002, 0x3a02: 0x0002, 0x3a03: 0x0002, 0x3a04: 0x0002, 0x3a05: 0x0002,
0x3a06: 0x0004, 0x3a07: 0x0004, 0x3a08: 0x0004, 0x3a09: 0x0004, 0x3a0a: 0x0004, 0x3a0b: 0x0004, 0x3a06: 0x0002, 0x3a07: 0x0002, 0x3a08: 0x0002, 0x3a09: 0x0002, 0x3a0a: 0x0002, 0x3a0b: 0x0002,
0x3a0c: 0x0004, 0x3a0d: 0x0004, 0x3a0e: 0x0004, 0x3a0f: 0x0004, 0x3a10: 0x0004, 0x3a11: 0x0004, 0x3a0c: 0x0002, 0x3a0d: 0x0002, 0x3a0e: 0x0002, 0x3a0f: 0x0002, 0x3a10: 0x0002, 0x3a11: 0x0002,
0x3a12: 0x0004, 0x3a13: 0x0004, 0x3a14: 0x0004, 0x3a15: 0x0004, 0x3a16: 0x0004, 0x3a17: 0x0004, 0x3a12: 0x0002, 0x3a13: 0x0002, 0x3a14: 0x0002, 0x3a15: 0x0002, 0x3a16: 0x0002, 0x3a17: 0x0002,
0x3a18: 0x0004, 0x3a19: 0x0004, 0x3a1a: 0x0004, 0x3a1b: 0x0004, 0x3a1c: 0x0004, 0x3a1d: 0x0004, 0x3a18: 0x0002, 0x3a19: 0x0002, 0x3a1a: 0x0002, 0x3a1b: 0x0002, 0x3a1c: 0x0002, 0x3a1d: 0x0002,
0x3a1e: 0x0004, 0x3a1f: 0x0004, 0x3a20: 0x0004, 0x3a21: 0x0004, 0x3a22: 0x0004, 0x3a23: 0x0004, 0x3a1e: 0x0002, 0x3a1f: 0x0002, 0x3a20: 0x0002, 0x3a21: 0x0002, 0x3a22: 0x0002, 0x3a23: 0x0002,
0x3a24: 0x0004, 0x3a25: 0x0004, 0x3a26: 0x0004, 0x3a27: 0x0004, 0x3a28: 0x0004, 0x3a29: 0x0004, 0x3a24: 0x0002, 0x3a25: 0x0002, 0x3a26: 0x0002, 0x3a27: 0x0002, 0x3a28: 0x0002, 0x3a29: 0x0002,
0x3a2a: 0x0004, 0x3a2b: 0x0004, 0x3a2c: 0x0004, 0x3a2d: 0x0004, 0x3a2e: 0x0004, 0x3a2f: 0x0004, 0x3a2a: 0x0002, 0x3a2b: 0x0002, 0x3a2c: 0x0002, 0x3a2d: 0x0002, 0x3a2e: 0x0002, 0x3a2f: 0x0002,
0x3a30: 0x0004, 0x3a31: 0x0004, 0x3a32: 0x0004, 0x3a33: 0x0004, 0x3a34: 0x0004, 0x3a35: 0x0004, 0x3a30: 0x0002, 0x3a31: 0x0002, 0x3a32: 0x0002, 0x3a33: 0x0002, 0x3a34: 0x0002, 0x3a35: 0x0002,
0x3a36: 0x0004, 0x3a37: 0x0004, 0x3a38: 0x0004, 0x3a39: 0x0004, 0x3a3a: 0x0004, 0x3a3b: 0x0004, 0x3a36: 0x0002, 0x3a37: 0x0002, 0x3a38: 0x0002, 0x3a39: 0x0002, 0x3a3a: 0x0002, 0x3a3b: 0x0002,
0x3a3c: 0x0004, 0x3a3f: 0x0004, 0x3a3c: 0x0002, 0x3a3d: 0x0002,
// Block 0xe9, offset 0x3a40 // Block 0xe9, offset 0x3a40
0x3a40: 0x0004, 0x3a41: 0x0004, 0x3a42: 0x0004, 0x3a43: 0x0004, 0x3a44: 0x0004, 0x3a45: 0x0004, 0x3a4b: 0x0002,
0x3a46: 0x0004, 0x3a47: 0x0004, 0x3a48: 0x0004, 0x3a49: 0x0004, 0x3a4a: 0x0004, 0x3a4b: 0x0004, 0x3a4c: 0x0002, 0x3a4d: 0x0002, 0x3a4e: 0x0002, 0x3a50: 0x0002, 0x3a51: 0x0002,
0x3a4c: 0x0004, 0x3a4d: 0x0004, 0x3a4e: 0x0004, 0x3a4f: 0x0004, 0x3a50: 0x0004, 0x3a51: 0x0004, 0x3a52: 0x0002, 0x3a53: 0x0002, 0x3a54: 0x0002, 0x3a55: 0x0002, 0x3a56: 0x0002, 0x3a57: 0x0002,
0x3a52: 0x0004, 0x3a53: 0x0004, 0x3a54: 0x0004, 0x3a55: 0x0004, 0x3a56: 0x0004, 0x3a57: 0x0004, 0x3a58: 0x0002, 0x3a59: 0x0002, 0x3a5a: 0x0002, 0x3a5b: 0x0002, 0x3a5c: 0x0002, 0x3a5d: 0x0002,
0x3a58: 0x0004, 0x3a59: 0x0004, 0x3a5a: 0x0004, 0x3a5b: 0x0004, 0x3a5c: 0x0004, 0x3a5d: 0x0004, 0x3a5e: 0x0002, 0x3a5f: 0x0002, 0x3a60: 0x0002, 0x3a61: 0x0002, 0x3a62: 0x0002, 0x3a63: 0x0002,
0x3a5e: 0x0004, 0x3a5f: 0x0004, 0x3a60: 0x0004, 0x3a61: 0x0004, 0x3a62: 0x0004, 0x3a63: 0x0004, 0x3a64: 0x0002, 0x3a65: 0x0002, 0x3a66: 0x0002, 0x3a67: 0x0002,
0x3a64: 0x0004, 0x3a65: 0x0004, 0x3a66: 0x0004, 0x3a67: 0x0004, 0x3a68: 0x0004, 0x3a69: 0x0004, 0x3a7a: 0x0002,
0x3a6a: 0x0004, 0x3a6b: 0x0004, 0x3a6c: 0x0004, 0x3a6d: 0x0004, 0x3a6e: 0x0004, 0x3a6f: 0x0004,
0x3a70: 0x0004, 0x3a71: 0x0004, 0x3a72: 0x0004, 0x3a73: 0x0004, 0x3a74: 0x0004, 0x3a75: 0x0004,
0x3a76: 0x0004, 0x3a77: 0x0004, 0x3a78: 0x0004, 0x3a79: 0x0004, 0x3a7a: 0x0004, 0x3a7b: 0x0004,
0x3a7c: 0x0004, 0x3a7d: 0x0004,
// Block 0xea, offset 0x3a80 // Block 0xea, offset 0x3a80
0x3a8b: 0x0004, 0x3a95: 0x0002, 0x3a96: 0x0002,
0x3a8c: 0x0004, 0x3a8d: 0x0004, 0x3a8e: 0x0004, 0x3a90: 0x0004, 0x3a91: 0x0004, 0x3aa4: 0x0002,
0x3a92: 0x0004, 0x3a93: 0x0004, 0x3a94: 0x0004, 0x3a95: 0x0004, 0x3a96: 0x0004, 0x3a97: 0x0004,
0x3a98: 0x0004, 0x3a99: 0x0004, 0x3a9a: 0x0004, 0x3a9b: 0x0004, 0x3a9c: 0x0004, 0x3a9d: 0x0004,
0x3a9e: 0x0004, 0x3a9f: 0x0004, 0x3aa0: 0x0004, 0x3aa1: 0x0004, 0x3aa2: 0x0004, 0x3aa3: 0x0004,
0x3aa4: 0x0004, 0x3aa5: 0x0004, 0x3aa6: 0x0004, 0x3aa7: 0x0004,
0x3aba: 0x0004,
// Block 0xeb, offset 0x3ac0 // Block 0xeb, offset 0x3ac0
0x3ad5: 0x0004, 0x3ad6: 0x0004, 0x3afb: 0x0002,
0x3ae4: 0x0004, 0x3afc: 0x0002, 0x3afd: 0x0002, 0x3afe: 0x0002, 0x3aff: 0x0002,
// Block 0xec, offset 0x3b00 // Block 0xec, offset 0x3b00
0x3b3b: 0x0004, 0x3b00: 0x0002, 0x3b01: 0x0002, 0x3b02: 0x0002, 0x3b03: 0x0002, 0x3b04: 0x0002, 0x3b05: 0x0002,
0x3b3c: 0x0004, 0x3b3d: 0x0004, 0x3b3e: 0x0004, 0x3b3f: 0x0004, 0x3b06: 0x0002, 0x3b07: 0x0002, 0x3b08: 0x0002, 0x3b09: 0x0002, 0x3b0a: 0x0002, 0x3b0b: 0x0002,
0x3b0c: 0x0002, 0x3b0d: 0x0002, 0x3b0e: 0x0002, 0x3b0f: 0x0002,
// Block 0xed, offset 0x3b40 // Block 0xed, offset 0x3b40
0x3b40: 0x0004, 0x3b41: 0x0004, 0x3b42: 0x0004, 0x3b43: 0x0004, 0x3b44: 0x0004, 0x3b45: 0x0004, 0x3b40: 0x0002, 0x3b41: 0x0002, 0x3b42: 0x0002, 0x3b43: 0x0002, 0x3b44: 0x0002, 0x3b45: 0x0002,
0x3b46: 0x0004, 0x3b47: 0x0004, 0x3b48: 0x0004, 0x3b49: 0x0004, 0x3b4a: 0x0004, 0x3b4b: 0x0004, 0x3b4c: 0x0002, 0x3b50: 0x0002, 0x3b51: 0x0002,
0x3b4c: 0x0004, 0x3b4d: 0x0004, 0x3b4e: 0x0004, 0x3b4f: 0x0004, 0x3b52: 0x0002, 0x3b55: 0x0002, 0x3b56: 0x0002, 0x3b57: 0x0002,
0x3b5c: 0x0002, 0x3b5d: 0x0002,
0x3b5e: 0x0002, 0x3b5f: 0x0002,
0x3b6b: 0x0002, 0x3b6c: 0x0002,
0x3b74: 0x0002, 0x3b75: 0x0002,
0x3b76: 0x0002, 0x3b77: 0x0002, 0x3b78: 0x0002, 0x3b79: 0x0002, 0x3b7a: 0x0002, 0x3b7b: 0x0002,
0x3b7c: 0x0002,
// Block 0xee, offset 0x3b80 // Block 0xee, offset 0x3b80
0x3b80: 0x0004, 0x3b81: 0x0004, 0x3b82: 0x0004, 0x3b83: 0x0004, 0x3b84: 0x0004, 0x3b85: 0x0004, 0x3ba0: 0x0002, 0x3ba1: 0x0002, 0x3ba2: 0x0002, 0x3ba3: 0x0002,
0x3b8c: 0x0004, 0x3b90: 0x0004, 0x3b91: 0x0004, 0x3ba4: 0x0002, 0x3ba5: 0x0002, 0x3ba6: 0x0002, 0x3ba7: 0x0002, 0x3ba8: 0x0002, 0x3ba9: 0x0002,
0x3b92: 0x0004, 0x3b95: 0x0004, 0x3b96: 0x0004, 0x3b97: 0x0004, 0x3baa: 0x0002, 0x3bab: 0x0002,
0x3b9c: 0x0004, 0x3b9d: 0x0004, 0x3bb0: 0x0002,
0x3b9e: 0x0004, 0x3b9f: 0x0004,
0x3bab: 0x0004, 0x3bac: 0x0004,
0x3bb4: 0x0004, 0x3bb5: 0x0004,
0x3bb6: 0x0004, 0x3bb7: 0x0004, 0x3bb8: 0x0004, 0x3bb9: 0x0004, 0x3bba: 0x0004, 0x3bbb: 0x0004,
0x3bbc: 0x0004,
// Block 0xef, offset 0x3bc0 // Block 0xef, offset 0x3bc0
0x3be0: 0x0004, 0x3be1: 0x0004, 0x3be2: 0x0004, 0x3be3: 0x0004, 0x3bcc: 0x0002, 0x3bcd: 0x0002, 0x3bce: 0x0002, 0x3bcf: 0x0002, 0x3bd0: 0x0002, 0x3bd1: 0x0002,
0x3be4: 0x0004, 0x3be5: 0x0004, 0x3be6: 0x0004, 0x3be7: 0x0004, 0x3be8: 0x0004, 0x3be9: 0x0004, 0x3bd2: 0x0002, 0x3bd3: 0x0002, 0x3bd4: 0x0002, 0x3bd5: 0x0002, 0x3bd6: 0x0002, 0x3bd7: 0x0002,
0x3bea: 0x0004, 0x3beb: 0x0004, 0x3bd8: 0x0002, 0x3bd9: 0x0002, 0x3bda: 0x0002, 0x3bdb: 0x0002, 0x3bdc: 0x0002, 0x3bdd: 0x0002,
0x3bf0: 0x0004, 0x3bde: 0x0002, 0x3bdf: 0x0002, 0x3be0: 0x0002, 0x3be1: 0x0002, 0x3be2: 0x0002, 0x3be3: 0x0002,
0x3be4: 0x0002, 0x3be5: 0x0002, 0x3be6: 0x0002, 0x3be7: 0x0002, 0x3be8: 0x0002, 0x3be9: 0x0002,
0x3bea: 0x0002, 0x3beb: 0x0002, 0x3bec: 0x0002, 0x3bed: 0x0002, 0x3bee: 0x0002, 0x3bef: 0x0002,
0x3bf0: 0x0002, 0x3bf1: 0x0002, 0x3bf2: 0x0002, 0x3bf3: 0x0002, 0x3bf4: 0x0002, 0x3bf5: 0x0002,
0x3bf6: 0x0002, 0x3bf7: 0x0002, 0x3bf8: 0x0002, 0x3bf9: 0x0002, 0x3bfa: 0x0002,
0x3bfc: 0x0002, 0x3bfd: 0x0002, 0x3bfe: 0x0002, 0x3bff: 0x0002,
// Block 0xf0, offset 0x3c00 // Block 0xf0, offset 0x3c00
0x3c0c: 0x0004, 0x3c0d: 0x0004, 0x3c0e: 0x0004, 0x3c0f: 0x0004, 0x3c10: 0x0004, 0x3c11: 0x0004, 0x3c00: 0x0002, 0x3c01: 0x0002, 0x3c02: 0x0002, 0x3c03: 0x0002, 0x3c04: 0x0002, 0x3c05: 0x0002,
0x3c12: 0x0004, 0x3c13: 0x0004, 0x3c14: 0x0004, 0x3c15: 0x0004, 0x3c16: 0x0004, 0x3c17: 0x0004, 0x3c07: 0x0002, 0x3c08: 0x0002, 0x3c09: 0x0002, 0x3c0a: 0x0002, 0x3c0b: 0x0002,
0x3c18: 0x0004, 0x3c19: 0x0004, 0x3c1a: 0x0004, 0x3c1b: 0x0004, 0x3c1c: 0x0004, 0x3c1d: 0x0004, 0x3c0c: 0x0002, 0x3c0d: 0x0002, 0x3c0e: 0x0002, 0x3c0f: 0x0002, 0x3c10: 0x0002, 0x3c11: 0x0002,
0x3c1e: 0x0004, 0x3c1f: 0x0004, 0x3c20: 0x0004, 0x3c21: 0x0004, 0x3c22: 0x0004, 0x3c23: 0x0004, 0x3c12: 0x0002, 0x3c13: 0x0002, 0x3c14: 0x0002, 0x3c15: 0x0002, 0x3c16: 0x0002, 0x3c17: 0x0002,
0x3c24: 0x0004, 0x3c25: 0x0004, 0x3c26: 0x0004, 0x3c27: 0x0004, 0x3c28: 0x0004, 0x3c29: 0x0004, 0x3c18: 0x0002, 0x3c19: 0x0002, 0x3c1a: 0x0002, 0x3c1b: 0x0002, 0x3c1c: 0x0002, 0x3c1d: 0x0002,
0x3c2a: 0x0004, 0x3c2b: 0x0004, 0x3c2c: 0x0004, 0x3c2d: 0x0004, 0x3c2e: 0x0004, 0x3c2f: 0x0004, 0x3c1e: 0x0002, 0x3c1f: 0x0002, 0x3c20: 0x0002, 0x3c21: 0x0002, 0x3c22: 0x0002, 0x3c23: 0x0002,
0x3c30: 0x0004, 0x3c31: 0x0004, 0x3c32: 0x0004, 0x3c33: 0x0004, 0x3c34: 0x0004, 0x3c35: 0x0004, 0x3c24: 0x0002, 0x3c25: 0x0002, 0x3c26: 0x0002, 0x3c27: 0x0002, 0x3c28: 0x0002, 0x3c29: 0x0002,
0x3c36: 0x0004, 0x3c37: 0x0004, 0x3c38: 0x0004, 0x3c39: 0x0004, 0x3c3a: 0x0004, 0x3c2a: 0x0002, 0x3c2b: 0x0002, 0x3c2c: 0x0002, 0x3c2d: 0x0002, 0x3c2e: 0x0002, 0x3c2f: 0x0002,
0x3c3c: 0x0004, 0x3c3d: 0x0004, 0x3c3e: 0x0004, 0x3c3f: 0x0004, 0x3c30: 0x0002, 0x3c31: 0x0002, 0x3c32: 0x0002, 0x3c33: 0x0002, 0x3c34: 0x0002, 0x3c35: 0x0002,
0x3c36: 0x0002, 0x3c37: 0x0002, 0x3c38: 0x0002, 0x3c39: 0x0002, 0x3c3a: 0x0002, 0x3c3b: 0x0002,
0x3c3c: 0x0002, 0x3c3d: 0x0002, 0x3c3e: 0x0002, 0x3c3f: 0x0002,
// Block 0xf1, offset 0x3c40 // Block 0xf1, offset 0x3c40
0x3c40: 0x0004, 0x3c41: 0x0004, 0x3c42: 0x0004, 0x3c43: 0x0004, 0x3c44: 0x0004, 0x3c45: 0x0004, 0x3c70: 0x0002, 0x3c71: 0x0002, 0x3c72: 0x0002, 0x3c73: 0x0002, 0x3c74: 0x0002, 0x3c75: 0x0002,
0x3c47: 0x0004, 0x3c48: 0x0004, 0x3c49: 0x0004, 0x3c4a: 0x0004, 0x3c4b: 0x0004, 0x3c76: 0x0002, 0x3c77: 0x0002, 0x3c78: 0x0002, 0x3c79: 0x0002, 0x3c7a: 0x0002, 0x3c7b: 0x0002,
0x3c4c: 0x0004, 0x3c4d: 0x0004, 0x3c4e: 0x0004, 0x3c4f: 0x0004, 0x3c50: 0x0004, 0x3c51: 0x0004, 0x3c7c: 0x0002,
0x3c52: 0x0004, 0x3c53: 0x0004, 0x3c54: 0x0004, 0x3c55: 0x0004, 0x3c56: 0x0004, 0x3c57: 0x0004,
0x3c58: 0x0004, 0x3c59: 0x0004, 0x3c5a: 0x0004, 0x3c5b: 0x0004, 0x3c5c: 0x0004, 0x3c5d: 0x0004,
0x3c5e: 0x0004, 0x3c5f: 0x0004, 0x3c60: 0x0004, 0x3c61: 0x0004, 0x3c62: 0x0004, 0x3c63: 0x0004,
0x3c64: 0x0004, 0x3c65: 0x0004, 0x3c66: 0x0004, 0x3c67: 0x0004, 0x3c68: 0x0004, 0x3c69: 0x0004,
0x3c6a: 0x0004, 0x3c6b: 0x0004, 0x3c6c: 0x0004, 0x3c6d: 0x0004, 0x3c6e: 0x0004, 0x3c6f: 0x0004,
0x3c70: 0x0004, 0x3c71: 0x0004, 0x3c72: 0x0004, 0x3c73: 0x0004, 0x3c74: 0x0004, 0x3c75: 0x0004,
0x3c76: 0x0004, 0x3c77: 0x0004, 0x3c78: 0x0004, 0x3c79: 0x0004, 0x3c7a: 0x0004, 0x3c7b: 0x0004,
0x3c7c: 0x0004, 0x3c7d: 0x0004, 0x3c7e: 0x0004, 0x3c7f: 0x0004,
// Block 0xf2, offset 0x3c80 // Block 0xf2, offset 0x3c80
0x3cb0: 0x0004, 0x3cb1: 0x0004, 0x3cb2: 0x0004, 0x3cb3: 0x0004, 0x3cb4: 0x0004, 0x3cb5: 0x0004, 0x3c80: 0x0002, 0x3c81: 0x0002, 0x3c82: 0x0002, 0x3c83: 0x0002, 0x3c84: 0x0002, 0x3c85: 0x0002,
0x3cb6: 0x0004, 0x3cb7: 0x0004, 0x3cb8: 0x0004, 0x3cb9: 0x0004, 0x3cba: 0x0004, 0x3cbb: 0x0004, 0x3c86: 0x0002, 0x3c87: 0x0002, 0x3c88: 0x0002, 0x3c89: 0x0002,
0x3cbc: 0x0004, 0x3c8f: 0x0002, 0x3c90: 0x0002, 0x3c91: 0x0002,
0x3c92: 0x0002, 0x3c93: 0x0002, 0x3c94: 0x0002, 0x3c95: 0x0002, 0x3c96: 0x0002, 0x3c97: 0x0002,
0x3c98: 0x0002, 0x3c99: 0x0002, 0x3c9a: 0x0002, 0x3c9b: 0x0002, 0x3c9c: 0x0002, 0x3c9d: 0x0002,
0x3c9e: 0x0002, 0x3c9f: 0x0002, 0x3ca0: 0x0002, 0x3ca1: 0x0002, 0x3ca2: 0x0002, 0x3ca3: 0x0002,
0x3ca4: 0x0002, 0x3ca5: 0x0002, 0x3ca6: 0x0002, 0x3ca7: 0x0002, 0x3ca8: 0x0002, 0x3ca9: 0x0002,
0x3caa: 0x0002, 0x3cab: 0x0002, 0x3cac: 0x0002, 0x3cad: 0x0002, 0x3cae: 0x0002, 0x3caf: 0x0002,
0x3cb0: 0x0002, 0x3cb1: 0x0002, 0x3cb2: 0x0002, 0x3cb3: 0x0002, 0x3cb4: 0x0002, 0x3cb5: 0x0002,
0x3cb6: 0x0002, 0x3cb7: 0x0002, 0x3cb8: 0x0002, 0x3cb9: 0x0002, 0x3cba: 0x0002, 0x3cbb: 0x0002,
0x3cbc: 0x0002, 0x3cbd: 0x0002, 0x3cbe: 0x0002, 0x3cbf: 0x0002,
// Block 0xf3, offset 0x3cc0 // Block 0xf3, offset 0x3cc0
0x3cc0: 0x0004, 0x3cc1: 0x0004, 0x3cc2: 0x0004, 0x3cc3: 0x0004, 0x3cc4: 0x0004, 0x3cc5: 0x0004, 0x3cc0: 0x0002, 0x3cc1: 0x0002, 0x3cc2: 0x0002, 0x3cc3: 0x0002, 0x3cc4: 0x0002, 0x3cc5: 0x0002,
0x3cc6: 0x0004, 0x3cc7: 0x0004, 0x3cc8: 0x0004, 0x3cc9: 0x0004, 0x3cc6: 0x0002,
0x3ccf: 0x0004, 0x3cd0: 0x0004, 0x3cd1: 0x0004, 0x3cce: 0x0002, 0x3ccf: 0x0002, 0x3cd0: 0x0002, 0x3cd1: 0x0002,
0x3cd2: 0x0004, 0x3cd3: 0x0004, 0x3cd4: 0x0004, 0x3cd5: 0x0004, 0x3cd6: 0x0004, 0x3cd7: 0x0004, 0x3cd2: 0x0002, 0x3cd3: 0x0002, 0x3cd4: 0x0002, 0x3cd5: 0x0002, 0x3cd6: 0x0002, 0x3cd7: 0x0002,
0x3cd8: 0x0004, 0x3cd9: 0x0004, 0x3cda: 0x0004, 0x3cdb: 0x0004, 0x3cdc: 0x0004, 0x3cdd: 0x0004, 0x3cd8: 0x0002, 0x3cd9: 0x0002, 0x3cda: 0x0002, 0x3cdb: 0x0002, 0x3cdc: 0x0002,
0x3cde: 0x0004, 0x3cdf: 0x0004, 0x3ce0: 0x0004, 0x3ce1: 0x0004, 0x3ce2: 0x0004, 0x3ce3: 0x0004, 0x3cdf: 0x0002, 0x3ce0: 0x0002, 0x3ce1: 0x0002, 0x3ce2: 0x0002, 0x3ce3: 0x0002,
0x3ce4: 0x0004, 0x3ce5: 0x0004, 0x3ce6: 0x0004, 0x3ce7: 0x0004, 0x3ce8: 0x0004, 0x3ce9: 0x0004, 0x3ce4: 0x0002, 0x3ce5: 0x0002, 0x3ce6: 0x0002, 0x3ce7: 0x0002, 0x3ce8: 0x0002, 0x3ce9: 0x0002,
0x3cea: 0x0004, 0x3ceb: 0x0004, 0x3cec: 0x0004, 0x3ced: 0x0004, 0x3cee: 0x0004, 0x3cef: 0x0004, 0x3cf0: 0x0002, 0x3cf1: 0x0002, 0x3cf2: 0x0002, 0x3cf3: 0x0002, 0x3cf4: 0x0002, 0x3cf5: 0x0002,
0x3cf0: 0x0004, 0x3cf1: 0x0004, 0x3cf2: 0x0004, 0x3cf3: 0x0004, 0x3cf4: 0x0004, 0x3cf5: 0x0004, 0x3cf6: 0x0002, 0x3cf7: 0x0002, 0x3cf8: 0x0002,
0x3cf6: 0x0004, 0x3cf7: 0x0004, 0x3cf8: 0x0004, 0x3cf9: 0x0004, 0x3cfa: 0x0004, 0x3cfb: 0x0004,
0x3cfc: 0x0004, 0x3cfd: 0x0004, 0x3cfe: 0x0004, 0x3cff: 0x0004,
// Block 0xf4, offset 0x3d00 // Block 0xf4, offset 0x3d00
0x3d00: 0x0004, 0x3d01: 0x0004, 0x3d02: 0x0004, 0x3d03: 0x0004, 0x3d04: 0x0004, 0x3d05: 0x0004, 0x3d01: 0x0001,
0x3d06: 0x0004, 0x3d20: 0x0001, 0x3d21: 0x0001, 0x3d22: 0x0001, 0x3d23: 0x0001,
0x3d0e: 0x0004, 0x3d0f: 0x0004, 0x3d10: 0x0004, 0x3d11: 0x0004, 0x3d24: 0x0001, 0x3d25: 0x0001, 0x3d26: 0x0001, 0x3d27: 0x0001, 0x3d28: 0x0001, 0x3d29: 0x0001,
0x3d12: 0x0004, 0x3d13: 0x0004, 0x3d14: 0x0004, 0x3d15: 0x0004, 0x3d16: 0x0004, 0x3d17: 0x0004, 0x3d2a: 0x0001, 0x3d2b: 0x0001, 0x3d2c: 0x0001, 0x3d2d: 0x0001, 0x3d2e: 0x0001, 0x3d2f: 0x0001,
0x3d18: 0x0004, 0x3d19: 0x0004, 0x3d1a: 0x0004, 0x3d1b: 0x0004, 0x3d1c: 0x0004, 0x3d30: 0x0001, 0x3d31: 0x0001, 0x3d32: 0x0001, 0x3d33: 0x0001, 0x3d34: 0x0001, 0x3d35: 0x0001,
0x3d1f: 0x0004, 0x3d20: 0x0004, 0x3d21: 0x0004, 0x3d22: 0x0004, 0x3d23: 0x0004, 0x3d36: 0x0001, 0x3d37: 0x0001, 0x3d38: 0x0001, 0x3d39: 0x0001, 0x3d3a: 0x0001, 0x3d3b: 0x0001,
0x3d24: 0x0004, 0x3d25: 0x0004, 0x3d26: 0x0004, 0x3d27: 0x0004, 0x3d28: 0x0004, 0x3d29: 0x0004, 0x3d3c: 0x0001, 0x3d3d: 0x0001, 0x3d3e: 0x0001, 0x3d3f: 0x0001,
0x3d30: 0x0004, 0x3d31: 0x0004, 0x3d32: 0x0004, 0x3d33: 0x0004, 0x3d34: 0x0004, 0x3d35: 0x0004,
0x3d36: 0x0004, 0x3d37: 0x0004, 0x3d38: 0x0004,
// Block 0xf5, offset 0x3d40 // Block 0xf5, offset 0x3d40
0x3d40: 0x0002, 0x3d41: 0x0002, 0x3d42: 0x0002, 0x3d43: 0x0002, 0x3d44: 0x0002, 0x3d45: 0x0002, 0x3d40: 0x0003, 0x3d41: 0x0003, 0x3d42: 0x0003, 0x3d43: 0x0003, 0x3d44: 0x0003, 0x3d45: 0x0003,
0x3d46: 0x0002, 0x3d47: 0x0002, 0x3d48: 0x0002, 0x3d49: 0x0002, 0x3d4a: 0x0002, 0x3d4b: 0x0002, 0x3d46: 0x0003, 0x3d47: 0x0003, 0x3d48: 0x0003, 0x3d49: 0x0003, 0x3d4a: 0x0003, 0x3d4b: 0x0003,
0x3d4c: 0x0002, 0x3d4d: 0x0002, 0x3d4e: 0x0002, 0x3d4f: 0x0002, 0x3d50: 0x0002, 0x3d51: 0x0002, 0x3d4c: 0x0003, 0x3d4d: 0x0003, 0x3d4e: 0x0003, 0x3d4f: 0x0003, 0x3d50: 0x0003, 0x3d51: 0x0003,
0x3d52: 0x0002, 0x3d53: 0x0002, 0x3d54: 0x0002, 0x3d55: 0x0002, 0x3d56: 0x0002, 0x3d57: 0x0002, 0x3d52: 0x0003, 0x3d53: 0x0003, 0x3d54: 0x0003, 0x3d55: 0x0003, 0x3d56: 0x0003, 0x3d57: 0x0003,
0x3d58: 0x0002, 0x3d59: 0x0002, 0x3d5a: 0x0002, 0x3d5b: 0x0002, 0x3d5c: 0x0002, 0x3d5d: 0x0002, 0x3d58: 0x0003, 0x3d59: 0x0003, 0x3d5a: 0x0003, 0x3d5b: 0x0003, 0x3d5c: 0x0003, 0x3d5d: 0x0003,
0x3d5e: 0x0002, 0x3d5f: 0x0002, 0x3d60: 0x0002, 0x3d61: 0x0002, 0x3d62: 0x0002, 0x3d63: 0x0002, 0x3d5e: 0x0003, 0x3d5f: 0x0003, 0x3d60: 0x0003, 0x3d61: 0x0003, 0x3d62: 0x0003, 0x3d63: 0x0003,
0x3d64: 0x0002, 0x3d65: 0x0002, 0x3d66: 0x0002, 0x3d67: 0x0002, 0x3d68: 0x0002, 0x3d69: 0x0002, 0x3d64: 0x0003, 0x3d65: 0x0003, 0x3d66: 0x0003, 0x3d67: 0x0003, 0x3d68: 0x0003, 0x3d69: 0x0003,
0x3d6a: 0x0002, 0x3d6b: 0x0002, 0x3d6c: 0x0002, 0x3d6d: 0x0002, 0x3d6e: 0x0002, 0x3d6f: 0x0002, 0x3d6a: 0x0003, 0x3d6b: 0x0003, 0x3d6c: 0x0003, 0x3d6d: 0x0003, 0x3d6e: 0x0003, 0x3d6f: 0x0003,
0x3d70: 0x0002, 0x3d71: 0x0002, 0x3d72: 0x0002, 0x3d73: 0x0002, 0x3d74: 0x0002, 0x3d75: 0x0002, 0x3d70: 0x0003, 0x3d71: 0x0003, 0x3d72: 0x0003, 0x3d73: 0x0003, 0x3d74: 0x0003, 0x3d75: 0x0003,
0x3d76: 0x0002, 0x3d77: 0x0002, 0x3d78: 0x0002, 0x3d79: 0x0002, 0x3d7a: 0x0002, 0x3d7b: 0x0002, 0x3d76: 0x0003, 0x3d77: 0x0003, 0x3d78: 0x0003, 0x3d79: 0x0003, 0x3d7a: 0x0003, 0x3d7b: 0x0003,
0x3d7c: 0x0002, 0x3d7d: 0x0002, 0x3d7c: 0x0003, 0x3d7d: 0x0003,
// Block 0xf6, offset 0x3d80
0x3d81: 0x0001,
0x3da0: 0x0001, 0x3da1: 0x0001, 0x3da2: 0x0001, 0x3da3: 0x0001,
0x3da4: 0x0001, 0x3da5: 0x0001, 0x3da6: 0x0001, 0x3da7: 0x0001, 0x3da8: 0x0001, 0x3da9: 0x0001,
0x3daa: 0x0001, 0x3dab: 0x0001, 0x3dac: 0x0001, 0x3dad: 0x0001, 0x3dae: 0x0001, 0x3daf: 0x0001,
0x3db0: 0x0001, 0x3db1: 0x0001, 0x3db2: 0x0001, 0x3db3: 0x0001, 0x3db4: 0x0001, 0x3db5: 0x0001,
0x3db6: 0x0001, 0x3db7: 0x0001, 0x3db8: 0x0001, 0x3db9: 0x0001, 0x3dba: 0x0001, 0x3dbb: 0x0001,
0x3dbc: 0x0001, 0x3dbd: 0x0001, 0x3dbe: 0x0001, 0x3dbf: 0x0001,
// Block 0xf7, offset 0x3dc0
0x3dc0: 0x0003, 0x3dc1: 0x0003, 0x3dc2: 0x0003, 0x3dc3: 0x0003, 0x3dc4: 0x0003, 0x3dc5: 0x0003,
0x3dc6: 0x0003, 0x3dc7: 0x0003, 0x3dc8: 0x0003, 0x3dc9: 0x0003, 0x3dca: 0x0003, 0x3dcb: 0x0003,
0x3dcc: 0x0003, 0x3dcd: 0x0003, 0x3dce: 0x0003, 0x3dcf: 0x0003, 0x3dd0: 0x0003, 0x3dd1: 0x0003,
0x3dd2: 0x0003, 0x3dd3: 0x0003, 0x3dd4: 0x0003, 0x3dd5: 0x0003, 0x3dd6: 0x0003, 0x3dd7: 0x0003,
0x3dd8: 0x0003, 0x3dd9: 0x0003, 0x3dda: 0x0003, 0x3ddb: 0x0003, 0x3ddc: 0x0003, 0x3ddd: 0x0003,
0x3dde: 0x0003, 0x3ddf: 0x0003, 0x3de0: 0x0003, 0x3de1: 0x0003, 0x3de2: 0x0003, 0x3de3: 0x0003,
0x3de4: 0x0003, 0x3de5: 0x0003, 0x3de6: 0x0003, 0x3de7: 0x0003, 0x3de8: 0x0003, 0x3de9: 0x0003,
0x3dea: 0x0003, 0x3deb: 0x0003, 0x3dec: 0x0003, 0x3ded: 0x0003, 0x3dee: 0x0003, 0x3def: 0x0003,
0x3df0: 0x0003, 0x3df1: 0x0003, 0x3df2: 0x0003, 0x3df3: 0x0003, 0x3df4: 0x0003, 0x3df5: 0x0003,
0x3df6: 0x0003, 0x3df7: 0x0003, 0x3df8: 0x0003, 0x3df9: 0x0003, 0x3dfa: 0x0003, 0x3dfb: 0x0003,
0x3dfc: 0x0003, 0x3dfd: 0x0003,
} }
// stringWidthIndex: 30 blocks, 1920 entries, 1920 bytes // stringWidthIndex: 30 blocks, 1920 entries, 1920 bytes
@ -1683,10 +1655,10 @@ var stringWidthIndex = [1920]uint8{
// Block 0x17, offset 0x5c0 // Block 0x17, offset 0x5c0
0x5c0: 0xd7, 0x5c3: 0xd8, 0x5c4: 0xd9, 0x5c5: 0xda, 0x5c6: 0xdb, 0x5c7: 0xdc, 0x5c0: 0xd7, 0x5c3: 0xd8, 0x5c4: 0xd9, 0x5c5: 0xda, 0x5c6: 0xdb, 0x5c7: 0xdc,
0x5c8: 0xdd, 0x5c9: 0xde, 0x5cc: 0xdf, 0x5cd: 0xe0, 0x5ce: 0xe1, 0x5cf: 0xe2, 0x5c8: 0xdd, 0x5c9: 0xde, 0x5cc: 0xdf, 0x5cd: 0xe0, 0x5ce: 0xe1, 0x5cf: 0xe2,
0x5d0: 0xe3, 0x5d1: 0xe4, 0x5d2: 0xe5, 0x5d3: 0xe6, 0x5d4: 0xe7, 0x5d5: 0xe8, 0x5d6: 0xe9, 0x5d7: 0xea, 0x5d0: 0xe3, 0x5d1: 0xe4, 0x5d2: 0x39, 0x5d3: 0xe5, 0x5d4: 0xe6, 0x5d5: 0xe7, 0x5d6: 0xe8, 0x5d7: 0xe9,
0x5d8: 0xe5, 0x5d9: 0xeb, 0x5da: 0xe5, 0x5db: 0xec, 0x5df: 0xed, 0x5d8: 0x39, 0x5d9: 0xea, 0x5da: 0x39, 0x5db: 0xeb, 0x5df: 0xec,
0x5e4: 0xee, 0x5e5: 0xef, 0x5e6: 0xe5, 0x5e7: 0xe5, 0x5e4: 0xed, 0x5e5: 0xee, 0x5e6: 0x39, 0x5e7: 0x39,
0x5e9: 0xf0, 0x5ea: 0xf1, 0x5eb: 0xf2, 0x5e9: 0xef, 0x5ea: 0xf0, 0x5eb: 0xf1,
// Block 0x18, offset 0x600 // Block 0x18, offset 0x600
0x600: 0x39, 0x601: 0x39, 0x602: 0x39, 0x603: 0x39, 0x604: 0x39, 0x605: 0x39, 0x606: 0x39, 0x607: 0x39, 0x600: 0x39, 0x601: 0x39, 0x602: 0x39, 0x603: 0x39, 0x604: 0x39, 0x605: 0x39, 0x606: 0x39, 0x607: 0x39,
0x608: 0x39, 0x609: 0x39, 0x60a: 0x39, 0x60b: 0x39, 0x60c: 0x39, 0x60d: 0x39, 0x60e: 0x39, 0x60f: 0x39, 0x608: 0x39, 0x609: 0x39, 0x60a: 0x39, 0x60b: 0x39, 0x60c: 0x39, 0x60d: 0x39, 0x60e: 0x39, 0x60f: 0x39,
@ -1695,7 +1667,7 @@ var stringWidthIndex = [1920]uint8{
0x620: 0x39, 0x621: 0x39, 0x622: 0x39, 0x623: 0x39, 0x624: 0x39, 0x625: 0x39, 0x626: 0x39, 0x627: 0x39, 0x620: 0x39, 0x621: 0x39, 0x622: 0x39, 0x623: 0x39, 0x624: 0x39, 0x625: 0x39, 0x626: 0x39, 0x627: 0x39,
0x628: 0x39, 0x629: 0x39, 0x62a: 0x39, 0x62b: 0x39, 0x62c: 0x39, 0x62d: 0x39, 0x62e: 0x39, 0x62f: 0x39, 0x628: 0x39, 0x629: 0x39, 0x62a: 0x39, 0x62b: 0x39, 0x62c: 0x39, 0x62d: 0x39, 0x62e: 0x39, 0x62f: 0x39,
0x630: 0x39, 0x631: 0x39, 0x632: 0x39, 0x633: 0x39, 0x634: 0x39, 0x635: 0x39, 0x636: 0x39, 0x637: 0x39, 0x630: 0x39, 0x631: 0x39, 0x632: 0x39, 0x633: 0x39, 0x634: 0x39, 0x635: 0x39, 0x636: 0x39, 0x637: 0x39,
0x638: 0x39, 0x639: 0x39, 0x63a: 0x39, 0x63b: 0x39, 0x63c: 0x39, 0x63d: 0x39, 0x63e: 0x39, 0x63f: 0xf3, 0x638: 0x39, 0x639: 0x39, 0x63a: 0x39, 0x63b: 0x39, 0x63c: 0x39, 0x63d: 0x39, 0x63e: 0x39, 0x63f: 0xe6,
// Block 0x19, offset 0x640 // Block 0x19, offset 0x640
0x650: 0x0b, 0x651: 0x0c, 0x653: 0x0d, 0x656: 0x0e, 0x657: 0x06, 0x650: 0x0b, 0x651: 0x0c, 0x653: 0x0d, 0x656: 0x0e, 0x657: 0x06,
0x658: 0x0f, 0x65a: 0x10, 0x65b: 0x11, 0x65c: 0x12, 0x65d: 0x13, 0x65e: 0x14, 0x65f: 0x15, 0x658: 0x0f, 0x65a: 0x10, 0x65b: 0x11, 0x65c: 0x12, 0x65d: 0x13, 0x65e: 0x14, 0x65f: 0x15,
@ -1704,7 +1676,7 @@ var stringWidthIndex = [1920]uint8{
0x670: 0x06, 0x671: 0x06, 0x672: 0x06, 0x673: 0x06, 0x674: 0x06, 0x675: 0x06, 0x676: 0x06, 0x677: 0x06, 0x670: 0x06, 0x671: 0x06, 0x672: 0x06, 0x673: 0x06, 0x674: 0x06, 0x675: 0x06, 0x676: 0x06, 0x677: 0x06,
0x678: 0x06, 0x679: 0x06, 0x67a: 0x06, 0x67b: 0x06, 0x67c: 0x06, 0x67d: 0x06, 0x67e: 0x06, 0x67f: 0x16, 0x678: 0x06, 0x679: 0x06, 0x67a: 0x06, 0x67b: 0x06, 0x67c: 0x06, 0x67d: 0x06, 0x67e: 0x06, 0x67f: 0x16,
// Block 0x1a, offset 0x680 // Block 0x1a, offset 0x680
0x680: 0xf4, 0x681: 0x08, 0x684: 0x08, 0x685: 0x08, 0x686: 0x08, 0x687: 0x09, 0x680: 0xf2, 0x681: 0x08, 0x684: 0x08, 0x685: 0x08, 0x686: 0x08, 0x687: 0x09,
// Block 0x1b, offset 0x6c0 // Block 0x1b, offset 0x6c0
0x6c0: 0x5b, 0x6c1: 0x5b, 0x6c2: 0x5b, 0x6c3: 0x5b, 0x6c4: 0x5b, 0x6c5: 0x5b, 0x6c6: 0x5b, 0x6c7: 0x5b, 0x6c0: 0x5b, 0x6c1: 0x5b, 0x6c2: 0x5b, 0x6c3: 0x5b, 0x6c4: 0x5b, 0x6c5: 0x5b, 0x6c6: 0x5b, 0x6c7: 0x5b,
0x6c8: 0x5b, 0x6c9: 0x5b, 0x6ca: 0x5b, 0x6cb: 0x5b, 0x6cc: 0x5b, 0x6cd: 0x5b, 0x6ce: 0x5b, 0x6cf: 0x5b, 0x6c8: 0x5b, 0x6c9: 0x5b, 0x6ca: 0x5b, 0x6cb: 0x5b, 0x6cc: 0x5b, 0x6cd: 0x5b, 0x6ce: 0x5b, 0x6cf: 0x5b,
@ -1713,7 +1685,7 @@ var stringWidthIndex = [1920]uint8{
0x6e0: 0x5b, 0x6e1: 0x5b, 0x6e2: 0x5b, 0x6e3: 0x5b, 0x6e4: 0x5b, 0x6e5: 0x5b, 0x6e6: 0x5b, 0x6e7: 0x5b, 0x6e0: 0x5b, 0x6e1: 0x5b, 0x6e2: 0x5b, 0x6e3: 0x5b, 0x6e4: 0x5b, 0x6e5: 0x5b, 0x6e6: 0x5b, 0x6e7: 0x5b,
0x6e8: 0x5b, 0x6e9: 0x5b, 0x6ea: 0x5b, 0x6eb: 0x5b, 0x6ec: 0x5b, 0x6ed: 0x5b, 0x6ee: 0x5b, 0x6ef: 0x5b, 0x6e8: 0x5b, 0x6e9: 0x5b, 0x6ea: 0x5b, 0x6eb: 0x5b, 0x6ec: 0x5b, 0x6ed: 0x5b, 0x6ee: 0x5b, 0x6ef: 0x5b,
0x6f0: 0x5b, 0x6f1: 0x5b, 0x6f2: 0x5b, 0x6f3: 0x5b, 0x6f4: 0x5b, 0x6f5: 0x5b, 0x6f6: 0x5b, 0x6f7: 0x5b, 0x6f0: 0x5b, 0x6f1: 0x5b, 0x6f2: 0x5b, 0x6f3: 0x5b, 0x6f4: 0x5b, 0x6f5: 0x5b, 0x6f6: 0x5b, 0x6f7: 0x5b,
0x6f8: 0x5b, 0x6f9: 0x5b, 0x6fa: 0x5b, 0x6fb: 0x5b, 0x6fc: 0x5b, 0x6fd: 0x5b, 0x6fe: 0x5b, 0x6ff: 0xf5, 0x6f8: 0x5b, 0x6f9: 0x5b, 0x6fa: 0x5b, 0x6fb: 0x5b, 0x6fc: 0x5b, 0x6fd: 0x5b, 0x6fe: 0x5b, 0x6ff: 0xf3,
// Block 0x1c, offset 0x700 // Block 0x1c, offset 0x700
0x720: 0x18, 0x720: 0x18,
0x730: 0x09, 0x731: 0x09, 0x732: 0x09, 0x733: 0x09, 0x734: 0x09, 0x735: 0x09, 0x736: 0x09, 0x737: 0x09, 0x730: 0x09, 0x731: 0x09, 0x732: 0x09, 0x733: 0x09, 0x734: 0x09, 0x735: 0x09, 0x736: 0x09, 0x737: 0x09,

View file

@ -122,28 +122,18 @@ func graphemeWidth[T stringish.Interface](s T, options Options) int {
prop := property(p) prop := property(p)
// Variation Selector 16 (VS16) requests emoji presentation // Variation Selector 16 (VS16) requests emoji presentation
if sz > 0 && len(s) >= sz+3 { if prop != _Wide && sz > 0 && len(s) >= sz+3 {
vs := s[sz : sz+3] vs := s[sz : sz+3]
if isVS16(vs) { if isVS16(vs) {
prop = _Emoji prop = _Wide
} }
// VS15 (0x8E) requests text presentation but does not affect width, // VS15 (0x8E) requests text presentation but does not affect width,
// in my reading of Unicode TR51. Falls through to return the base // in my reading of Unicode TR51. Falls through to return the base
// character's property. // character's property.
} }
/*
Note: we previously had some regional indicator handling here,
intending to treat single RI's as width 1 and pairs as width 2.
We think that's what the Unicode #11 indicates?
Then we looked at what actual terminals do, and they seem to treat
single and paired RI's as width 2, regardless. See terminal-test/.
Looks like VS Code does the same FWIW.
*/
if options.EastAsianWidth && prop == _East_Asian_Ambiguous { if options.EastAsianWidth && prop == _East_Asian_Ambiguous {
prop = _East_Asian_Wide prop = _Wide
} }
if prop > upperBound { if prop > upperBound {
@ -167,13 +157,11 @@ func isVS16[T stringish.Interface](s T) bool {
} }
// propertyWidths is a jump table of sorts, instead of a switch // propertyWidths is a jump table of sorts, instead of a switch
var propertyWidths = [6]int{ var propertyWidths = [4]int{
_Default: 1, _Default: 1,
_Zero_Width: 0, _Zero_Width: 0,
_East_Asian_Wide: 2, _Wide: 2,
_East_Asian_Ambiguous: 1, _East_Asian_Ambiguous: 1,
_Emoji: 2,
_Regional_Indicator: 2,
} }
const upperBound = property(len(propertyWidths) - 1) const upperBound = property(len(propertyWidths) - 1)

View file

@ -1,5 +1,41 @@
version: "2" version: "2"
run:
timeout: 5m
linters: linters:
exclusions: exclusions:
presets: presets:
- std-error-handling - std-error-handling
enable:
- gosec # Detects security problems.
# Keep all extras disabled for now to focus on the integer overflow problem.
# TODO: enable these and other good linters
- dogsled # Detects assignments with too many blank identifiers.
- errcheck
- errchkjson # Detects unsupported types passed to json encoding functions and reports if checks for the returned error can be omitted.
- exhaustive # Detects missing options in enum switch statements.
- gocyclo
- govet
- ineffassign
- makezero # Finds slice declarations with non-zero initial length.
- misspell # Detects commonly misspelled English words in comments.
- nakedret # Detects uses of naked returns.
- prealloc # Detects slice declarations that could potentially be pre-allocated.
- predeclared # Detects code that shadows one of Go's predeclared identifiers.
- reassign # Detects reassigning a top-level variable in another package.
- staticcheck
- thelper # Detects test helpers without t.Helper().
- tparallel # Detects inappropriate usage of t.Parallel().
- unconvert # Detects unnecessary type conversions.
- unused
- usestdlibvars # Detects the possibility to use variables/constants from the Go standard library.
- usetesting # Reports uses of functions with replacement inside the testing package.
settings:
govet:
disable:
- stdversion
gosec:
excludes:
- G404 # Weak random number generator used in tests.
- G304 # File inclusion

View file

@ -70,13 +70,13 @@ If increasing the limit does not help, please
## Tests ## Tests
In addition to unit tests, In addition to unit tests,
[mimetype_tests](https://github.com/gabriel-vasile/mimetype_tests) compares the [mimetype_tests](https://github.com/gabriel-vasile/mimetype_tests) compares the
library with the [Unix file utility](https://en.wikipedia.org/wiki/File_(command)) library with [libmagic](https://en.wikipedia.org/wiki/File_(command))
for around 50 000 sample files. Check the latest comparison results for around 50 000 sample files. Check the latest comparison results
[here](https://github.com/gabriel-vasile/mimetype_tests/actions). [here](https://github.com/gabriel-vasile/mimetype_tests/actions).
## Benchmarks ## Benchmarks
Benchmarks for each file format are performed when a PR is open. The results can Benchmarks are performed when a PR is open. The results can be seen on the
be seen on the [workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml). [workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml).
Performance improvements are welcome but correctness is prioritized. Performance improvements are welcome but correctness is prioritized.
## Structure ## Structure

View file

@ -94,20 +94,6 @@ func eq(path1, path2 [][]byte) bool {
return true return true
} }
// LooksLikeObjectOrArray reports if first non white space character from raw
// is either { or [. Parsing raw as JSON is a heavy operation. When receiving some
// text input we can skip parsing if the input does not even look like JSON.
func LooksLikeObjectOrArray(raw []byte) bool {
for i := range raw {
if isSpace(raw[i]) {
continue
}
return raw[i] == '{' || raw[i] == '['
}
return false
}
// Parse will take out a parser from the pool depending on queryType and tries // Parse will take out a parser from the pool depending on queryType and tries
// to parse raw bytes as JSON. // to parse raw bytes as JSON.
func Parse(queryType string, raw []byte) (parsed, inspected, firstToken int, querySatisfied bool) { func Parse(queryType string, raw []byte) (parsed, inspected, firstToken int, querySatisfied bool) {

View file

@ -106,10 +106,10 @@ func CRX(raw []byte, limit uint32) bool {
if len(raw) < minHeaderLen || !bytes.HasPrefix(raw, []byte("Cr24")) { if len(raw) < minHeaderLen || !bytes.HasPrefix(raw, []byte("Cr24")) {
return false return false
} }
pubkeyLen := binary.LittleEndian.Uint32(raw[8:12]) pubkeyLen := int64(binary.LittleEndian.Uint32(raw[8:12]))
sigLen := binary.LittleEndian.Uint32(raw[12:16]) sigLen := int64(binary.LittleEndian.Uint32(raw[12:16]))
zipOffset := minHeaderLen + pubkeyLen + sigLen zipOffset := minHeaderLen + pubkeyLen + sigLen
if uint32(len(raw)) < zipOffset { if zipOffset < 0 || int64(len(raw)) < zipOffset {
return false return false
} }
return Zip(raw[zipOffset:], limit) return Zip(raw[zipOffset:], limit)
@ -209,3 +209,13 @@ func tarChksum(b []byte) (unsigned, signed int64) {
} }
return unsigned, signed return unsigned, signed
} }
// Zlib matches zlib compressed files.
func Zlib(raw []byte, _ uint32) bool {
// https://www.ietf.org/rfc/rfc6713.txt
// This check has one fault: ASCII code can satisfy it; for ex: []byte("x ")
zlib := len(raw) > 1 &&
raw[0] == 'x' && binary.BigEndian.Uint16(raw)%31 == 0
// Check that the file is not a regular text to avoid false positives.
return zlib && !Text(raw, 0)
}

View file

@ -0,0 +1,12 @@
package magic
import "bytes"
// GRIB matches a GRIdded Binary meteorological file.
// https://www.nco.ncep.noaa.gov/pmb/docs/on388/
// https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/
func GRIB(raw []byte, _ uint32) bool {
return len(raw) > 7 &&
bytes.HasPrefix(raw, []byte("GRIB")) &&
(raw[7] == 1 || raw[7] == 2)
}

View file

@ -192,7 +192,8 @@ func matchOleClsid(in []byte, clsid []byte) bool {
// Expected offset of CLSID for root storage object. // Expected offset of CLSID for root storage object.
clsidOffset := sectorLength*(1+firstSecID) + 80 clsidOffset := sectorLength*(1+firstSecID) + 80
if len(in) <= clsidOffset+16 { // #731 offset is outside in or wrapped around due to integer overflow.
if len(in) <= clsidOffset+16 || clsidOffset < 0 {
return false return false
} }

View file

@ -283,7 +283,7 @@ func Shell(raw []byte, _ uint32) bool {
// Text matches a plain text file. // Text matches a plain text file.
// //
// TODO: This function does not parse BOM-less UTF16 and UTF32 files. Not really // TODO: This function does not parse BOM-less UTF16 and UTF32 files. Not really
// sure it should. Linux file utility also requires a BOM for UTF16 and UTF32. // sure it should. libmagic also requires a BOM for UTF16 and UTF32.
func Text(raw []byte, _ uint32) bool { func Text(raw []byte, _ uint32) bool {
// First look for BOM. // First look for BOM.
if cset := charset.FromBOM(raw); cset != "" { if cset := charset.FromBOM(raw); cset != "" {
@ -352,13 +352,20 @@ func GLTF(raw []byte, limit uint32) bool {
return jsonHelper(raw, limit, json.QueryGLTF, json.TokObject) return jsonHelper(raw, limit, json.QueryGLTF, json.TokObject)
} }
func jsonHelper(raw []byte, limit uint32, q string, wantTok int) bool { func jsonHelper(raw scan.Bytes, limit uint32, q string, wantToks ...int) bool {
if !json.LooksLikeObjectOrArray(raw) { firstNonWS := raw.FirstNonWS()
hasTargetTok := false
for _, t := range wantToks {
hasTargetTok = hasTargetTok || (t&json.TokArray > 0 && firstNonWS == '[')
hasTargetTok = hasTargetTok || (t&json.TokObject > 0 && firstNonWS == '{')
}
if !hasTargetTok {
return false return false
} }
lraw := len(raw) lraw := len(raw)
parsed, inspected, firstToken, querySatisfied := json.Parse(q, raw) parsed, inspected, _, querySatisfied := json.Parse(q, raw)
if !querySatisfied || firstToken&wantTok == 0 { if !querySatisfied {
return false return false
} }
@ -369,7 +376,7 @@ func jsonHelper(raw []byte, limit uint32, q string, wantTok int) bool {
// If a section of the file was provided, check if all of it was inspected. // If a section of the file was provided, check if all of it was inspected.
// In other words, check that if there was a problem parsing, that problem // In other words, check that if there was a problem parsing, that problem
// occured at the last byte in the input. // occurred at the last byte in the input.
return inspected == lraw && lraw > 0 return inspected == lraw && lraw > 0
} }
@ -536,3 +543,57 @@ func Vtt(raw []byte, limit uint32) bool {
return bytes.Equal(raw, []byte{0xEF, 0xBB, 0xBF, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) || // UTF-8 BOM and "WEBVTT" return bytes.Equal(raw, []byte{0xEF, 0xBB, 0xBF, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) || // UTF-8 BOM and "WEBVTT"
bytes.Equal(raw, []byte{0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) // "WEBVTT" bytes.Equal(raw, []byte{0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) // "WEBVTT"
} }
type rfc822Hint struct {
h []byte
matchFlags scan.Flags
}
// The hints come from libmagic, but the implementation is bit different. libmagic
// only checks if the file starts with the hint, while we additionally look for
// a secondary hint in the first few lines of input.
func RFC822(raw []byte, limit uint32) bool {
b := scan.Bytes(raw)
// Keep hints here to avoid instantiating them several times in lineHasRFC822Hint.
// The alternative is to make them a package level var, but then they'd go
// on the heap.
// Some of the hints are IgnoreCase, some not. I selected based on what libmagic
// does and based on personal observations from sample files.
hints := []rfc822Hint{
{[]byte("From: "), 0},
{[]byte("To: "), 0},
{[]byte("CC: "), scan.IgnoreCase},
{[]byte("Date: "), 0},
{[]byte("Subject: "), 0},
{[]byte("Received: "), 0},
{[]byte("Relay-Version: "), 0},
{[]byte("#! rnews"), 0},
{[]byte("N#! rnews"), 0},
{[]byte("Forward to"), 0},
{[]byte("Pipe to"), 0},
{[]byte("DELIVERED-TO: "), scan.IgnoreCase},
{[]byte("RETURN-PATH: "), scan.IgnoreCase},
{[]byte("Content-Type: "), 0},
{[]byte("Content-Transfer-Encoding: "), 0},
}
if !lineHasRFC822Hint(b.Line(), hints) {
return false
}
for i := 0; i < 20; i++ {
if lineHasRFC822Hint(b.Line(), hints) {
return true
}
}
return false
}
func lineHasRFC822Hint(b scan.Bytes, hints []rfc822Hint) bool {
for _, h := range hints {
if b.Match(h.h, h.matchFlags) > -1 {
return true
}
}
return false
}

View file

@ -69,9 +69,9 @@ func isFileTypeNamePresent(in []byte, flType string) bool {
// vintWidth parses the variable-integer width in matroska containers // vintWidth parses the variable-integer width in matroska containers
func vintWidth(v int) int { func vintWidth(v int) int {
mask, max, num := 128, 8, 1 mask, nTimes, num := 128, 8, 1
for num < max && v&mask == 0 { for num < nTimes && v&mask == 0 {
mask = mask >> 1 mask >>= 1
num++ num++
} }
return num return num

View file

@ -85,10 +85,14 @@ func Zip(raw []byte, limit uint32) bool {
// (instead of relying on offsets told by the file.) // (instead of relying on offsets told by the file.)
func Jar(raw []byte, limit uint32) bool { func Jar(raw []byte, limit uint32) bool {
return executableJar(raw) || return executableJar(raw) ||
// First entry must be an empty META-INF directory or the manifest.
// There is no specification saying that, but the jar reader and writer
// implementations from Java do it that way.
// https://github.com/openjdk/jdk/blob/88c4678eed818cbe9380f35352e90883fed27d33/src/java.base/share/classes/java/util/jar/JarInputStream.java#L170-L173
zipHas(raw, zipEntries{{ zipHas(raw, zipEntries{{
name: []byte("META-INF/MANIFEST.MF"),
}, {
name: []byte("META-INF/"), name: []byte("META-INF/"),
}, {
name: []byte("META-INF/MANIFEST.MF"),
}}, 1) }}, 1)
} }
@ -127,11 +131,14 @@ type zipEntries []struct {
func (z zipEntries) match(file []byte) bool { func (z zipEntries) match(file []byte) bool {
for i := range z { for i := range z {
if z[i].dir && bytes.HasPrefix(file, z[i].name) { if z[i].dir {
return true if bytes.HasPrefix(file, z[i].name) {
} return true
if bytes.Equal(file, z[i].name) { }
return true } else {
if bytes.Equal(file, z[i].name) {
return true
}
} }
} }
return false return false

View file

@ -35,6 +35,19 @@ func (b *Bytes) TrimRWS() {
} }
} }
// FirstNonWS returns the first non-whitespace character from b,
// or 0x00 if no such character is found.
func (b Bytes) FirstNonWS() byte {
for i := range b {
if ByteIsWS(b[i]) {
continue
}
return b[i]
}
return 0x00
}
// Peek one byte from b or 0x00 if b is empty. // Peek one byte from b or 0x00 if b is empty.
func (b *Bytes) Peek() byte { func (b *Bytes) Peek() byte {
if len(*b) > 0 { if len(*b) > 0 {
@ -63,8 +76,8 @@ func (b *Bytes) PopN(n int) []byte {
return nil return nil
} }
// PopUntil will advance b until, but not including, the first occurence of stopAt // PopUntil will advance b until, but not including, the first occurrence of stopAt
// character. If no occurence is found, then it will advance until the end of b. // character. If no occurrence is found, then it will advance until the end of b.
// The returned Bytes is a slice of all the bytes that we're advanced over. // The returned Bytes is a slice of all the bytes that we're advanced over.
func (b *Bytes) PopUntil(stopAt ...byte) Bytes { func (b *Bytes) PopUntil(stopAt ...byte) Bytes {
if len(*b) == 0 { if len(*b) == 0 {
@ -77,7 +90,7 @@ func (b *Bytes) PopUntil(stopAt ...byte) Bytes {
prefix := (*b)[:i] prefix := (*b)[:i]
*b = (*b)[i:] *b = (*b)[i:]
return Bytes(prefix) return prefix
} }
// ReadSlice is the same as PopUntil, but the returned value includes stopAt as well. // ReadSlice is the same as PopUntil, but the returned value includes stopAt as well.
@ -94,7 +107,7 @@ func (b *Bytes) ReadSlice(stopAt byte) Bytes {
prefix := (*b)[:i] prefix := (*b)[:i]
*b = (*b)[i:] *b = (*b)[i:]
return Bytes(prefix) return prefix
} }
// Line returns the first line from b and advances b with the length of the // Line returns the first line from b and advances b with the length of the
@ -117,7 +130,7 @@ func (b *Bytes) Line() Bytes {
// If b length is less than readLimit, it means we received an incomplete file // If b length is less than readLimit, it means we received an incomplete file
// and proceed with dropping the last line. // and proceed with dropping the last line.
func (b *Bytes) DropLastLine(readLimit uint32) { func (b *Bytes) DropLastLine(readLimit uint32) {
if readLimit == 0 || uint32(len(*b)) < readLimit { if readLimit == 0 || uint64(len(*b)) < uint64(readLimit) {
return return
} }
@ -151,7 +164,7 @@ const (
FullWord FullWord
) )
// Search for occurences of pattern p inside b at any index. // Search for occurrences of pattern p inside b at any index.
// It returns the index where p was found in b and how many bytes were needed // It returns the index where p was found in b and how many bytes were needed
// for matching the pattern. // for matching the pattern.
func (b Bytes) Search(p []byte, flags Flags) (i int, l int) { func (b Bytes) Search(p []byte, flags Flags) (i int, l int) {

View file

@ -2,6 +2,7 @@ package mimetype
import ( import (
"mime" "mime"
"slices"
"strings" "strings"
"github.com/gabriel-vasile/mimetype/internal/charset" "github.com/gabriel-vasile/mimetype/internal/charset"
@ -58,10 +59,8 @@ func (m *MIME) Is(expectedMIME string) bool {
return true return true
} }
for _, alias := range m.aliases { if slices.Contains(m.aliases, expectedMIME) {
if alias == expectedMIME { return true
return true
}
} }
return false return false
@ -180,10 +179,8 @@ func (m *MIME) lookup(mime string) *MIME {
if mime == m.mime { if mime == m.mime {
return m return m
} }
for _, n := range m.aliases { if slices.Contains(m.aliases, mime) {
if n == mime { return m
return m
}
} }
for _, c := range m.children { for _, c := range m.children {

View file

@ -1,4 +1,4 @@
## 192 Supported MIME types ## 195 Supported MIME types
This file is automatically generated when running tests. Do not edit manually. This file is automatically generated when running tests. Do not edit manually.
Extension | MIME type <br> Aliases | Hierarchy Extension | MIME type <br> Aliases | Hierarchy
@ -152,6 +152,8 @@ Extension | MIME type <br> Aliases | Hierarchy
**.chm** | **application/vnd.ms-htmlhelp** | chm>root **.chm** | **application/vnd.ms-htmlhelp** | chm>root
**.wpd** | **application/vnd.wordperfect** | wpd>root **.wpd** | **application/vnd.wordperfect** | wpd>root
**.dxf** | **image/vnd.dxf** | dxf>root **.dxf** | **image/vnd.dxf** | dxf>root
**.grb** | **application/grib** | grb>root
**n/a** | **application/zlib** | zlib>root
**.txt** | **text/plain** | txt>root **.txt** | **text/plain** | txt>root
**.svg** | **image/svg+xml** | svg>txt>root **.svg** | **image/svg+xml** | svg>txt>root
**.html** | **text/html** | html>txt>root **.html** | **text/html** | html>txt>root
@ -195,3 +197,4 @@ Extension | MIME type <br> Aliases | Hierarchy
**.pgm** | **image/x-portable-graymap** | pgm>txt>root **.pgm** | **image/x-portable-graymap** | pgm>txt>root
**.ppm** | **image/x-portable-pixmap** | ppm>txt>root **.ppm** | **image/x-portable-pixmap** | ppm>txt>root
**.pam** | **image/x-portable-arbitrarymap** | pam>txt>root **.pam** | **image/x-portable-arbitrarymap** | pam>txt>root
**.eml** | **message/rfc822** | eml>txt>root

View file

@ -24,7 +24,7 @@ var root = newMIME("application/octet-stream", "",
woff2, otf, ttc, eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg, cbor, woff2, otf, ttc, eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg, cbor,
sqlite3, dwg, nes, lnk, macho, qcp, icns, hdr, mrc, mdb, accdb, zstd, cab, sqlite3, dwg, nes, lnk, macho, qcp, icns, hdr, mrc, mdb, accdb, zstd, cab,
rpm, xz, lzip, torrent, cpio, tzif, xcf, pat, gbr, glb, cabIS, jxr, parquet, rpm, xz, lzip, torrent, cpio, tzif, xcf, pat, gbr, glb, cabIS, jxr, parquet,
oneNote, chm, wpd, dxf, oneNote, chm, wpd, dxf, grib, zlib,
// Keep text last because it is the slowest check. // Keep text last because it is the slowest check.
text, text,
) )
@ -82,7 +82,7 @@ var (
alias("application/x-ogg") alias("application/x-ogg")
oggAudio = newMIME("audio/ogg", ".oga", magic.OggAudio) oggAudio = newMIME("audio/ogg", ".oga", magic.OggAudio)
oggVideo = newMIME("video/ogg", ".ogv", magic.OggVideo) oggVideo = newMIME("video/ogg", ".ogv", magic.OggVideo)
text = newMIME("text/plain", ".txt", magic.Text, svg, html, xml, php, js, lua, perl, python, ruby, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt, shell, netpbm, netpgm, netppm, netpam) text = newMIME("text/plain", ".txt", magic.Text, svg, html, xml, php, js, lua, perl, python, ruby, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt, shell, netpbm, netpgm, netppm, netpam, rfc822)
xml = newMIME("text/xml", ".xml", magic.XML, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf, xfdf, owl2, xhtml). xml = newMIME("text/xml", ".xml", magic.XML, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf, xfdf, owl2, xhtml).
alias("application/xml") alias("application/xml")
xhtml = newMIME("application/xhtml+xml", ".html", magic.XHTML) xhtml = newMIME("application/xhtml+xml", ".html", magic.XHTML)
@ -287,4 +287,7 @@ var (
chm = newMIME("application/vnd.ms-htmlhelp", ".chm", magic.CHM) chm = newMIME("application/vnd.ms-htmlhelp", ".chm", magic.CHM)
wpd = newMIME("application/vnd.wordperfect", ".wpd", magic.WPD) wpd = newMIME("application/vnd.wordperfect", ".wpd", magic.WPD)
dxf = newMIME("image/vnd.dxf", ".dxf", magic.DXF) dxf = newMIME("image/vnd.dxf", ".dxf", magic.DXF)
rfc822 = newMIME("message/rfc822", ".eml", magic.RFC822)
grib = newMIME("application/grib", ".grb", magic.GRIB)
zlib = newMIME("application/zlib", "", magic.Zlib)
) )

View file

@ -33,6 +33,18 @@ var ErrForeignTables = errors.New("the database contains foreign tables")
var ErrNotOwned = errors.New("the database is owned by") var ErrNotOwned = errors.New("the database is owned by")
var ErrUnsupportedDialect = errors.New("unsupported database dialect") var ErrUnsupportedDialect = errors.New("unsupported database dialect")
type NotOwnedError struct {
Owner string
}
func (e NotOwnedError) Error() string {
return fmt.Sprintf("%v %s", ErrNotOwned, e.Owner)
}
func (e NotOwnedError) Unwrap() error {
return ErrNotOwned
}
func DangerousInternalUpgradeVersionTable(ctx context.Context, db *Database) error { func DangerousInternalUpgradeVersionTable(ctx context.Context, db *Database) error {
return db.upgradeVersionTable(ctx) return db.upgradeVersionTable(ctx)
} }
@ -143,7 +155,7 @@ func (db *Database) checkDatabaseOwner(ctx context.Context) error {
} else if err != nil { } else if err != nil {
return fmt.Errorf("failed to check database owner: %w", err) return fmt.Errorf("failed to check database owner: %w", err)
} else if owner != db.Owner { } else if owner != db.Owner {
return fmt.Errorf("%w %s", ErrNotOwned, owner) return NotOwnedError{owner}
} }
return nil return nil
} }

View file

@ -82,6 +82,23 @@ func (sm *Map[Key, Value]) GetDefault(key Key, def Value) Value {
return def return def
} }
// GetOrSetFactory gets a value in the map if the key already exists,
// otherwise inserts a new value from the given function and returns it.
func (sm *Map[Key, Value]) GetOrSetFactory(key Key, def func() Value) Value {
val, ok := sm.Get(key)
if ok {
return val
}
sm.lock.Lock()
defer sm.lock.Unlock()
value, ok := sm.data[key]
if !ok {
value = def()
sm.data[key] = value
}
return value
}
// GetOrSet gets a value in the map if the key already exists, otherwise inserts the given value and returns it. // GetOrSet gets a value in the map if the key already exists, otherwise inserts the given value and returns it.
// //
// The boolean return parameter is true if the key already exists, and false if the given value was inserted. // The boolean return parameter is true if the key already exists, and false if the given value was inserted.
@ -120,6 +137,21 @@ func (sm *Map[Key, Value]) CopyFrom(other map[Key]Value) {
sm.lock.Unlock() sm.lock.Unlock()
} }
// SwapData replaces the internal map with the given map, returning the previous map.
// If the given map is nil, a new empty map is created.
// The given map must not be modified after passing it to this function.
func (sm *Map[Key, Value]) SwapData(other map[Key]Value) map[Key]Value {
sm.lock.Lock()
prev := sm.data
if other == nil {
sm.data = make(map[Key]Value)
} else {
sm.data = other
}
sm.lock.Unlock()
return prev
}
// Clone returns a copy of the map. // Clone returns a copy of the map.
func (sm *Map[Key, Value]) Clone() *Map[Key, Value] { func (sm *Map[Key, Value]) Clone() *Map[Key, Value] {
return NewMapWithData(sm.CopyData()) return NewMapWithData(sm.CopyData())

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"reflect"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@ -149,7 +150,7 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added
if newImport.Name != nil { if newImport.Name != nil {
newImport.Name.NamePos = pos newImport.Name.NamePos = pos
} }
newImport.Path.ValuePos = pos updateBasicLitPos(newImport.Path, pos)
newImport.EndPos = pos newImport.EndPos = pos
// Clean up parens. impDecl contains at least one spec. // Clean up parens. impDecl contains at least one spec.
@ -184,7 +185,7 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added
first.Lparen = first.Pos() first.Lparen = first.Pos()
// Move the imports of the other import declaration to the first one. // Move the imports of the other import declaration to the first one.
for _, spec := range gen.Specs { for _, spec := range gen.Specs {
spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() updateBasicLitPos(spec.(*ast.ImportSpec).Path, first.Pos())
first.Specs = append(first.Specs, spec) first.Specs = append(first.Specs, spec)
} }
f.Decls = slices.Delete(f.Decls, i, i+1) f.Decls = slices.Delete(f.Decls, i, i+1)
@ -470,3 +471,17 @@ func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
return groups return groups
} }
// updateBasicLitPos updates lit.Pos,
// ensuring that lit.End (if set) is displaced by the same amount.
// (See https://go.dev/issue/76395.)
func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) {
len := lit.End() - lit.Pos()
lit.ValuePos = pos
// TODO(adonovan): after go1.26, simplify to:
// lit.ValueEnd = pos + len
v := reflect.ValueOf(lit).Elem().FieldByName("ValueEnd")
if v.IsValid() && v.Int() != 0 {
v.SetInt(int64(pos + len))
}
}

View file

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 rev: v6.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
exclude_types: [markdown] exclude_types: [markdown]
@ -9,7 +9,7 @@ repos:
- id: check-added-large-files - id: check-added-large-files
- repo: https://github.com/tekwizely/pre-commit-golang - repo: https://github.com/tekwizely/pre-commit-golang
rev: v1.0.0-rc.1 rev: v1.0.0-rc.4
hooks: hooks:
- id: go-imports-repo - id: go-imports-repo
args: args:
@ -18,8 +18,7 @@ repos:
- "-w" - "-w"
- id: go-vet-repo-mod - id: go-vet-repo-mod
- id: go-mod-tidy - id: go-mod-tidy
# TODO enable this - id: go-staticcheck-repo-mod
#- id: go-staticcheck-repo-mod
- repo: https://github.com/beeper/pre-commit-go - repo: https://github.com/beeper/pre-commit-go
rev: v0.4.2 rev: v0.4.2

View file

@ -1,3 +1,35 @@
## v0.26.1 (2025-12-16)
* **Breaking change *(mediaproxy)*** Changed `GetMediaResponseFile` to return
the mime type from the callback rather than in the return get media return
value. The callback can now also redirect the caller to a different file.
* *(federation)* Added join/knock/leave functions
(thanks to [@nexy7574] in [#422]).
* *(federation/eventauth)* Fixed various incorrect checks.
* *(client)* Added backoff for retrying media uploads to external URLs
(with MSC3870).
* *(bridgev2/config)* Added support for overriding config fields using
environment variables.
* *(bridgev2/commands)* Added command to mute chat on remote network.
* *(bridgev2)* Added interface for network connectors to redirect to a different
user ID when handling an invite from Matrix.
* *(bridgev2)* Added interface for signaling message request status of portals.
* *(bridgev2)* Changed portal creation to not backfill unless `CanBackfill` flag
is set in chat info.
* *(bridgev2)* Changed Matrix reaction handling to only delete old reaction if
bridging the new one is successful.
* *(bridgev2/mxmain)* Improved error message when trying to run bridge with
pre-megabridge database when no database migration exists.
* *(bridgev2)* Improved reliability of database migration when enabling split
portals.
* *(bridgev2)* Improved detection of orphaned DM rooms when starting new chats.
* *(bridgev2)* Stopped sending redundant invites when joining ghosts to public
portal rooms.
* *(bridgev2)* Stopped hardcoding room versions in favor of checking
server capabilities to determine appropriate `/createRoom` parameters.
[#422]: https://github.com/mautrix/go/pull/422
## v0.26.0 (2025-11-16) ## v0.26.0 (2025-11-16)
* *(client,appservice)* Deprecated `SendMassagedStateEvent` as `SendStateEvent` * *(client,appservice)* Deprecated `SendMassagedStateEvent` as `SendStateEvent`
@ -43,7 +75,7 @@
* *(federation)* Fixed validating auth for requests with query params. * *(federation)* Fixed validating auth for requests with query params.
* *(federation/eventauth)* Fixed typo causing restricted joins to not work. * *(federation/eventauth)* Fixed typo causing restricted joins to not work.
[MSC416]: https://github.com/matrix-org/matrix-spec-proposals/pull/4169 [MSC4169]: https://github.com/matrix-org/matrix-spec-proposals/pull/4169
[#411]: github.com/mautrix/go/pull/411 [#411]: github.com/mautrix/go/pull/411
[#420]: github.com/mautrix/go/pull/420 [#420]: github.com/mautrix/go/pull/420
[#426]: github.com/mautrix/go/pull/426 [#426]: github.com/mautrix/go/pull/426

View file

@ -614,7 +614,9 @@ func (cli *Client) doRetry(
select { select {
case <-time.After(backoff): case <-time.After(backoff):
case <-req.Context().Done(): case <-req.Context().Done():
return nil, nil, req.Context().Err() if !errors.Is(context.Cause(req.Context()), ErrContextCancelRetry) {
return nil, nil, req.Context().Err()
}
} }
if cli.UpdateRequestOnRetry != nil { if cli.UpdateRequestOnRetry != nil {
req = cli.UpdateRequestOnRetry(req, cause) req = cli.UpdateRequestOnRetry(req, cause)
@ -740,12 +742,15 @@ func (cli *Client) executeCompiledRequest(
cli.RequestStart(req) cli.RequestStart(req)
startTime := time.Now() startTime := time.Now()
res, err := client.Do(req) res, err := client.Do(req)
duration := time.Now().Sub(startTime) duration := time.Since(startTime)
if res != nil && !dontReadResponse { if res != nil && !dontReadResponse {
defer res.Body.Close() defer res.Body.Close()
} }
if err != nil { if err != nil {
if retries > 0 && !errors.Is(err, context.Canceled) { // Either error is *not* canceled or the underlying cause of cancelation explicitly asks to retry
canRetry := !errors.Is(err, context.Canceled) ||
errors.Is(context.Cause(req.Context()), ErrContextCancelRetry)
if retries > 0 && canRetry {
return cli.doRetry( return cli.doRetry(
req, err, retries, backoff, responseJSON, handler, dontReadResponse, sizeLimit, client, req, err, retries, backoff, responseJSON, handler, dontReadResponse, sizeLimit, client,
) )
@ -857,7 +862,7 @@ func (cli *Client) FullSyncRequest(ctx context.Context, req ReqSync) (resp *Resp
} }
start := time.Now() start := time.Now()
_, err = cli.MakeFullRequest(ctx, fullReq) _, err = cli.MakeFullRequest(ctx, fullReq)
duration := time.Now().Sub(start) duration := time.Since(start)
timeout := time.Duration(req.Timeout) * time.Millisecond timeout := time.Duration(req.Timeout) * time.Millisecond
buffer := 10 * time.Second buffer := 10 * time.Second
if req.Since == "" { if req.Since == "" {
@ -961,7 +966,7 @@ func (cli *Client) RegisterGuest(ctx context.Context, req *ReqRegister) (*RespRe
// } // }
// token := res.AccessToken // token := res.AccessToken
func (cli *Client) RegisterDummy(ctx context.Context, req *ReqRegister) (*RespRegister, error) { func (cli *Client) RegisterDummy(ctx context.Context, req *ReqRegister) (*RespRegister, error) {
res, uia, err := cli.Register(ctx, req) _, uia, err := cli.Register(ctx, req)
if err != nil && uia == nil { if err != nil && uia == nil {
return nil, err return nil, err
} else if uia == nil { } else if uia == nil {
@ -970,7 +975,7 @@ func (cli *Client) RegisterDummy(ctx context.Context, req *ReqRegister) (*RespRe
return nil, errors.New("server does not support m.login.dummy") return nil, errors.New("server does not support m.login.dummy")
} }
req.Auth = BaseAuthData{Type: AuthTypeDummy, Session: uia.Session} req.Auth = BaseAuthData{Type: AuthTypeDummy, Session: uia.Session}
res, _, err = cli.Register(ctx, req) res, _, err := cli.Register(ctx, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1746,6 +1751,8 @@ func parseRoomStateArray(req *http.Request, res *http.Response, responseJSON any
return nil, nil return nil, nil
} }
type RoomStateMap = map[event.Type]map[string]*event.Event
// State gets all state in a room. // State gets all state in a room.
// See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidstate // See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidstate
func (cli *Client) State(ctx context.Context, roomID id.RoomID) (stateMap RoomStateMap, err error) { func (cli *Client) State(ctx context.Context, roomID id.RoomID) (stateMap RoomStateMap, err error) {
@ -2014,8 +2021,16 @@ func (cli *Client) uploadMediaToURL(ctx context.Context, data ReqUploadMedia) (*
Msg("Error uploading media to external URL, not retrying") Msg("Error uploading media to external URL, not retrying")
return nil, err return nil, err
} }
cli.Log.Warn().Str("url", data.UnstableUploadURL).Err(err). backoff := time.Second * time.Duration(cli.DefaultHTTPRetries-retries)
cli.Log.Warn().Err(err).
Str("url", data.UnstableUploadURL).
Int("retry_in_seconds", int(backoff.Seconds())).
Msg("Error uploading media to external URL, retrying") Msg("Error uploading media to external URL, retrying")
select {
case <-time.After(backoff):
case <-ctx.Done():
return nil, ctx.Err()
}
retries-- retries--
_, err = readerSeeker.Seek(0, io.SeekStart) _, err = readerSeeker.Seek(0, io.SeekStart)
if err != nil { if err != nil {

View file

@ -21,13 +21,24 @@ import (
) )
var ( var (
HashMismatch = errors.New("mismatching SHA-256 digest") ErrHashMismatch = errors.New("mismatching SHA-256 digest")
UnsupportedVersion = errors.New("unsupported Matrix file encryption version") ErrUnsupportedVersion = errors.New("unsupported Matrix file encryption version")
UnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm") ErrUnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm")
InvalidKey = errors.New("failed to decode key") ErrInvalidKey = errors.New("failed to decode key")
InvalidInitVector = errors.New("failed to decode initialization vector") ErrInvalidInitVector = errors.New("failed to decode initialization vector")
InvalidHash = errors.New("failed to decode SHA-256 hash") ErrInvalidHash = errors.New("failed to decode SHA-256 hash")
ReaderClosed = errors.New("encrypting reader was already closed") ErrReaderClosed = errors.New("encrypting reader was already closed")
)
// Deprecated: use variables prefixed with Err
var (
HashMismatch = ErrHashMismatch
UnsupportedVersion = ErrUnsupportedVersion
UnsupportedAlgorithm = ErrUnsupportedAlgorithm
InvalidKey = ErrInvalidKey
InvalidInitVector = ErrInvalidInitVector
InvalidHash = ErrInvalidHash
ReaderClosed = ErrReaderClosed
) )
var ( var (
@ -85,25 +96,25 @@ func (ef *EncryptedFile) decodeKeys(includeHash bool) error {
if ef.decoded != nil { if ef.decoded != nil {
return nil return nil
} else if len(ef.Key.Key) != keyBase64Length { } else if len(ef.Key.Key) != keyBase64Length {
return InvalidKey return ErrInvalidKey
} else if len(ef.InitVector) != ivBase64Length { } else if len(ef.InitVector) != ivBase64Length {
return InvalidInitVector return ErrInvalidInitVector
} else if includeHash && len(ef.Hashes.SHA256) != hashBase64Length { } else if includeHash && len(ef.Hashes.SHA256) != hashBase64Length {
return InvalidHash return ErrInvalidHash
} }
ef.decoded = &decodedKeys{} ef.decoded = &decodedKeys{}
_, err := base64.RawURLEncoding.Decode(ef.decoded.key[:], []byte(ef.Key.Key)) _, err := base64.RawURLEncoding.Decode(ef.decoded.key[:], []byte(ef.Key.Key))
if err != nil { if err != nil {
return InvalidKey return ErrInvalidKey
} }
_, err = base64.RawStdEncoding.Decode(ef.decoded.iv[:], []byte(ef.InitVector)) _, err = base64.RawStdEncoding.Decode(ef.decoded.iv[:], []byte(ef.InitVector))
if err != nil { if err != nil {
return InvalidInitVector return ErrInvalidInitVector
} }
if includeHash { if includeHash {
_, err = base64.RawStdEncoding.Decode(ef.decoded.sha256[:], []byte(ef.Hashes.SHA256)) _, err = base64.RawStdEncoding.Decode(ef.decoded.sha256[:], []byte(ef.Hashes.SHA256))
if err != nil { if err != nil {
return InvalidHash return ErrInvalidHash
} }
} }
return nil return nil
@ -179,7 +190,7 @@ var _ io.ReadSeekCloser = (*encryptingReader)(nil)
func (r *encryptingReader) Seek(offset int64, whence int) (int64, error) { func (r *encryptingReader) Seek(offset int64, whence int) (int64, error) {
if r.closed { if r.closed {
return 0, ReaderClosed return 0, ErrReaderClosed
} }
if offset != 0 || whence != io.SeekStart { if offset != 0 || whence != io.SeekStart {
return 0, fmt.Errorf("attachments.EncryptStream: only seeking to the beginning is supported") return 0, fmt.Errorf("attachments.EncryptStream: only seeking to the beginning is supported")
@ -200,7 +211,7 @@ func (r *encryptingReader) Seek(offset int64, whence int) (int64, error) {
func (r *encryptingReader) Read(dst []byte) (n int, err error) { func (r *encryptingReader) Read(dst []byte) (n int, err error) {
if r.closed { if r.closed {
return 0, ReaderClosed return 0, ErrReaderClosed
} else if r.isDecrypting && r.file.decoded == nil { } else if r.isDecrypting && r.file.decoded == nil {
if err = r.file.PrepareForDecryption(); err != nil { if err = r.file.PrepareForDecryption(); err != nil {
return return
@ -224,7 +235,7 @@ func (r *encryptingReader) Close() (err error) {
} }
if r.isDecrypting { if r.isDecrypting {
if !hmac.Equal(r.hash.Sum(nil), r.file.decoded.sha256[:]) { if !hmac.Equal(r.hash.Sum(nil), r.file.decoded.sha256[:]) {
return HashMismatch return ErrHashMismatch
} }
} else { } else {
r.file.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(r.hash.Sum(nil)) r.file.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(r.hash.Sum(nil))
@ -265,9 +276,9 @@ func (ef *EncryptedFile) Decrypt(ciphertext []byte) ([]byte, error) {
// DecryptInPlace will always call this automatically, so calling this manually is not necessary when using that function. // DecryptInPlace will always call this automatically, so calling this manually is not necessary when using that function.
func (ef *EncryptedFile) PrepareForDecryption() error { func (ef *EncryptedFile) PrepareForDecryption() error {
if ef.Version != "v2" { if ef.Version != "v2" {
return UnsupportedVersion return ErrUnsupportedVersion
} else if ef.Key.Algorithm != "A256CTR" { } else if ef.Key.Algorithm != "A256CTR" {
return UnsupportedAlgorithm return ErrUnsupportedAlgorithm
} else if err := ef.decodeKeys(true); err != nil { } else if err := ef.decodeKeys(true); err != nil {
return err return err
} }
@ -281,7 +292,7 @@ func (ef *EncryptedFile) DecryptInPlace(data []byte) error {
} }
dataHash := sha256.Sum256(data) dataHash := sha256.Sum256(data)
if !hmac.Equal(ef.decoded.sha256[:], dataHash[:]) { if !hmac.Equal(ef.decoded.sha256[:], dataHash[:]) {
return HashMismatch return ErrHashMismatch
} }
utils.XorA256CTR(data, ef.decoded.key, ef.decoded.iv) utils.XorA256CTR(data, ef.decoded.key, ef.decoded.iv)
return nil return nil

View file

@ -63,8 +63,8 @@ func (mach *OlmMachine) GetCrossSigningPublicKeys(ctx context.Context, userID id
if len(dbKeys) > 0 { if len(dbKeys) > 0 {
masterKey, ok := dbKeys[id.XSUsageMaster] masterKey, ok := dbKeys[id.XSUsageMaster]
if ok { if ok {
selfSigning, _ := dbKeys[id.XSUsageSelfSigning] selfSigning := dbKeys[id.XSUsageSelfSigning]
userSigning, _ := dbKeys[id.XSUsageUserSigning] userSigning := dbKeys[id.XSUsageUserSigning]
return &CrossSigningPublicKeysCache{ return &CrossSigningPublicKeysCache{
MasterKey: masterKey.Key, MasterKey: masterKey.Key,
SelfSigningKey: selfSigning.Key, SelfSigningKey: selfSigning.Key,

View file

@ -26,24 +26,22 @@ func (mach *OlmMachine) storeCrossSigningKeys(ctx context.Context, crossSigningK
log.Error().Err(err). log.Error().Err(err).
Msg("Error fetching current cross-signing keys of user") Msg("Error fetching current cross-signing keys of user")
} }
if currentKeys != nil { for curKeyUsage, curKey := range currentKeys {
for curKeyUsage, curKey := range currentKeys { log := log.With().Stringer("old_key", curKey.Key).Str("old_key_usage", string(curKeyUsage)).Logger()
log := log.With().Stringer("old_key", curKey.Key).Str("old_key_usage", string(curKeyUsage)).Logger() // got a new key with the same usage as an existing key
// got a new key with the same usage as an existing key for _, newKeyUsage := range userKeys.Usage {
for _, newKeyUsage := range userKeys.Usage { if newKeyUsage == curKeyUsage {
if newKeyUsage == curKeyUsage { if _, ok := userKeys.Keys[id.NewKeyID(id.KeyAlgorithmEd25519, curKey.Key.String())]; !ok {
if _, ok := userKeys.Keys[id.NewKeyID(id.KeyAlgorithmEd25519, curKey.Key.String())]; !ok { // old key is not in the new key map, so we drop signatures made by it
// old key is not in the new key map, so we drop signatures made by it if count, err := mach.CryptoStore.DropSignaturesByKey(ctx, userID, curKey.Key); err != nil {
if count, err := mach.CryptoStore.DropSignaturesByKey(ctx, userID, curKey.Key); err != nil { log.Error().Err(err).Msg("Error deleting old signatures made by user")
log.Error().Err(err).Msg("Error deleting old signatures made by user") } else {
} else { log.Debug().
log.Debug(). Int64("signature_count", count).
Int64("signature_count", count). Msg("Dropped signatures made by old key as it has been replaced")
Msg("Dropped signatures made by old key as it has been replaced")
}
} }
break
} }
break
} }
} }
} }

View file

@ -278,7 +278,7 @@ func (helper *CryptoHelper) verifyDeviceKeysOnServer(ctx context.Context) error
} }
} }
var NoSessionFound = crypto.NoSessionFound var NoSessionFound = crypto.ErrNoSessionFound
const initialSessionWaitTimeout = 3 * time.Second const initialSessionWaitTimeout = 3 * time.Second
const extendedSessionWaitTimeout = 22 * time.Second const extendedSessionWaitTimeout = 22 * time.Second
@ -371,6 +371,7 @@ func (helper *CryptoHelper) waitLongerForSession(ctx context.Context, evt *event
content := evt.Content.AsEncrypted() content := evt.Content.AsEncrypted()
log.Debug().Int("wait_seconds", int(extendedSessionWaitTimeout.Seconds())).Msg("Couldn't find session, requesting keys and waiting longer...") log.Debug().Int("wait_seconds", int(extendedSessionWaitTimeout.Seconds())).Msg("Couldn't find session, requesting keys and waiting longer...")
//lint:ignore SA1019 RequestSession will gracefully request from all devices if DeviceID is blank
go helper.RequestSession(context.TODO(), evt.RoomID, content.SenderKey, content.SessionID, evt.Sender, content.DeviceID) go helper.RequestSession(context.TODO(), evt.RoomID, content.SenderKey, content.SessionID, evt.Sender, content.DeviceID)
if !helper.mach.WaitForSession(ctx, evt.RoomID, content.SenderKey, content.SessionID, extendedSessionWaitTimeout) { if !helper.mach.WaitForSession(ctx, evt.RoomID, content.SenderKey, content.SessionID, extendedSessionWaitTimeout) {
@ -418,7 +419,7 @@ func (helper *CryptoHelper) EncryptWithStateKey(ctx context.Context, roomID id.R
defer helper.lock.RUnlock() defer helper.lock.RUnlock()
encrypted, err = helper.mach.EncryptMegolmEventWithStateKey(ctx, roomID, evtType, stateKey, content) encrypted, err = helper.mach.EncryptMegolmEventWithStateKey(ctx, roomID, evtType, stateKey, content)
if err != nil { if err != nil {
if !errors.Is(err, crypto.SessionExpired) && err != crypto.NoGroupSession && !errors.Is(err, crypto.SessionNotShared) { if !errors.Is(err, crypto.ErrSessionExpired) && err != crypto.ErrNoGroupSession && !errors.Is(err, crypto.ErrSessionNotShared) {
return return
} }
helper.log.Debug(). helper.log.Debug().

View file

@ -24,13 +24,24 @@ import (
) )
var ( var (
IncorrectEncryptedContentType = errors.New("event content is not instance of *event.EncryptedEventContent") ErrIncorrectEncryptedContentType = errors.New("event content is not instance of *event.EncryptedEventContent")
NoSessionFound = errors.New("failed to decrypt megolm event: no session with given ID found") ErrNoSessionFound = errors.New("failed to decrypt megolm event: no session with given ID found")
DuplicateMessageIndex = errors.New("duplicate megolm message index") ErrDuplicateMessageIndex = errors.New("duplicate megolm message index")
WrongRoom = errors.New("encrypted megolm event is not intended for this room") ErrWrongRoom = errors.New("encrypted megolm event is not intended for this room")
DeviceKeyMismatch = errors.New("device keys in event and verified device info do not match") ErrDeviceKeyMismatch = errors.New("device keys in event and verified device info do not match")
SenderKeyMismatch = errors.New("sender keys in content and megolm session do not match") ErrSenderKeyMismatch = errors.New("sender keys in content and megolm session do not match")
RatchetError = errors.New("failed to ratchet session after use") ErrRatchetError = errors.New("failed to ratchet session after use")
)
// Deprecated: use variables prefixed with Err
var (
IncorrectEncryptedContentType = ErrIncorrectEncryptedContentType
NoSessionFound = ErrNoSessionFound
DuplicateMessageIndex = ErrDuplicateMessageIndex
WrongRoom = ErrWrongRoom
DeviceKeyMismatch = ErrDeviceKeyMismatch
SenderKeyMismatch = ErrSenderKeyMismatch
RatchetError = ErrRatchetError
) )
type megolmEvent struct { type megolmEvent struct {
@ -49,9 +60,9 @@ var (
func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event) (*event.Event, error) { func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event) (*event.Event, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent) content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
if !ok { if !ok {
return nil, IncorrectEncryptedContentType return nil, ErrIncorrectEncryptedContentType
} else if content.Algorithm != id.AlgorithmMegolmV1 { } else if content.Algorithm != id.AlgorithmMegolmV1 {
return nil, UnsupportedAlgorithm return nil, ErrUnsupportedAlgorithm
} }
log := mach.machOrContextLog(ctx).With(). log := mach.machOrContextLog(ctx).With().
Str("action", "decrypt megolm event"). Str("action", "decrypt megolm event").
@ -97,7 +108,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
Msg("Couldn't resolve trust level of session: sent by unknown device") Msg("Couldn't resolve trust level of session: sent by unknown device")
trustLevel = id.TrustStateUnknownDevice trustLevel = id.TrustStateUnknownDevice
} else if device.SigningKey != sess.SigningKey || device.IdentityKey != sess.SenderKey { } else if device.SigningKey != sess.SigningKey || device.IdentityKey != sess.SenderKey {
return nil, DeviceKeyMismatch return nil, ErrDeviceKeyMismatch
} else { } else {
trustLevel, err = mach.ResolveTrustContext(ctx, device) trustLevel, err = mach.ResolveTrustContext(ctx, device)
if err != nil { if err != nil {
@ -147,7 +158,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse megolm payload: %w", err) return nil, fmt.Errorf("failed to parse megolm payload: %w", err)
} else if megolmEvt.RoomID != encryptionRoomID { } else if megolmEvt.RoomID != encryptionRoomID {
return nil, WrongRoom return nil, ErrWrongRoom
} }
if evt.StateKey != nil && megolmEvt.StateKey != nil && mach.AllowEncryptedState { if evt.StateKey != nil && megolmEvt.StateKey != nil && mach.AllowEncryptedState {
megolmEvt.Type.Class = event.StateEventType megolmEvt.Type.Class = event.StateEventType
@ -201,19 +212,19 @@ func (mach *OlmMachine) checkUndecryptableMessageIndexDuplication(ctx context.Co
messageIndex, decodeErr := ParseMegolmMessageIndex(content.MegolmCiphertext) messageIndex, decodeErr := ParseMegolmMessageIndex(content.MegolmCiphertext)
if decodeErr != nil { if decodeErr != nil {
log.Warn().Err(decodeErr).Msg("Failed to parse message index to check if it's a duplicate for message that failed to decrypt") log.Warn().Err(decodeErr).Msg("Failed to parse message index to check if it's a duplicate for message that failed to decrypt")
return 0, fmt.Errorf("%w (also failed to parse message index)", olm.UnknownMessageIndex) return 0, fmt.Errorf("%w (also failed to parse message index)", olm.ErrUnknownMessageIndex)
} }
firstKnown := sess.Internal.FirstKnownIndex() firstKnown := sess.Internal.FirstKnownIndex()
log = log.With().Uint("message_index", messageIndex).Uint32("first_known_index", firstKnown).Logger() log = log.With().Uint("message_index", messageIndex).Uint32("first_known_index", firstKnown).Logger()
if ok, err := mach.CryptoStore.ValidateMessageIndex(ctx, sess.SenderKey, content.SessionID, evt.ID, messageIndex, evt.Timestamp); err != nil { if ok, err := mach.CryptoStore.ValidateMessageIndex(ctx, sess.SenderKey, content.SessionID, evt.ID, messageIndex, evt.Timestamp); err != nil {
log.Debug().Err(err).Msg("Failed to check if message index is duplicate") log.Debug().Err(err).Msg("Failed to check if message index is duplicate")
return messageIndex, fmt.Errorf("%w (failed to check if index is duplicate; received: %d, earliest known: %d)", olm.UnknownMessageIndex, messageIndex, firstKnown) return messageIndex, fmt.Errorf("%w (failed to check if index is duplicate; received: %d, earliest known: %d)", olm.ErrUnknownMessageIndex, messageIndex, firstKnown)
} else if !ok { } else if !ok {
log.Debug().Msg("Failed to decrypt message due to unknown index and found duplicate") log.Debug().Msg("Failed to decrypt message due to unknown index and found duplicate")
return messageIndex, fmt.Errorf("%w %d (also failed to decrypt because earliest known index is %d)", DuplicateMessageIndex, messageIndex, firstKnown) return messageIndex, fmt.Errorf("%w %d (also failed to decrypt because earliest known index is %d)", ErrDuplicateMessageIndex, messageIndex, firstKnown)
} }
log.Debug().Msg("Failed to decrypt message due to unknown index, but index is not duplicate") log.Debug().Msg("Failed to decrypt message due to unknown index, but index is not duplicate")
return messageIndex, fmt.Errorf("%w (not duplicate index; received: %d, earliest known: %d)", olm.UnknownMessageIndex, messageIndex, firstKnown) return messageIndex, fmt.Errorf("%w (not duplicate index; received: %d, earliest known: %d)", olm.ErrUnknownMessageIndex, messageIndex, firstKnown)
} }
func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *event.Event, encryptionRoomID id.RoomID, content *event.EncryptedEventContent) (*InboundGroupSession, []byte, uint, error) { func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *event.Event, encryptionRoomID id.RoomID, content *event.EncryptedEventContent) (*InboundGroupSession, []byte, uint, error) {
@ -224,13 +235,13 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
if err != nil { if err != nil {
return nil, nil, 0, fmt.Errorf("failed to get group session: %w", err) return nil, nil, 0, fmt.Errorf("failed to get group session: %w", err)
} else if sess == nil { } else if sess == nil {
return nil, nil, 0, fmt.Errorf("%w (ID %s)", NoSessionFound, content.SessionID) return nil, nil, 0, fmt.Errorf("%w (ID %s)", ErrNoSessionFound, content.SessionID)
} else if content.SenderKey != "" && content.SenderKey != sess.SenderKey { } else if content.SenderKey != "" && content.SenderKey != sess.SenderKey {
return sess, nil, 0, SenderKeyMismatch return sess, nil, 0, ErrSenderKeyMismatch
} }
plaintext, messageIndex, err := sess.Internal.Decrypt(content.MegolmCiphertext) plaintext, messageIndex, err := sess.Internal.Decrypt(content.MegolmCiphertext)
if err != nil { if err != nil {
if errors.Is(err, olm.UnknownMessageIndex) && mach.RatchetKeysOnDecrypt { if errors.Is(err, olm.ErrUnknownMessageIndex) && mach.RatchetKeysOnDecrypt {
messageIndex, err = mach.checkUndecryptableMessageIndexDuplication(ctx, sess, evt, content) messageIndex, err = mach.checkUndecryptableMessageIndexDuplication(ctx, sess, evt, content)
return sess, nil, messageIndex, fmt.Errorf("failed to decrypt megolm event: %w", err) return sess, nil, messageIndex, fmt.Errorf("failed to decrypt megolm event: %w", err)
} }
@ -238,7 +249,7 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
} else if ok, err := mach.CryptoStore.ValidateMessageIndex(ctx, sess.SenderKey, content.SessionID, evt.ID, messageIndex, evt.Timestamp); err != nil { } else if ok, err := mach.CryptoStore.ValidateMessageIndex(ctx, sess.SenderKey, content.SessionID, evt.ID, messageIndex, evt.Timestamp); err != nil {
return sess, nil, messageIndex, fmt.Errorf("failed to check if message index is duplicate: %w", err) return sess, nil, messageIndex, fmt.Errorf("failed to check if message index is duplicate: %w", err)
} else if !ok { } else if !ok {
return sess, nil, messageIndex, fmt.Errorf("%w %d", DuplicateMessageIndex, messageIndex) return sess, nil, messageIndex, fmt.Errorf("%w %d", ErrDuplicateMessageIndex, messageIndex)
} }
// Normal clients don't care about tracking the ratchet state, so let them bypass the rest of the function // Normal clients don't care about tracking the ratchet state, so let them bypass the rest of the function
@ -290,24 +301,24 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
err = mach.CryptoStore.RedactGroupSession(ctx, sess.RoomID, sess.ID(), "maximum messages reached") err = mach.CryptoStore.RedactGroupSession(ctx, sess.RoomID, sess.ID(), "maximum messages reached")
if err != nil { if err != nil {
log.Err(err).Msg("Failed to delete fully used session") log.Err(err).Msg("Failed to delete fully used session")
return sess, plaintext, messageIndex, RatchetError return sess, plaintext, messageIndex, ErrRatchetError
} else { } else {
log.Info().Msg("Deleted fully used session") log.Info().Msg("Deleted fully used session")
} }
} else if ratchetCurrentIndex < ratchetTargetIndex && mach.RatchetKeysOnDecrypt { } else if ratchetCurrentIndex < ratchetTargetIndex && mach.RatchetKeysOnDecrypt {
if err = sess.RatchetTo(ratchetTargetIndex); err != nil { if err = sess.RatchetTo(ratchetTargetIndex); err != nil {
log.Err(err).Msg("Failed to ratchet session") log.Err(err).Msg("Failed to ratchet session")
return sess, plaintext, messageIndex, RatchetError return sess, plaintext, messageIndex, ErrRatchetError
} else if err = mach.CryptoStore.PutGroupSession(ctx, sess); err != nil { } else if err = mach.CryptoStore.PutGroupSession(ctx, sess); err != nil {
log.Err(err).Msg("Failed to store ratcheted session") log.Err(err).Msg("Failed to store ratcheted session")
return sess, plaintext, messageIndex, RatchetError return sess, plaintext, messageIndex, ErrRatchetError
} else { } else {
log.Info().Msg("Ratcheted session forward") log.Info().Msg("Ratcheted session forward")
} }
} else if didModify { } else if didModify {
if err = mach.CryptoStore.PutGroupSession(ctx, sess); err != nil { if err = mach.CryptoStore.PutGroupSession(ctx, sess); err != nil {
log.Err(err).Msg("Failed to store updated ratchet safety data") log.Err(err).Msg("Failed to store updated ratchet safety data")
return sess, plaintext, messageIndex, RatchetError return sess, plaintext, messageIndex, ErrRatchetError
} else { } else {
log.Debug().Msg("Ratchet safety data changed (ratchet state didn't change)") log.Debug().Msg("Ratchet safety data changed (ratchet state didn't change)")
} }

View file

@ -26,15 +26,27 @@ import (
) )
var ( var (
UnsupportedAlgorithm = errors.New("unsupported event encryption algorithm") ErrUnsupportedAlgorithm = errors.New("unsupported event encryption algorithm")
NotEncryptedForMe = errors.New("olm event doesn't contain ciphertext for this device") ErrNotEncryptedForMe = errors.New("olm event doesn't contain ciphertext for this device")
UnsupportedOlmMessageType = errors.New("unsupported olm message type") ErrUnsupportedOlmMessageType = errors.New("unsupported olm message type")
DecryptionFailedWithMatchingSession = errors.New("decryption failed with matching session") ErrDecryptionFailedWithMatchingSession = errors.New("decryption failed with matching session")
DecryptionFailedForNormalMessage = errors.New("decryption failed for normal message") ErrDecryptionFailedForNormalMessage = errors.New("decryption failed for normal message")
SenderMismatch = errors.New("mismatched sender in olm payload") ErrSenderMismatch = errors.New("mismatched sender in olm payload")
RecipientMismatch = errors.New("mismatched recipient in olm payload") ErrRecipientMismatch = errors.New("mismatched recipient in olm payload")
RecipientKeyMismatch = errors.New("mismatched recipient key in olm payload") ErrRecipientKeyMismatch = errors.New("mismatched recipient key in olm payload")
ErrDuplicateMessage = errors.New("duplicate olm message") ErrDuplicateMessage = errors.New("duplicate olm message")
)
// Deprecated: use variables prefixed with Err
var (
UnsupportedAlgorithm = ErrUnsupportedAlgorithm
NotEncryptedForMe = ErrNotEncryptedForMe
UnsupportedOlmMessageType = ErrUnsupportedOlmMessageType
DecryptionFailedWithMatchingSession = ErrDecryptionFailedWithMatchingSession
DecryptionFailedForNormalMessage = ErrDecryptionFailedForNormalMessage
SenderMismatch = ErrSenderMismatch
RecipientMismatch = ErrRecipientMismatch
RecipientKeyMismatch = ErrRecipientKeyMismatch
) )
// DecryptedOlmEvent represents an event that was decrypted from an event encrypted with the m.olm.v1.curve25519-aes-sha2 algorithm. // DecryptedOlmEvent represents an event that was decrypted from an event encrypted with the m.olm.v1.curve25519-aes-sha2 algorithm.
@ -56,13 +68,13 @@ type DecryptedOlmEvent struct {
func (mach *OlmMachine) decryptOlmEvent(ctx context.Context, evt *event.Event) (*DecryptedOlmEvent, error) { func (mach *OlmMachine) decryptOlmEvent(ctx context.Context, evt *event.Event) (*DecryptedOlmEvent, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent) content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
if !ok { if !ok {
return nil, IncorrectEncryptedContentType return nil, ErrIncorrectEncryptedContentType
} else if content.Algorithm != id.AlgorithmOlmV1 { } else if content.Algorithm != id.AlgorithmOlmV1 {
return nil, UnsupportedAlgorithm return nil, ErrUnsupportedAlgorithm
} }
ownContent, ok := content.OlmCiphertext[mach.account.IdentityKey()] ownContent, ok := content.OlmCiphertext[mach.account.IdentityKey()]
if !ok { if !ok {
return nil, NotEncryptedForMe return nil, ErrNotEncryptedForMe
} }
decrypted, err := mach.decryptAndParseOlmCiphertext(ctx, evt, content.SenderKey, ownContent.Type, ownContent.Body) decrypted, err := mach.decryptAndParseOlmCiphertext(ctx, evt, content.SenderKey, ownContent.Type, ownContent.Body)
if err != nil { if err != nil {
@ -78,7 +90,7 @@ type OlmEventKeys struct {
func (mach *OlmMachine) decryptAndParseOlmCiphertext(ctx context.Context, evt *event.Event, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) (*DecryptedOlmEvent, error) { func (mach *OlmMachine) decryptAndParseOlmCiphertext(ctx context.Context, evt *event.Event, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) (*DecryptedOlmEvent, error) {
if olmType != id.OlmMsgTypePreKey && olmType != id.OlmMsgTypeMsg { if olmType != id.OlmMsgTypePreKey && olmType != id.OlmMsgTypeMsg {
return nil, UnsupportedOlmMessageType return nil, ErrUnsupportedOlmMessageType
} }
log := mach.machOrContextLog(ctx).With(). log := mach.machOrContextLog(ctx).With().
@ -102,11 +114,11 @@ func (mach *OlmMachine) decryptAndParseOlmCiphertext(ctx context.Context, evt *e
} }
olmEvt.Type.Class = evt.Type.Class olmEvt.Type.Class = evt.Type.Class
if evt.Sender != olmEvt.Sender { if evt.Sender != olmEvt.Sender {
return nil, SenderMismatch return nil, ErrSenderMismatch
} else if mach.Client.UserID != olmEvt.Recipient { } else if mach.Client.UserID != olmEvt.Recipient {
return nil, RecipientMismatch return nil, ErrRecipientMismatch
} else if mach.account.SigningKey() != olmEvt.RecipientKeys.Ed25519 { } else if mach.account.SigningKey() != olmEvt.RecipientKeys.Ed25519 {
return nil, RecipientKeyMismatch return nil, ErrRecipientKeyMismatch
} }
if len(olmEvt.Content.VeryRaw) > 0 { if len(olmEvt.Content.VeryRaw) > 0 {
@ -151,7 +163,7 @@ func (mach *OlmMachine) tryDecryptOlmCiphertext(ctx context.Context, sender id.U
plaintext, err := mach.tryDecryptOlmCiphertextWithExistingSession(ctx, senderKey, olmType, ciphertext, ciphertextHash) plaintext, err := mach.tryDecryptOlmCiphertextWithExistingSession(ctx, senderKey, olmType, ciphertext, ciphertextHash)
if err != nil { if err != nil {
if err == DecryptionFailedWithMatchingSession { if err == ErrDecryptionFailedWithMatchingSession {
log.Warn().Msg("Found matching session, but decryption failed") log.Warn().Msg("Found matching session, but decryption failed")
go mach.unwedgeDevice(log, sender, senderKey) go mach.unwedgeDevice(log, sender, senderKey)
} }
@ -169,10 +181,10 @@ func (mach *OlmMachine) tryDecryptOlmCiphertext(ctx context.Context, sender id.U
// if it isn't one at this point in time anymore, so return early. // if it isn't one at this point in time anymore, so return early.
if olmType != id.OlmMsgTypePreKey { if olmType != id.OlmMsgTypePreKey {
go mach.unwedgeDevice(log, sender, senderKey) go mach.unwedgeDevice(log, sender, senderKey)
return nil, DecryptionFailedForNormalMessage return nil, ErrDecryptionFailedForNormalMessage
} }
accountBackup, err := mach.account.Internal.Pickle([]byte("tmp")) accountBackup, _ := mach.account.Internal.Pickle([]byte("tmp"))
log.Trace().Msg("Trying to create inbound session") log.Trace().Msg("Trying to create inbound session")
endTimeTrace = mach.timeTrace(ctx, "creating inbound olm session", time.Second) endTimeTrace = mach.timeTrace(ctx, "creating inbound olm session", time.Second)
session, err := mach.createInboundSession(ctx, senderKey, ciphertext) session, err := mach.createInboundSession(ctx, senderKey, ciphertext)
@ -302,7 +314,7 @@ func (mach *OlmMachine) tryDecryptOlmCiphertextWithExistingSession(
Str("session_description", session.Describe()). Str("session_description", session.Describe()).
Msg("Failed to decrypt olm message") Msg("Failed to decrypt olm message")
if olmType == id.OlmMsgTypePreKey { if olmType == id.OlmMsgTypePreKey {
return nil, DecryptionFailedWithMatchingSession return nil, ErrDecryptionFailedWithMatchingSession
} }
} else { } else {
endTimeTrace = mach.timeTrace(ctx, "updating session in database", time.Second) endTimeTrace = mach.timeTrace(ctx, "updating session in database", time.Second)
@ -345,7 +357,7 @@ func (mach *OlmMachine) unwedgeDevice(log zerolog.Logger, sender id.UserID, send
ctx := log.WithContext(mach.backgroundCtx) ctx := log.WithContext(mach.backgroundCtx)
mach.recentlyUnwedgedLock.Lock() mach.recentlyUnwedgedLock.Lock()
prevUnwedge, ok := mach.recentlyUnwedged[senderKey] prevUnwedge, ok := mach.recentlyUnwedged[senderKey]
delta := time.Now().Sub(prevUnwedge) delta := time.Since(prevUnwedge)
if ok && delta < MinUnwedgeInterval { if ok && delta < MinUnwedgeInterval {
log.Debug(). log.Debug().
Str("previous_recreation", delta.String()). Str("previous_recreation", delta.String()).

View file

@ -22,14 +22,23 @@ import (
) )
var ( var (
MismatchingDeviceID = errors.New("mismatching device ID in parameter and keys object") ErrMismatchingDeviceID = errors.New("mismatching device ID in parameter and keys object")
MismatchingUserID = errors.New("mismatching user ID in parameter and keys object") ErrMismatchingUserID = errors.New("mismatching user ID in parameter and keys object")
MismatchingSigningKey = errors.New("received update for device with different signing key") ErrMismatchingSigningKey = errors.New("received update for device with different signing key")
NoSigningKeyFound = errors.New("didn't find ed25519 signing key") ErrNoSigningKeyFound = errors.New("didn't find ed25519 signing key")
NoIdentityKeyFound = errors.New("didn't find curve25519 identity key") ErrNoIdentityKeyFound = errors.New("didn't find curve25519 identity key")
InvalidKeySignature = errors.New("invalid signature on device keys") ErrInvalidKeySignature = errors.New("invalid signature on device keys")
ErrUserNotTracked = errors.New("user is not tracked")
)
ErrUserNotTracked = errors.New("user is not tracked") // Deprecated: use variables prefixed with Err
var (
MismatchingDeviceID = ErrMismatchingDeviceID
MismatchingUserID = ErrMismatchingUserID
MismatchingSigningKey = ErrMismatchingSigningKey
NoSigningKeyFound = ErrNoSigningKeyFound
NoIdentityKeyFound = ErrNoIdentityKeyFound
InvalidKeySignature = ErrInvalidKeySignature
) )
func (mach *OlmMachine) LoadDevices(ctx context.Context, user id.UserID) (keys map[id.DeviceID]*id.Device) { func (mach *OlmMachine) LoadDevices(ctx context.Context, user id.UserID) (keys map[id.DeviceID]*id.Device) {
@ -312,28 +321,28 @@ func (mach *OlmMachine) OnDevicesChanged(ctx context.Context, userID id.UserID)
func (mach *OlmMachine) validateDevice(userID id.UserID, deviceID id.DeviceID, deviceKeys mautrix.DeviceKeys, existing *id.Device) (*id.Device, error) { func (mach *OlmMachine) validateDevice(userID id.UserID, deviceID id.DeviceID, deviceKeys mautrix.DeviceKeys, existing *id.Device) (*id.Device, error) {
if deviceID != deviceKeys.DeviceID { if deviceID != deviceKeys.DeviceID {
return nil, fmt.Errorf("%w (expected %s, got %s)", MismatchingDeviceID, deviceID, deviceKeys.DeviceID) return nil, fmt.Errorf("%w (expected %s, got %s)", ErrMismatchingDeviceID, deviceID, deviceKeys.DeviceID)
} else if userID != deviceKeys.UserID { } else if userID != deviceKeys.UserID {
return nil, fmt.Errorf("%w (expected %s, got %s)", MismatchingUserID, userID, deviceKeys.UserID) return nil, fmt.Errorf("%w (expected %s, got %s)", ErrMismatchingUserID, userID, deviceKeys.UserID)
} }
signingKey := deviceKeys.Keys.GetEd25519(deviceID) signingKey := deviceKeys.Keys.GetEd25519(deviceID)
identityKey := deviceKeys.Keys.GetCurve25519(deviceID) identityKey := deviceKeys.Keys.GetCurve25519(deviceID)
if signingKey == "" { if signingKey == "" {
return nil, NoSigningKeyFound return nil, ErrNoSigningKeyFound
} else if identityKey == "" { } else if identityKey == "" {
return nil, NoIdentityKeyFound return nil, ErrNoIdentityKeyFound
} }
if existing != nil && existing.SigningKey != signingKey { if existing != nil && existing.SigningKey != signingKey {
return existing, fmt.Errorf("%w (expected %s, got %s)", MismatchingSigningKey, existing.SigningKey, signingKey) return existing, fmt.Errorf("%w (expected %s, got %s)", ErrMismatchingSigningKey, existing.SigningKey, signingKey)
} }
ok, err := signatures.VerifySignatureJSON(deviceKeys, userID, deviceID.String(), signingKey) ok, err := signatures.VerifySignatureJSON(deviceKeys, userID, deviceID.String(), signingKey)
if err != nil { if err != nil {
return existing, fmt.Errorf("failed to verify signature: %w", err) return existing, fmt.Errorf("failed to verify signature: %w", err)
} else if !ok { } else if !ok {
return existing, InvalidKeySignature return existing, ErrInvalidKeySignature
} }
name, ok := deviceKeys.Unsigned["device_display_name"].(string) name, ok := deviceKeys.Unsigned["device_display_name"].(string)

View file

@ -25,7 +25,12 @@ import (
) )
var ( var (
NoGroupSession = errors.New("no group session created") ErrNoGroupSession = errors.New("no group session created")
)
// Deprecated: use variables prefixed with Err
var (
NoGroupSession = ErrNoGroupSession
) )
func getRawJSON[T any](content json.RawMessage, path ...string) *T { func getRawJSON[T any](content json.RawMessage, path ...string) *T {
@ -82,7 +87,7 @@ type rawMegolmEvent struct {
// IsShareError returns true if the error is caused by the lack of an outgoing megolm session and can be solved with OlmMachine.ShareGroupSession // IsShareError returns true if the error is caused by the lack of an outgoing megolm session and can be solved with OlmMachine.ShareGroupSession
func IsShareError(err error) bool { func IsShareError(err error) bool {
return err == SessionExpired || err == SessionNotShared || err == NoGroupSession return err == ErrSessionExpired || err == ErrSessionNotShared || err == ErrNoGroupSession
} }
func ParseMegolmMessageIndex(ciphertext []byte) (uint, error) { func ParseMegolmMessageIndex(ciphertext []byte) (uint, error) {
@ -120,7 +125,7 @@ func (mach *OlmMachine) EncryptMegolmEventWithStateKey(ctx context.Context, room
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get outbound group session: %w", err) return nil, fmt.Errorf("failed to get outbound group session: %w", err)
} else if session == nil { } else if session == nil {
return nil, NoGroupSession return nil, ErrNoGroupSession
} }
plaintext, err := json.Marshal(&rawMegolmEvent{ plaintext, err := json.Marshal(&rawMegolmEvent{
RoomID: roomID, RoomID: roomID,

View file

@ -334,7 +334,7 @@ func (a *Account) UnpickleLibOlm(buf []byte) error {
if err != nil { if err != nil {
return err return err
} else if pickledVersion != accountPickleVersionLibOLM && pickledVersion != 3 && pickledVersion != 2 { } else if pickledVersion != accountPickleVersionLibOLM && pickledVersion != 3 && pickledVersion != 2 {
return fmt.Errorf("unpickle account: %w (found version %d)", olm.ErrBadVersion, pickledVersion) return fmt.Errorf("unpickle account: %w (found version %d)", olm.ErrUnknownOlmPickleVersion, pickledVersion)
} else if err = a.IdKeys.Ed25519.UnpickleLibOlm(decoder); err != nil { // read the ed25519 key pair } else if err = a.IdKeys.Ed25519.UnpickleLibOlm(decoder); err != nil { // read the ed25519 key pair
return err return err
} else if err = a.IdKeys.Curve25519.UnpickleLibOlm(decoder); err != nil { // read curve25519 key pair } else if err = a.IdKeys.Curve25519.UnpickleLibOlm(decoder); err != nil { // read curve25519 key pair

View file

@ -4,7 +4,8 @@ import (
"encoding/base64" "encoding/base64"
) )
// Deprecated: base64.RawStdEncoding should be used directly // These methods should only be used for raw byte operations, never with string conversion
func Decode(input []byte) ([]byte, error) { func Decode(input []byte) ([]byte, error) {
decoded := make([]byte, base64.RawStdEncoding.DecodedLen(len(input))) decoded := make([]byte, base64.RawStdEncoding.DecodedLen(len(input)))
writtenBytes, err := base64.RawStdEncoding.Decode(decoded, input) writtenBytes, err := base64.RawStdEncoding.Decode(decoded, input)
@ -14,7 +15,6 @@ func Decode(input []byte) ([]byte, error) {
return decoded[:writtenBytes], nil return decoded[:writtenBytes], nil
} }
// Deprecated: base64.RawStdEncoding should be used directly
func Encode(input []byte) []byte { func Encode(input []byte) []byte {
encoded := make([]byte, base64.RawStdEncoding.EncodedLen(len(input))) encoded := make([]byte, base64.RawStdEncoding.EncodedLen(len(input)))
base64.RawStdEncoding.Encode(encoded, input) base64.RawStdEncoding.Encode(encoded, input)

View file

@ -50,7 +50,7 @@ func UnpickleAsJSON(object any, pickled, key []byte, pickleVersion byte) error {
} }
} }
if decrypted[0] != pickleVersion { if decrypted[0] != pickleVersion {
return fmt.Errorf("unpickle: %w", olm.ErrWrongPickleVersion) return fmt.Errorf("unpickle: %w", olm.ErrUnknownJSONPickleVersion)
} }
err = json.Unmarshal(decrypted[1:], object) err = json.Unmarshal(decrypted[1:], object)
if err != nil { if err != nil {

View file

@ -39,7 +39,7 @@ func (r *GroupMessage) Decode(input []byte) (err error) {
return return
} }
if r.Version != protocolVersion { if r.Version != protocolVersion {
return fmt.Errorf("GroupMessage.Decode: %w", olm.ErrWrongProtocolVersion) return fmt.Errorf("GroupMessage.Decode: %w (got %d, expected %d)", olm.ErrWrongProtocolVersion, r.Version, protocolVersion)
} }
for { for {

View file

@ -43,7 +43,7 @@ func (r *Message) Decode(input []byte) (err error) {
return return
} }
if r.Version != protocolVersion { if r.Version != protocolVersion {
return fmt.Errorf("Message.Decode: %w", olm.ErrWrongProtocolVersion) return fmt.Errorf("Message.Decode: %w (got %d, expected %d)", olm.ErrWrongProtocolVersion, r.Version, protocolVersion)
} }
for { for {

View file

@ -48,7 +48,7 @@ func (r *PreKeyMessage) Decode(input []byte) (err error) {
return return
} }
if r.Version != protocolVersion { if r.Version != protocolVersion {
return fmt.Errorf("PreKeyMessage.Decode: %w", olm.ErrWrongProtocolVersion) return fmt.Errorf("PreKeyMessage.Decode: %w (got %d, expected %d)", olm.ErrWrongProtocolVersion, r.Version, protocolVersion)
} }
for { for {

View file

@ -35,7 +35,7 @@ func (s *MegolmSessionExport) Decode(input []byte) error {
return fmt.Errorf("decrypt: %w", olm.ErrBadInput) return fmt.Errorf("decrypt: %w", olm.ErrBadInput)
} }
if input[0] != sessionExportVersion { if input[0] != sessionExportVersion {
return fmt.Errorf("decrypt: %w", olm.ErrBadVersion) return fmt.Errorf("decrypt: %w", olm.ErrUnknownOlmPickleVersion)
} }
s.Counter = binary.BigEndian.Uint32(input[1:5]) s.Counter = binary.BigEndian.Uint32(input[1:5])
copy(s.RatchetData[:], input[5:133]) copy(s.RatchetData[:], input[5:133])

View file

@ -42,7 +42,7 @@ func (s *MegolmSessionSharing) VerifyAndDecode(input []byte) error {
} }
s.PublicKey = publicKey s.PublicKey = publicKey
if input[0] != sessionSharingVersion { if input[0] != sessionSharingVersion {
return fmt.Errorf("verify: %w", olm.ErrBadVersion) return fmt.Errorf("verify: %w", olm.ErrUnknownOlmPickleVersion)
} }
s.Counter = binary.BigEndian.Uint32(input[1:5]) s.Counter = binary.BigEndian.Uint32(input[1:5])
copy(s.RatchetData[:], input[5:133]) copy(s.RatchetData[:], input[5:133])

View file

@ -103,7 +103,7 @@ func (a *Decryption) UnpickleLibOlm(unpickled []byte) error {
if pickledVersion == decryptionPickleVersionLibOlm { if pickledVersion == decryptionPickleVersionLibOlm {
return a.KeyPair.UnpickleLibOlm(decoder) return a.KeyPair.UnpickleLibOlm(decoder)
} else { } else {
return fmt.Errorf("unpickle olmSession: %w (found %d, expected %d)", olm.ErrBadVersion, pickledVersion, decryptionPickleVersionLibOlm) return fmt.Errorf("unpickle olmSession: %w (found %d, expected %d)", olm.ErrUnknownOlmPickleVersion, pickledVersion, decryptionPickleVersionLibOlm)
} }
} }

View file

@ -37,6 +37,9 @@ func (e Encryption) Encrypt(plaintext []byte, privateKey crypto.Curve25519Privat
return nil, nil, err return nil, nil, err
} }
cipher, err := aessha2.NewAESSHA2(sharedSecret, nil) cipher, err := aessha2.NewAESSHA2(sharedSecret, nil)
if err != nil {
return nil, nil, err
}
ciphertext, err = cipher.Encrypt(plaintext) ciphertext, err = cipher.Encrypt(plaintext)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View file

@ -142,7 +142,7 @@ func (r *Ratchet) Decrypt(input []byte) ([]byte, error) {
return nil, err return nil, err
} }
if message.Version != protocolVersion { if message.Version != protocolVersion {
return nil, fmt.Errorf("decrypt: %w", olm.ErrWrongProtocolVersion) return nil, fmt.Errorf("decrypt: %w (got %d, expected %d)", olm.ErrWrongProtocolVersion, message.Version, protocolVersion)
} }
if !message.HasCounter || len(message.RatchetKey) == 0 || len(message.Ciphertext) == 0 { if !message.HasCounter || len(message.RatchetKey) == 0 || len(message.Ciphertext) == 0 {
return nil, fmt.Errorf("decrypt: %w", olm.ErrBadMessageFormat) return nil, fmt.Errorf("decrypt: %w", olm.ErrBadMessageFormat)

View file

@ -99,7 +99,7 @@ func (o *MegolmInboundSession) getRatchet(messageIndex uint32) (*megolm.Ratchet,
} }
if (messageIndex - o.InitialRatchet.Counter) >= uint32(1<<31) { if (messageIndex - o.InitialRatchet.Counter) >= uint32(1<<31) {
// the counter is before our initial ratchet - we can't decode this // the counter is before our initial ratchet - we can't decode this
return nil, fmt.Errorf("decrypt: %w", olm.ErrRatchetNotAvailable) return nil, fmt.Errorf("decrypt: %w", olm.ErrUnknownMessageIndex)
} }
// otherwise, start from the initial ratchet. Take a copy so that we don't overwrite the initial ratchet // otherwise, start from the initial ratchet. Take a copy so that we don't overwrite the initial ratchet
copiedRatchet := o.InitialRatchet copiedRatchet := o.InitialRatchet
@ -126,7 +126,7 @@ func (o *MegolmInboundSession) Decrypt(ciphertext []byte) ([]byte, uint, error)
return nil, 0, err return nil, 0, err
} }
if msg.Version != protocolVersion { if msg.Version != protocolVersion {
return nil, 0, fmt.Errorf("decrypt: %w", olm.ErrWrongProtocolVersion) return nil, 0, fmt.Errorf("decrypt: %w (got %d, expected %d)", olm.ErrWrongProtocolVersion, msg.Version, protocolVersion)
} }
if msg.Ciphertext == nil || !msg.HasMessageIndex { if msg.Ciphertext == nil || !msg.HasMessageIndex {
return nil, 0, fmt.Errorf("decrypt: %w", olm.ErrBadMessageFormat) return nil, 0, fmt.Errorf("decrypt: %w", olm.ErrBadMessageFormat)
@ -206,7 +206,7 @@ func (o *MegolmInboundSession) UnpickleLibOlm(value []byte) error {
return err return err
} }
if pickledVersion != megolmInboundSessionPickleVersionLibOlm && pickledVersion != 1 { if pickledVersion != megolmInboundSessionPickleVersionLibOlm && pickledVersion != 1 {
return fmt.Errorf("unpickle MegolmInboundSession: %w (found version %d)", olm.ErrBadVersion, pickledVersion) return fmt.Errorf("unpickle MegolmInboundSession: %w (found version %d)", olm.ErrUnknownOlmPickleVersion, pickledVersion)
} }
if err = o.InitialRatchet.UnpickleLibOlm(decoder); err != nil { if err = o.InitialRatchet.UnpickleLibOlm(decoder); err != nil {

View file

@ -101,8 +101,10 @@ func (o *MegolmOutboundSession) Unpickle(pickled, key []byte) error {
func (o *MegolmOutboundSession) UnpickleLibOlm(buf []byte) error { func (o *MegolmOutboundSession) UnpickleLibOlm(buf []byte) error {
decoder := libolmpickle.NewDecoder(buf) decoder := libolmpickle.NewDecoder(buf)
pickledVersion, err := decoder.ReadUInt32() pickledVersion, err := decoder.ReadUInt32()
if pickledVersion != megolmOutboundSessionPickleVersionLibOlm { if err != nil {
return fmt.Errorf("unpickle MegolmInboundSession: %w (found version %d)", olm.ErrBadVersion, pickledVersion) return fmt.Errorf("unpickle MegolmOutboundSession: failed to read version: %w", err)
} else if pickledVersion != megolmOutboundSessionPickleVersionLibOlm {
return fmt.Errorf("unpickle MegolmInboundSession: %w (found version %d)", olm.ErrUnknownOlmPickleVersion, pickledVersion)
} }
if err = o.Ratchet.UnpickleLibOlm(decoder); err != nil { if err = o.Ratchet.UnpickleLibOlm(decoder); err != nil {
return err return err

View file

@ -168,11 +168,11 @@ func NewInboundOlmSession(identityKeyAlice *crypto.Curve25519PublicKey, received
msg := message.Message{} msg := message.Message{}
err = msg.Decode(oneTimeMsg.Message) err = msg.Decode(oneTimeMsg.Message)
if err != nil { if err != nil {
return nil, fmt.Errorf("Message decode: %w", err) return nil, fmt.Errorf("message decode: %w", err)
} }
if len(msg.RatchetKey) == 0 { if len(msg.RatchetKey) == 0 {
return nil, fmt.Errorf("Message missing ratchet key: %w", olm.ErrBadMessageFormat) return nil, fmt.Errorf("message missing ratchet key: %w", olm.ErrBadMessageFormat)
} }
//Init Ratchet //Init Ratchet
s.Ratchet.InitializeAsBob(secret, msg.RatchetKey) s.Ratchet.InitializeAsBob(secret, msg.RatchetKey)
@ -203,7 +203,7 @@ func (s *OlmSession) ID() id.SessionID {
copy(message[crypto.Curve25519PrivateKeyLength:], s.AliceBaseKey) copy(message[crypto.Curve25519PrivateKeyLength:], s.AliceBaseKey)
copy(message[2*crypto.Curve25519PrivateKeyLength:], s.BobOneTimeKey) copy(message[2*crypto.Curve25519PrivateKeyLength:], s.BobOneTimeKey)
hash := sha256.Sum256(message) hash := sha256.Sum256(message)
res := id.SessionID(goolmbase64.Encode(hash[:])) res := id.SessionID(base64.RawStdEncoding.EncodeToString(hash[:]))
return res return res
} }
@ -325,7 +325,7 @@ func (s *OlmSession) Decrypt(crypttext string, msgType id.OlmMsgType) ([]byte, e
if len(crypttext) == 0 { if len(crypttext) == 0 {
return nil, fmt.Errorf("decrypt: %w", olm.ErrEmptyInput) return nil, fmt.Errorf("decrypt: %w", olm.ErrEmptyInput)
} }
decodedCrypttext, err := goolmbase64.Decode([]byte(crypttext)) decodedCrypttext, err := base64.RawStdEncoding.DecodeString(crypttext)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -365,6 +365,9 @@ func (o *OlmSession) Unpickle(pickled, key []byte) error {
func (o *OlmSession) UnpickleLibOlm(buf []byte) error { func (o *OlmSession) UnpickleLibOlm(buf []byte) error {
decoder := libolmpickle.NewDecoder(buf) decoder := libolmpickle.NewDecoder(buf)
pickledVersion, err := decoder.ReadUInt32() pickledVersion, err := decoder.ReadUInt32()
if err != nil {
return fmt.Errorf("unpickle olmSession: failed to read version: %w", err)
}
var includesChainIndex bool var includesChainIndex bool
switch pickledVersion { switch pickledVersion {
@ -373,7 +376,7 @@ func (o *OlmSession) UnpickleLibOlm(buf []byte) error {
case uint32(0x80000001): case uint32(0x80000001):
includesChainIndex = true includesChainIndex = true
default: default:
return fmt.Errorf("unpickle olmSession: %w (found version %d)", olm.ErrBadVersion, pickledVersion) return fmt.Errorf("unpickle olmSession: %w (found version %d)", olm.ErrUnknownOlmPickleVersion, pickledVersion)
} }
if o.ReceivedMessage, err = decoder.ReadBool(); err != nil { if o.ReceivedMessage, err = decoder.ReadBool(); err != nil {

View file

@ -14,7 +14,7 @@ func Register() {
// Inbound Session // Inbound Session
olm.InitInboundGroupSessionFromPickled = func(pickled, key []byte) (olm.InboundGroupSession, error) { olm.InitInboundGroupSessionFromPickled = func(pickled, key []byte) (olm.InboundGroupSession, error) {
if len(pickled) == 0 { if len(pickled) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
if len(key) == 0 { if len(key) == 0 {
key = []byte(" ") key = []byte(" ")
@ -23,13 +23,13 @@ func Register() {
} }
olm.InitNewInboundGroupSession = func(sessionKey []byte) (olm.InboundGroupSession, error) { olm.InitNewInboundGroupSession = func(sessionKey []byte) (olm.InboundGroupSession, error) {
if len(sessionKey) == 0 { if len(sessionKey) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
return NewMegolmInboundSession(sessionKey) return NewMegolmInboundSession(sessionKey)
} }
olm.InitInboundGroupSessionImport = func(sessionKey []byte) (olm.InboundGroupSession, error) { olm.InitInboundGroupSessionImport = func(sessionKey []byte) (olm.InboundGroupSession, error) {
if len(sessionKey) == 0 { if len(sessionKey) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
return NewMegolmInboundSessionFromExport(sessionKey) return NewMegolmInboundSessionFromExport(sessionKey)
} }
@ -40,7 +40,7 @@ func Register() {
// Outbound Session // Outbound Session
olm.InitNewOutboundGroupSessionFromPickled = func(pickled, key []byte) (olm.OutboundGroupSession, error) { olm.InitNewOutboundGroupSessionFromPickled = func(pickled, key []byte) (olm.OutboundGroupSession, error) {
if len(pickled) == 0 { if len(pickled) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
lenKey := len(key) lenKey := len(key)
if lenKey == 0 { if lenKey == 0 {

View file

@ -56,11 +56,12 @@ func (mach *OlmMachine) GetAndVerifyLatestKeyBackupVersion(ctx context.Context,
// ...by deriving the public key from a private key that it obtained from a trusted source. Trusted sources for the private // ...by deriving the public key from a private key that it obtained from a trusted source. Trusted sources for the private
// key include the user entering the key, retrieving the key stored in secret storage, or obtaining the key via secret sharing // key include the user entering the key, retrieving the key stored in secret storage, or obtaining the key via secret sharing
// from a verified device belonging to the same user." // from a verified device belonging to the same user."
megolmBackupDerivedPublicKey := id.Ed25519(base64.RawStdEncoding.EncodeToString(megolmBackupKey.PublicKey().Bytes())) if megolmBackupKey != nil {
if megolmBackupKey != nil && versionInfo.AuthData.PublicKey == megolmBackupDerivedPublicKey { megolmBackupDerivedPublicKey := id.Ed25519(base64.RawStdEncoding.EncodeToString(megolmBackupKey.PublicKey().Bytes()))
log.Debug().Msg("key backup is trusted based on derived public key") if versionInfo.AuthData.PublicKey == megolmBackupDerivedPublicKey {
return versionInfo, nil log.Debug().Msg("Key backup is trusted based on derived public key")
} else { return versionInfo, nil
}
log.Debug(). log.Debug().
Stringer("expected_key", megolmBackupDerivedPublicKey). Stringer("expected_key", megolmBackupDerivedPublicKey).
Stringer("actual_key", versionInfo.AuthData.PublicKey). Stringer("actual_key", versionInfo.AuthData.PublicKey).

View file

@ -33,7 +33,7 @@ var _ olm.Account = (*Account)(nil)
// "INVALID_BASE64". // "INVALID_BASE64".
func AccountFromPickled(pickled, key []byte) (*Account, error) { func AccountFromPickled(pickled, key []byte) (*Account, error) {
if len(pickled) == 0 { if len(pickled) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
a := NewBlankAccount() a := NewBlankAccount()
return a, a.Unpickle(pickled, key) return a, a.Unpickle(pickled, key)
@ -53,7 +53,7 @@ func NewAccount() (*Account, error) {
random := make([]byte, a.createRandomLen()+1) random := make([]byte, a.createRandomLen()+1)
_, err := rand.Read(random) _, err := rand.Read(random)
if err != nil { if err != nil {
panic(olm.NotEnoughGoRandom) panic(olm.ErrNotEnoughGoRandom)
} }
ret := C.olm_create_account( ret := C.olm_create_account(
(*C.OlmAccount)(a.int), (*C.OlmAccount)(a.int),
@ -128,7 +128,7 @@ func (a *Account) genOneTimeKeysRandomLen(num uint) uint {
// supplied key. // supplied key.
func (a *Account) Pickle(key []byte) ([]byte, error) { func (a *Account) Pickle(key []byte) ([]byte, error) {
if len(key) == 0 { if len(key) == 0 {
return nil, olm.NoKeyProvided return nil, olm.ErrNoKeyProvided
} }
pickled := make([]byte, a.pickleLen()) pickled := make([]byte, a.pickleLen())
r := C.olm_pickle_account( r := C.olm_pickle_account(
@ -145,7 +145,7 @@ func (a *Account) Pickle(key []byte) ([]byte, error) {
func (a *Account) Unpickle(pickled, key []byte) error { func (a *Account) Unpickle(pickled, key []byte) error {
if len(key) == 0 { if len(key) == 0 {
return olm.NoKeyProvided return olm.ErrNoKeyProvided
} }
r := C.olm_unpickle_account( r := C.olm_unpickle_account(
(*C.OlmAccount)(a.int), (*C.OlmAccount)(a.int),
@ -198,7 +198,7 @@ func (a *Account) MarshalJSON() ([]byte, error) {
// Deprecated // Deprecated
func (a *Account) UnmarshalJSON(data []byte) error { func (a *Account) UnmarshalJSON(data []byte) error {
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' { if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
return olm.InputNotJSONString return olm.ErrInputNotJSONString
} }
if a.int == nil { if a.int == nil {
*a = *NewBlankAccount() *a = *NewBlankAccount()
@ -235,7 +235,7 @@ func (a *Account) IdentityKeys() (id.Ed25519, id.Curve25519, error) {
// Account. // Account.
func (a *Account) Sign(message []byte) ([]byte, error) { func (a *Account) Sign(message []byte) ([]byte, error) {
if len(message) == 0 { if len(message) == 0 {
panic(olm.EmptyInput) panic(olm.ErrEmptyInput)
} }
signature := make([]byte, a.signatureLen()) signature := make([]byte, a.signatureLen())
r := C.olm_account_sign( r := C.olm_account_sign(
@ -299,7 +299,7 @@ func (a *Account) GenOneTimeKeys(num uint) error {
random := make([]byte, a.genOneTimeKeysRandomLen(num)+1) random := make([]byte, a.genOneTimeKeysRandomLen(num)+1)
_, err := rand.Read(random) _, err := rand.Read(random)
if err != nil { if err != nil {
return olm.NotEnoughGoRandom return olm.ErrNotEnoughGoRandom
} }
r := C.olm_account_generate_one_time_keys( r := C.olm_account_generate_one_time_keys(
(*C.OlmAccount)(a.int), (*C.OlmAccount)(a.int),
@ -319,13 +319,13 @@ func (a *Account) GenOneTimeKeys(num uint) error {
// keys couldn't be decoded as base64 then the error will be "INVALID_BASE64" // keys couldn't be decoded as base64 then the error will be "INVALID_BASE64"
func (a *Account) NewOutboundSession(theirIdentityKey, theirOneTimeKey id.Curve25519) (olm.Session, error) { func (a *Account) NewOutboundSession(theirIdentityKey, theirOneTimeKey id.Curve25519) (olm.Session, error) {
if len(theirIdentityKey) == 0 || len(theirOneTimeKey) == 0 { if len(theirIdentityKey) == 0 || len(theirOneTimeKey) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
s := NewBlankSession() s := NewBlankSession()
random := make([]byte, s.createOutboundRandomLen()+1) random := make([]byte, s.createOutboundRandomLen()+1)
_, err := rand.Read(random) _, err := rand.Read(random)
if err != nil { if err != nil {
panic(olm.NotEnoughGoRandom) panic(olm.ErrNotEnoughGoRandom)
} }
theirIdentityKeyCopy := []byte(theirIdentityKey) theirIdentityKeyCopy := []byte(theirIdentityKey)
theirOneTimeKeyCopy := []byte(theirOneTimeKey) theirOneTimeKeyCopy := []byte(theirOneTimeKey)
@ -357,7 +357,7 @@ func (a *Account) NewOutboundSession(theirIdentityKey, theirOneTimeKey id.Curve2
// time key then the error will be "BAD_MESSAGE_KEY_ID". // time key then the error will be "BAD_MESSAGE_KEY_ID".
func (a *Account) NewInboundSession(oneTimeKeyMsg string) (olm.Session, error) { func (a *Account) NewInboundSession(oneTimeKeyMsg string) (olm.Session, error) {
if len(oneTimeKeyMsg) == 0 { if len(oneTimeKeyMsg) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
s := NewBlankSession() s := NewBlankSession()
oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg) oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg)
@ -383,7 +383,7 @@ func (a *Account) NewInboundSession(oneTimeKeyMsg string) (olm.Session, error) {
// time key then the error will be "BAD_MESSAGE_KEY_ID". // time key then the error will be "BAD_MESSAGE_KEY_ID".
func (a *Account) NewInboundSessionFrom(theirIdentityKey *id.Curve25519, oneTimeKeyMsg string) (olm.Session, error) { func (a *Account) NewInboundSessionFrom(theirIdentityKey *id.Curve25519, oneTimeKeyMsg string) (olm.Session, error) {
if theirIdentityKey == nil || len(oneTimeKeyMsg) == 0 { if theirIdentityKey == nil || len(oneTimeKeyMsg) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
theirIdentityKeyCopy := []byte(*theirIdentityKey) theirIdentityKeyCopy := []byte(*theirIdentityKey)
oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg) oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg)

View file

@ -11,21 +11,21 @@ import (
) )
var errorMap = map[string]error{ var errorMap = map[string]error{
"NOT_ENOUGH_RANDOM": olm.NotEnoughRandom, "NOT_ENOUGH_RANDOM": olm.ErrLibolmNotEnoughRandom,
"OUTPUT_BUFFER_TOO_SMALL": olm.OutputBufferTooSmall, "OUTPUT_BUFFER_TOO_SMALL": olm.ErrLibolmOutputBufferTooSmall,
"BAD_MESSAGE_VERSION": olm.BadMessageVersion, "BAD_MESSAGE_VERSION": olm.ErrWrongProtocolVersion,
"BAD_MESSAGE_FORMAT": olm.BadMessageFormat, "BAD_MESSAGE_FORMAT": olm.ErrBadMessageFormat,
"BAD_MESSAGE_MAC": olm.BadMessageMAC, "BAD_MESSAGE_MAC": olm.ErrBadMAC,
"BAD_MESSAGE_KEY_ID": olm.BadMessageKeyID, "BAD_MESSAGE_KEY_ID": olm.ErrBadMessageKeyID,
"INVALID_BASE64": olm.InvalidBase64, "INVALID_BASE64": olm.ErrLibolmInvalidBase64,
"BAD_ACCOUNT_KEY": olm.BadAccountKey, "BAD_ACCOUNT_KEY": olm.ErrLibolmBadAccountKey,
"UNKNOWN_PICKLE_VERSION": olm.UnknownPickleVersion, "UNKNOWN_PICKLE_VERSION": olm.ErrUnknownOlmPickleVersion,
"CORRUPTED_PICKLE": olm.CorruptedPickle, "CORRUPTED_PICKLE": olm.ErrLibolmCorruptedPickle,
"BAD_SESSION_KEY": olm.BadSessionKey, "BAD_SESSION_KEY": olm.ErrLibolmBadSessionKey,
"UNKNOWN_MESSAGE_INDEX": olm.UnknownMessageIndex, "UNKNOWN_MESSAGE_INDEX": olm.ErrUnknownMessageIndex,
"BAD_LEGACY_ACCOUNT_PICKLE": olm.BadLegacyAccountPickle, "BAD_LEGACY_ACCOUNT_PICKLE": olm.ErrLibolmBadLegacyAccountPickle,
"BAD_SIGNATURE": olm.BadSignature, "BAD_SIGNATURE": olm.ErrBadSignature,
"INPUT_BUFFER_TOO_SMALL": olm.InputBufferTooSmall, "INPUT_BUFFER_TOO_SMALL": olm.ErrInputToSmall,
} }
func convertError(errCode string) error { func convertError(errCode string) error {

View file

@ -31,7 +31,7 @@ var _ olm.InboundGroupSession = (*InboundGroupSession)(nil)
// base64 couldn't be decoded then the error will be "INVALID_BASE64". // base64 couldn't be decoded then the error will be "INVALID_BASE64".
func InboundGroupSessionFromPickled(pickled, key []byte) (*InboundGroupSession, error) { func InboundGroupSessionFromPickled(pickled, key []byte) (*InboundGroupSession, error) {
if len(pickled) == 0 { if len(pickled) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
lenKey := len(key) lenKey := len(key)
if lenKey == 0 { if lenKey == 0 {
@ -48,7 +48,7 @@ func InboundGroupSessionFromPickled(pickled, key []byte) (*InboundGroupSession,
// "OLM_BAD_SESSION_KEY". // "OLM_BAD_SESSION_KEY".
func NewInboundGroupSession(sessionKey []byte) (*InboundGroupSession, error) { func NewInboundGroupSession(sessionKey []byte) (*InboundGroupSession, error) {
if len(sessionKey) == 0 { if len(sessionKey) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
s := NewBlankInboundGroupSession() s := NewBlankInboundGroupSession()
r := C.olm_init_inbound_group_session( r := C.olm_init_inbound_group_session(
@ -69,7 +69,7 @@ func NewInboundGroupSession(sessionKey []byte) (*InboundGroupSession, error) {
// error will be "OLM_BAD_SESSION_KEY". // error will be "OLM_BAD_SESSION_KEY".
func InboundGroupSessionImport(sessionKey []byte) (*InboundGroupSession, error) { func InboundGroupSessionImport(sessionKey []byte) (*InboundGroupSession, error) {
if len(sessionKey) == 0 { if len(sessionKey) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
s := NewBlankInboundGroupSession() s := NewBlankInboundGroupSession()
r := C.olm_import_inbound_group_session( r := C.olm_import_inbound_group_session(
@ -124,7 +124,7 @@ func (s *InboundGroupSession) pickleLen() uint {
// InboundGroupSession using the supplied key. // InboundGroupSession using the supplied key.
func (s *InboundGroupSession) Pickle(key []byte) ([]byte, error) { func (s *InboundGroupSession) Pickle(key []byte) ([]byte, error) {
if len(key) == 0 { if len(key) == 0 {
return nil, olm.NoKeyProvided return nil, olm.ErrNoKeyProvided
} }
pickled := make([]byte, s.pickleLen()) pickled := make([]byte, s.pickleLen())
r := C.olm_pickle_inbound_group_session( r := C.olm_pickle_inbound_group_session(
@ -143,9 +143,9 @@ func (s *InboundGroupSession) Pickle(key []byte) ([]byte, error) {
func (s *InboundGroupSession) Unpickle(pickled, key []byte) error { func (s *InboundGroupSession) Unpickle(pickled, key []byte) error {
if len(key) == 0 { if len(key) == 0 {
return olm.NoKeyProvided return olm.ErrNoKeyProvided
} else if len(pickled) == 0 { } else if len(pickled) == 0 {
return olm.EmptyInput return olm.ErrEmptyInput
} }
r := C.olm_unpickle_inbound_group_session( r := C.olm_unpickle_inbound_group_session(
(*C.OlmInboundGroupSession)(s.int), (*C.OlmInboundGroupSession)(s.int),
@ -200,7 +200,7 @@ func (s *InboundGroupSession) MarshalJSON() ([]byte, error) {
// Deprecated // Deprecated
func (s *InboundGroupSession) UnmarshalJSON(data []byte) error { func (s *InboundGroupSession) UnmarshalJSON(data []byte) error {
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' { if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
return olm.InputNotJSONString return olm.ErrInputNotJSONString
} }
if s == nil || s.int == nil { if s == nil || s.int == nil {
*s = *NewBlankInboundGroupSession() *s = *NewBlankInboundGroupSession()
@ -217,7 +217,7 @@ func (s *InboundGroupSession) UnmarshalJSON(data []byte) error {
// will be "BAD_MESSAGE_FORMAT". // will be "BAD_MESSAGE_FORMAT".
func (s *InboundGroupSession) decryptMaxPlaintextLen(message []byte) (uint, error) { func (s *InboundGroupSession) decryptMaxPlaintextLen(message []byte) (uint, error) {
if len(message) == 0 { if len(message) == 0 {
return 0, olm.EmptyInput return 0, olm.ErrEmptyInput
} }
// olm_group_decrypt_max_plaintext_length destroys the input, so we have to clone it // olm_group_decrypt_max_plaintext_length destroys the input, so we have to clone it
messageCopy := bytes.Clone(message) messageCopy := bytes.Clone(message)
@ -244,7 +244,7 @@ func (s *InboundGroupSession) decryptMaxPlaintextLen(message []byte) (uint, erro
// was shared with us) the error will be "OLM_UNKNOWN_MESSAGE_INDEX". // was shared with us) the error will be "OLM_UNKNOWN_MESSAGE_INDEX".
func (s *InboundGroupSession) Decrypt(message []byte) ([]byte, uint, error) { func (s *InboundGroupSession) Decrypt(message []byte) ([]byte, uint, error) {
if len(message) == 0 { if len(message) == 0 {
return nil, 0, olm.EmptyInput return nil, 0, olm.ErrEmptyInput
} }
decryptMaxPlaintextLen, err := s.decryptMaxPlaintextLen(message) decryptMaxPlaintextLen, err := s.decryptMaxPlaintextLen(message)
if err != nil { if err != nil {

View file

@ -84,7 +84,7 @@ func (s *OutboundGroupSession) pickleLen() uint {
// OutboundGroupSession using the supplied key. // OutboundGroupSession using the supplied key.
func (s *OutboundGroupSession) Pickle(key []byte) ([]byte, error) { func (s *OutboundGroupSession) Pickle(key []byte) ([]byte, error) {
if len(key) == 0 { if len(key) == 0 {
return nil, olm.NoKeyProvided return nil, olm.ErrNoKeyProvided
} }
pickled := make([]byte, s.pickleLen()) pickled := make([]byte, s.pickleLen())
r := C.olm_pickle_outbound_group_session( r := C.olm_pickle_outbound_group_session(
@ -103,7 +103,7 @@ func (s *OutboundGroupSession) Pickle(key []byte) ([]byte, error) {
func (s *OutboundGroupSession) Unpickle(pickled, key []byte) error { func (s *OutboundGroupSession) Unpickle(pickled, key []byte) error {
if len(key) == 0 { if len(key) == 0 {
return olm.NoKeyProvided return olm.ErrNoKeyProvided
} }
r := C.olm_unpickle_outbound_group_session( r := C.olm_unpickle_outbound_group_session(
(*C.OlmOutboundGroupSession)(s.int), (*C.OlmOutboundGroupSession)(s.int),
@ -159,7 +159,7 @@ func (s *OutboundGroupSession) MarshalJSON() ([]byte, error) {
// Deprecated // Deprecated
func (s *OutboundGroupSession) UnmarshalJSON(data []byte) error { func (s *OutboundGroupSession) UnmarshalJSON(data []byte) error {
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' { if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
return olm.InputNotJSONString return olm.ErrInputNotJSONString
} }
if s == nil || s.int == nil { if s == nil || s.int == nil {
*s = *NewBlankOutboundGroupSession() *s = *NewBlankOutboundGroupSession()
@ -183,7 +183,7 @@ func (s *OutboundGroupSession) encryptMsgLen(plainTextLen int) uint {
// as base64. // as base64.
func (s *OutboundGroupSession) Encrypt(plaintext []byte) ([]byte, error) { func (s *OutboundGroupSession) Encrypt(plaintext []byte) ([]byte, error) {
if len(plaintext) == 0 { if len(plaintext) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
message := make([]byte, s.encryptMsgLen(len(plaintext))) message := make([]byte, s.encryptMsgLen(len(plaintext)))
r := C.olm_group_encrypt( r := C.olm_group_encrypt(

View file

@ -86,7 +86,7 @@ func NewPKSigning() (*PKSigning, error) {
seed := make([]byte, pkSigningSeedLength()) seed := make([]byte, pkSigningSeedLength())
_, err := rand.Read(seed) _, err := rand.Read(seed)
if err != nil { if err != nil {
panic(olm.NotEnoughGoRandom) panic(olm.ErrNotEnoughGoRandom)
} }
pk, err := NewPKSigningFromSeed(seed) pk, err := NewPKSigningFromSeed(seed)
return pk, err return pk, err

View file

@ -65,7 +65,7 @@ func Register() {
olm.InitNewOutboundGroupSessionFromPickled = func(pickled, key []byte) (olm.OutboundGroupSession, error) { olm.InitNewOutboundGroupSessionFromPickled = func(pickled, key []byte) (olm.OutboundGroupSession, error) {
if len(pickled) == 0 { if len(pickled) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
s := NewBlankOutboundGroupSession() s := NewBlankOutboundGroupSession()
return s, s.Unpickle(pickled, key) return s, s.Unpickle(pickled, key)

View file

@ -51,7 +51,7 @@ func sessionSize() uint {
// "INVALID_BASE64". // "INVALID_BASE64".
func SessionFromPickled(pickled, key []byte) (*Session, error) { func SessionFromPickled(pickled, key []byte) (*Session, error) {
if len(pickled) == 0 { if len(pickled) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
s := NewBlankSession() s := NewBlankSession()
return s, s.Unpickle(pickled, key) return s, s.Unpickle(pickled, key)
@ -118,7 +118,7 @@ func (s *Session) encryptMsgLen(plainTextLen int) uint {
// will be "BAD_MESSAGE_FORMAT". // will be "BAD_MESSAGE_FORMAT".
func (s *Session) decryptMaxPlaintextLen(message string, msgType id.OlmMsgType) (uint, error) { func (s *Session) decryptMaxPlaintextLen(message string, msgType id.OlmMsgType) (uint, error) {
if len(message) == 0 { if len(message) == 0 {
return 0, olm.EmptyInput return 0, olm.ErrEmptyInput
} }
messageCopy := []byte(message) messageCopy := []byte(message)
r := C.olm_decrypt_max_plaintext_length( r := C.olm_decrypt_max_plaintext_length(
@ -138,7 +138,7 @@ func (s *Session) decryptMaxPlaintextLen(message string, msgType id.OlmMsgType)
// supplied key. // supplied key.
func (s *Session) Pickle(key []byte) ([]byte, error) { func (s *Session) Pickle(key []byte) ([]byte, error) {
if len(key) == 0 { if len(key) == 0 {
return nil, olm.NoKeyProvided return nil, olm.ErrNoKeyProvided
} }
pickled := make([]byte, s.pickleLen()) pickled := make([]byte, s.pickleLen())
r := C.olm_pickle_session( r := C.olm_pickle_session(
@ -158,7 +158,7 @@ func (s *Session) Pickle(key []byte) ([]byte, error) {
// provided key. This function mutates the input pickled data slice. // provided key. This function mutates the input pickled data slice.
func (s *Session) Unpickle(pickled, key []byte) error { func (s *Session) Unpickle(pickled, key []byte) error {
if len(key) == 0 { if len(key) == 0 {
return olm.NoKeyProvided return olm.ErrNoKeyProvided
} }
r := C.olm_unpickle_session( r := C.olm_unpickle_session(
(*C.OlmSession)(s.int), (*C.OlmSession)(s.int),
@ -213,7 +213,7 @@ func (s *Session) MarshalJSON() ([]byte, error) {
// Deprecated // Deprecated
func (s *Session) UnmarshalJSON(data []byte) error { func (s *Session) UnmarshalJSON(data []byte) error {
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' { if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
return olm.InputNotJSONString return olm.ErrInputNotJSONString
} }
if s == nil || s.int == nil { if s == nil || s.int == nil {
*s = *NewBlankSession() *s = *NewBlankSession()
@ -256,7 +256,7 @@ func (s *Session) HasReceivedMessage() bool {
// decoded then then the error will be "BAD_MESSAGE_FORMAT". // decoded then then the error will be "BAD_MESSAGE_FORMAT".
func (s *Session) MatchesInboundSession(oneTimeKeyMsg string) (bool, error) { func (s *Session) MatchesInboundSession(oneTimeKeyMsg string) (bool, error) {
if len(oneTimeKeyMsg) == 0 { if len(oneTimeKeyMsg) == 0 {
return false, olm.EmptyInput return false, olm.ErrEmptyInput
} }
oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg) oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg)
r := C.olm_matches_inbound_session( r := C.olm_matches_inbound_session(
@ -284,7 +284,7 @@ func (s *Session) MatchesInboundSession(oneTimeKeyMsg string) (bool, error) {
// decoded then then the error will be "BAD_MESSAGE_FORMAT". // decoded then then the error will be "BAD_MESSAGE_FORMAT".
func (s *Session) MatchesInboundSessionFrom(theirIdentityKey, oneTimeKeyMsg string) (bool, error) { func (s *Session) MatchesInboundSessionFrom(theirIdentityKey, oneTimeKeyMsg string) (bool, error) {
if len(theirIdentityKey) == 0 || len(oneTimeKeyMsg) == 0 { if len(theirIdentityKey) == 0 || len(oneTimeKeyMsg) == 0 {
return false, olm.EmptyInput return false, olm.ErrEmptyInput
} }
theirIdentityKeyCopy := []byte(theirIdentityKey) theirIdentityKeyCopy := []byte(theirIdentityKey)
oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg) oneTimeKeyMsgCopy := []byte(oneTimeKeyMsg)
@ -325,14 +325,14 @@ func (s *Session) EncryptMsgType() id.OlmMsgType {
// as base64. // as base64.
func (s *Session) Encrypt(plaintext []byte) (id.OlmMsgType, []byte, error) { func (s *Session) Encrypt(plaintext []byte) (id.OlmMsgType, []byte, error) {
if len(plaintext) == 0 { if len(plaintext) == 0 {
return 0, nil, olm.EmptyInput return 0, nil, olm.ErrEmptyInput
} }
// Make the slice be at least length 1 // Make the slice be at least length 1
random := make([]byte, s.encryptRandomLen()+1) random := make([]byte, s.encryptRandomLen()+1)
_, err := rand.Read(random) _, err := rand.Read(random)
if err != nil { if err != nil {
// TODO can we just return err here? // TODO can we just return err here?
return 0, nil, olm.NotEnoughGoRandom return 0, nil, olm.ErrNotEnoughGoRandom
} }
messageType := s.EncryptMsgType() messageType := s.EncryptMsgType()
message := make([]byte, s.encryptMsgLen(len(plaintext))) message := make([]byte, s.encryptMsgLen(len(plaintext)))
@ -362,7 +362,7 @@ func (s *Session) Encrypt(plaintext []byte) (id.OlmMsgType, []byte, error) {
// "BAD_MESSAGE_MAC". // "BAD_MESSAGE_MAC".
func (s *Session) Decrypt(message string, msgType id.OlmMsgType) ([]byte, error) { func (s *Session) Decrypt(message string, msgType id.OlmMsgType) ([]byte, error) {
if len(message) == 0 { if len(message) == 0 {
return nil, olm.EmptyInput return nil, olm.ErrEmptyInput
} }
decryptMaxPlaintextLen, err := s.decryptMaxPlaintextLen(message, msgType) decryptMaxPlaintextLen, err := s.decryptMaxPlaintextLen(message, msgType)
if err != nil { if err != nil {

View file

@ -205,7 +205,7 @@ func (mach *OlmMachine) FlushStore(ctx context.Context) error {
func (mach *OlmMachine) timeTrace(ctx context.Context, thing string, expectedDuration time.Duration) func() { func (mach *OlmMachine) timeTrace(ctx context.Context, thing string, expectedDuration time.Duration) func() {
start := time.Now() start := time.Now()
return func() { return func() {
duration := time.Now().Sub(start) duration := time.Since(start)
if duration > expectedDuration { if duration > expectedDuration {
zerolog.Ctx(ctx).Warn(). zerolog.Ctx(ctx).Warn().
Str("action", thing). Str("action", thing).

View file

@ -10,50 +10,67 @@ import "errors"
// Those are the most common used errors // Those are the most common used errors
var ( var (
ErrBadSignature = errors.New("bad signature") ErrBadSignature = errors.New("bad signature")
ErrBadMAC = errors.New("bad mac") ErrBadMAC = errors.New("the message couldn't be decrypted (bad mac)")
ErrBadMessageFormat = errors.New("bad message format") ErrBadMessageFormat = errors.New("the message couldn't be decoded")
ErrBadVerification = errors.New("bad verification") ErrBadVerification = errors.New("bad verification")
ErrWrongProtocolVersion = errors.New("wrong protocol version") ErrWrongProtocolVersion = errors.New("wrong protocol version")
ErrEmptyInput = errors.New("empty input") ErrEmptyInput = errors.New("empty input")
ErrNoKeyProvided = errors.New("no key") ErrNoKeyProvided = errors.New("no key provided")
ErrBadMessageKeyID = errors.New("bad message key id") ErrBadMessageKeyID = errors.New("the message references an unknown key ID")
ErrRatchetNotAvailable = errors.New("ratchet not available: attempt to decode a message whose index is earlier than our earliest known session key") ErrUnknownMessageIndex = errors.New("attempt to decode a message whose index is earlier than our earliest known session key")
ErrMsgIndexTooHigh = errors.New("message index too high") ErrMsgIndexTooHigh = errors.New("message index too high")
ErrProtocolViolation = errors.New("not protocol message order") ErrProtocolViolation = errors.New("not protocol message order")
ErrMessageKeyNotFound = errors.New("message key not found") ErrMessageKeyNotFound = errors.New("message key not found")
ErrChainTooHigh = errors.New("chain index too high") ErrChainTooHigh = errors.New("chain index too high")
ErrBadInput = errors.New("bad input") ErrBadInput = errors.New("bad input")
ErrBadVersion = errors.New("wrong version") ErrUnknownOlmPickleVersion = errors.New("unknown olm pickle version")
ErrWrongPickleVersion = errors.New("wrong pickle version") ErrUnknownJSONPickleVersion = errors.New("unknown JSON pickle version")
ErrInputToSmall = errors.New("input too small (truncated?)") ErrInputToSmall = errors.New("input too small (truncated?)")
ErrOverflow = errors.New("overflow")
) )
// Error codes from go-olm // Error codes from go-olm
var ( var (
EmptyInput = errors.New("empty input") ErrNotEnoughGoRandom = errors.New("couldn't get enough randomness from crypto/rand")
NoKeyProvided = errors.New("no pickle key provided") ErrInputNotJSONString = errors.New("input doesn't look like a JSON string")
NotEnoughGoRandom = errors.New("couldn't get enough randomness from crypto/rand")
SignatureNotFound = errors.New("input JSON doesn't contain signature from specified device")
InputNotJSONString = errors.New("input doesn't look like a JSON string")
) )
// Error codes from olm code // Error codes from olm code
var ( var (
NotEnoughRandom = errors.New("not enough entropy was supplied") ErrLibolmInvalidBase64 = errors.New("the input base64 was invalid")
OutputBufferTooSmall = errors.New("supplied output buffer is too small")
BadMessageVersion = errors.New("the message version is unsupported") ErrLibolmNotEnoughRandom = errors.New("not enough entropy was supplied")
BadMessageFormat = errors.New("the message couldn't be decoded") ErrLibolmOutputBufferTooSmall = errors.New("supplied output buffer is too small")
BadMessageMAC = errors.New("the message couldn't be decrypted") ErrLibolmBadAccountKey = errors.New("the supplied account key is invalid")
BadMessageKeyID = errors.New("the message references an unknown key ID") ErrLibolmCorruptedPickle = errors.New("the pickled object couldn't be decoded")
InvalidBase64 = errors.New("the input base64 was invalid") ErrLibolmBadSessionKey = errors.New("attempt to initialise an inbound group session from an invalid session key")
BadAccountKey = errors.New("the supplied account key is invalid") ErrLibolmBadLegacyAccountPickle = errors.New("attempt to unpickle an account which uses pickle version 1")
UnknownPickleVersion = errors.New("the pickled object is too new") )
CorruptedPickle = errors.New("the pickled object couldn't be decoded")
BadSessionKey = errors.New("attempt to initialise an inbound group session from an invalid session key") // Deprecated: use variables prefixed with Err
UnknownMessageIndex = errors.New("attempt to decode a message whose index is earlier than our earliest known session key") var (
BadLegacyAccountPickle = errors.New("attempt to unpickle an account which uses pickle version 1") EmptyInput = ErrEmptyInput
BadSignature = errors.New("received message had a bad signature") BadSignature = ErrBadSignature
InputBufferTooSmall = errors.New("the input data was too small to be valid") InvalidBase64 = ErrLibolmInvalidBase64
BadMessageKeyID = ErrBadMessageKeyID
BadMessageFormat = ErrBadMessageFormat
BadMessageVersion = ErrWrongProtocolVersion
BadMessageMAC = ErrBadMAC
UnknownPickleVersion = ErrUnknownOlmPickleVersion
NotEnoughRandom = ErrLibolmNotEnoughRandom
OutputBufferTooSmall = ErrLibolmOutputBufferTooSmall
BadAccountKey = ErrLibolmBadAccountKey
CorruptedPickle = ErrLibolmCorruptedPickle
BadSessionKey = ErrLibolmBadSessionKey
UnknownMessageIndex = ErrUnknownMessageIndex
BadLegacyAccountPickle = ErrLibolmBadLegacyAccountPickle
InputBufferTooSmall = ErrInputToSmall
NoKeyProvided = ErrNoKeyProvided
NotEnoughGoRandom = ErrNotEnoughGoRandom
InputNotJSONString = ErrInputNotJSONString
ErrBadVersion = ErrUnknownJSONPickleVersion
ErrWrongPickleVersion = ErrUnknownJSONPickleVersion
ErrRatchetNotAvailable = ErrUnknownMessageIndex
) )

View file

@ -18,8 +18,14 @@ import (
) )
var ( var (
SessionNotShared = errors.New("session has not been shared") ErrSessionNotShared = errors.New("session has not been shared")
SessionExpired = errors.New("session has expired") ErrSessionExpired = errors.New("session has expired")
)
// Deprecated: use variables prefixed with Err
var (
SessionNotShared = ErrSessionNotShared
SessionExpired = ErrSessionExpired
) )
// OlmSessionList is a list of OlmSessions. // OlmSessionList is a list of OlmSessions.
@ -255,9 +261,9 @@ func (ogs *OutboundGroupSession) Expired() bool {
func (ogs *OutboundGroupSession) Encrypt(plaintext []byte) ([]byte, error) { func (ogs *OutboundGroupSession) Encrypt(plaintext []byte) ([]byte, error) {
if !ogs.Shared { if !ogs.Shared {
return nil, SessionNotShared return nil, ErrSessionNotShared
} else if ogs.Expired() { } else if ogs.Expired() {
return nil, SessionExpired return nil, ErrSessionExpired
} }
ogs.MessageCount++ ogs.MessageCount++
ogs.LastEncryptedTime = time.Now() ogs.LastEncryptedTime = time.Now()

View file

@ -95,6 +95,22 @@ func (mach *Machine) SetEncryptedAccountData(ctx context.Context, eventType even
return mach.Client.SetAccountData(ctx, eventType.Type, &EncryptedAccountDataEventContent{Encrypted: encrypted}) return mach.Client.SetAccountData(ctx, eventType.Type, &EncryptedAccountDataEventContent{Encrypted: encrypted})
} }
// SetEncryptedAccountDataWithMetadata encrypts the given data with the given keys and stores it,
// alongside the unencrypted metadata, on the server.
func (mach *Machine) SetEncryptedAccountDataWithMetadata(ctx context.Context, eventType event.Type, data []byte, metadata map[string]any, keys ...*Key) error {
if len(keys) == 0 {
return ErrNoKeyGiven
}
encrypted := make(map[string]EncryptedKeyData, len(keys))
for _, key := range keys {
encrypted[key.ID] = key.Encrypt(eventType.Type, data)
}
return mach.Client.SetAccountData(ctx, eventType.Type, &EncryptedAccountDataEventContent{
Encrypted: encrypted,
Metadata: metadata,
})
}
// GenerateAndUploadKey generates a new SSSS key and stores the metadata on the server. // GenerateAndUploadKey generates a new SSSS key and stores the metadata on the server.
func (mach *Machine) GenerateAndUploadKey(ctx context.Context, passphrase string) (key *Key, err error) { func (mach *Machine) GenerateAndUploadKey(ctx context.Context, passphrase string) (key *Key, err error) {
key, err = NewKey(passphrase) key, err = NewKey(passphrase)

View file

@ -57,6 +57,7 @@ type EncryptedKeyData struct {
type EncryptedAccountDataEventContent struct { type EncryptedAccountDataEventContent struct {
Encrypted map[string]EncryptedKeyData `json:"encrypted"` Encrypted map[string]EncryptedKeyData `json:"encrypted"`
Metadata map[string]any `json:"com.beeper.metadata,omitzero"`
} }
func (ed *EncryptedAccountDataEventContent) Decrypt(eventType string, key *Key) ([]byte, error) { func (ed *EncryptedAccountDataEventContent) Decrypt(eventType string, key *Key) ([]byte, error) {

View file

@ -85,6 +85,10 @@ var (
ErrResponseTooLong = errors.New("response content length too long") ErrResponseTooLong = errors.New("response content length too long")
ErrBodyReadReachedLimit = errors.New("reached response size limit while reading body") ErrBodyReadReachedLimit = errors.New("reached response size limit while reading body")
// Special error that indicates we should retry canceled contexts. Note that on it's own this
// is useless, the context itself must also be replaced.
ErrContextCancelRetry = errors.New("retry canceled context")
) )
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error. // HTTPError An HTTP Error response, which may wrap an underlying native Go Error.

View file

@ -53,6 +53,8 @@ type BeeperMessageStatusEventContent struct {
LastRetry id.EventID `json:"last_retry,omitempty"` LastRetry id.EventID `json:"last_retry,omitempty"`
TargetTxnID string `json:"relates_to_txn_id,omitempty"`
MutateEventKey string `json:"mutate_event_key,omitempty"` MutateEventKey string `json:"mutate_event_key,omitempty"`
// Indicates the set of users to whom the event was delivered. If nil, then // Indicates the set of users to whom the event was delivered. If nil, then
@ -87,7 +89,17 @@ type BeeperRoomKeyAckEventContent struct {
} }
type BeeperChatDeleteEventContent struct { type BeeperChatDeleteEventContent struct {
DeleteForEveryone bool `json:"delete_for_everyone,omitempty"` DeleteForEveryone bool `json:"delete_for_everyone,omitempty"`
FromMessageRequest bool `json:"from_message_request,omitempty"`
}
type BeeperAcceptMessageRequestEventContent struct {
}
type BeeperSendStateEventContent struct {
Type string `json:"type"`
StateKey string `json:"state_key"`
Content Content `json:"content"`
} }
type IntOrString int type IntOrString int

View file

@ -77,6 +77,11 @@ export interface RoomFeatures {
delete_chat?: boolean delete_chat?: boolean
/** Whether deleting the chat for all participants is supported. */ /** Whether deleting the chat for all participants is supported. */
delete_chat_for_everyone?: boolean delete_chat_for_everyone?: boolean
/** What can be done with message requests? */
message_request?: {
accept_with_message?: CapabilitySupportLevel
accept_with_button?: CapabilitySupportLevel
}
} }
declare type integer = number declare type integer = number

View file

@ -61,6 +61,8 @@ type RoomFeatures struct {
DeleteChat bool `json:"delete_chat,omitempty"` DeleteChat bool `json:"delete_chat,omitempty"`
DeleteChatForEveryone bool `json:"delete_chat_for_everyone,omitempty"` DeleteChatForEveryone bool `json:"delete_chat_for_everyone,omitempty"`
MessageRequest *MessageRequestFeatures `json:"message_request,omitempty"`
PerMessageProfileRelay bool `json:"-"` PerMessageProfileRelay bool `json:"-"`
} }
@ -84,6 +86,7 @@ func (rf *RoomFeatures) Clone() *RoomFeatures {
clone.DeleteMaxAge = ptr.Clone(clone.DeleteMaxAge) clone.DeleteMaxAge = ptr.Clone(clone.DeleteMaxAge)
clone.DisappearingTimer = clone.DisappearingTimer.Clone() clone.DisappearingTimer = clone.DisappearingTimer.Clone()
clone.AllowedReactions = slices.Clone(clone.AllowedReactions) clone.AllowedReactions = slices.Clone(clone.AllowedReactions)
clone.MessageRequest = clone.MessageRequest.Clone()
return &clone return &clone
} }
@ -165,6 +168,25 @@ func (dtc *DisappearingTimerCapability) Supports(content *BeeperDisappearingTime
return slices.Contains(dtc.Types, content.Type) && (dtc.Timers == nil || slices.Contains(dtc.Timers, content.Timer)) return slices.Contains(dtc.Types, content.Type) && (dtc.Timers == nil || slices.Contains(dtc.Timers, content.Timer))
} }
type MessageRequestFeatures struct {
AcceptWithMessage CapabilitySupportLevel `json:"accept_with_message,omitempty"`
AcceptWithButton CapabilitySupportLevel `json:"accept_with_button,omitempty"`
}
func (mrf *MessageRequestFeatures) Clone() *MessageRequestFeatures {
return ptr.Clone(mrf)
}
func (mrf *MessageRequestFeatures) Hash() []byte {
if mrf == nil {
return nil
}
hasher := sha256.New()
hashValue(hasher, "accept_with_message", mrf.AcceptWithMessage)
hashValue(hasher, "accept_with_button", mrf.AcceptWithButton)
return hasher.Sum(nil)
}
type CapabilityMsgType = MessageType type CapabilityMsgType = MessageType
// Message types which are used for event capability signaling, but aren't real values for the msgtype field. // Message types which are used for event capability signaling, but aren't real values for the msgtype field.
@ -347,6 +369,7 @@ func (rf *RoomFeatures) Hash() []byte {
hashBool(hasher, "mark_as_unread", rf.MarkAsUnread) hashBool(hasher, "mark_as_unread", rf.MarkAsUnread)
hashBool(hasher, "delete_chat", rf.DeleteChat) hashBool(hasher, "delete_chat", rf.DeleteChat)
hashBool(hasher, "delete_chat_for_everyone", rf.DeleteChatForEveryone) hashBool(hasher, "delete_chat_for_everyone", rf.DeleteChatForEveryone)
hashValue(hasher, "message_request", rf.MessageRequest)
return hasher.Sum(nil) return hasher.Sum(nil)
} }

View file

@ -61,9 +61,11 @@ var TypeMap = map[Type]reflect.Type{
EventUnstablePollStart: reflect.TypeOf(PollStartEventContent{}), EventUnstablePollStart: reflect.TypeOf(PollStartEventContent{}),
EventUnstablePollResponse: reflect.TypeOf(PollResponseEventContent{}), EventUnstablePollResponse: reflect.TypeOf(PollResponseEventContent{}),
BeeperMessageStatus: reflect.TypeOf(BeeperMessageStatusEventContent{}), BeeperMessageStatus: reflect.TypeOf(BeeperMessageStatusEventContent{}),
BeeperTranscription: reflect.TypeOf(BeeperTranscriptionEventContent{}), BeeperTranscription: reflect.TypeOf(BeeperTranscriptionEventContent{}),
BeeperDeleteChat: reflect.TypeOf(BeeperChatDeleteEventContent{}), BeeperDeleteChat: reflect.TypeOf(BeeperChatDeleteEventContent{}),
BeeperAcceptMessageRequest: reflect.TypeOf(BeeperAcceptMessageRequestEventContent{}),
BeeperSendState: reflect.TypeOf(BeeperSendStateEventContent{}),
AccountDataRoomTags: reflect.TypeOf(TagEventContent{}), AccountDataRoomTags: reflect.TypeOf(TagEventContent{}),
AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}), AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}),

View file

@ -63,7 +63,7 @@ func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error {
return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext) return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext)
case id.AlgorithmMegolmV1: case id.AlgorithmMegolmV1:
if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' { if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' {
return id.InputNotJSONString return id.ErrInputNotJSONString
} }
content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1] content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1]
} }

View file

@ -231,7 +231,8 @@ type BridgeInfoSection struct {
AvatarURL id.ContentURIString `json:"avatar_url,omitempty"` AvatarURL id.ContentURIString `json:"avatar_url,omitempty"`
ExternalURL string `json:"external_url,omitempty"` ExternalURL string `json:"external_url,omitempty"`
Receiver string `json:"fi.mau.receiver,omitempty"` Receiver string `json:"fi.mau.receiver,omitempty"`
MessageRequest bool `json:"com.beeper.message_request,omitempty"`
} }
// BridgeEventContent represents the content of a m.bridge state event. // BridgeEventContent represents the content of a m.bridge state event.

View file

@ -128,7 +128,7 @@ func (et *Type) GuessClass() TypeClass {
InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type, InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type,
CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type, CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type,
CallNegotiate.Type, CallHangup.Type, BeeperMessageStatus.Type, EventUnstablePollStart.Type, EventUnstablePollResponse.Type, CallNegotiate.Type, CallHangup.Type, BeeperMessageStatus.Type, EventUnstablePollStart.Type, EventUnstablePollResponse.Type,
EventUnstablePollEnd.Type, BeeperTranscription.Type, BeeperDeleteChat.Type: EventUnstablePollEnd.Type, BeeperTranscription.Type, BeeperDeleteChat.Type, BeeperAcceptMessageRequest.Type:
return MessageEventType return MessageEventType
case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type, ToDeviceRoomKeyWithheld.Type, case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type, ToDeviceRoomKeyWithheld.Type,
ToDeviceBeeperRoomKeyAck.Type: ToDeviceBeeperRoomKeyAck.Type:
@ -234,9 +234,11 @@ var (
CallNegotiate = Type{"m.call.negotiate", MessageEventType} CallNegotiate = Type{"m.call.negotiate", MessageEventType}
CallHangup = Type{"m.call.hangup", MessageEventType} CallHangup = Type{"m.call.hangup", MessageEventType}
BeeperMessageStatus = Type{"com.beeper.message_send_status", MessageEventType} BeeperMessageStatus = Type{"com.beeper.message_send_status", MessageEventType}
BeeperTranscription = Type{"com.beeper.transcription", MessageEventType} BeeperTranscription = Type{"com.beeper.transcription", MessageEventType}
BeeperDeleteChat = Type{"com.beeper.delete_chat", MessageEventType} BeeperDeleteChat = Type{"com.beeper.delete_chat", MessageEventType}
BeeperAcceptMessageRequest = Type{"com.beeper.accept_message_request", MessageEventType}
BeeperSendState = Type{"com.beeper.send_state", MessageEventType}
EventUnstablePollStart = Type{Type: "org.matrix.msc3381.poll.start", Class: MessageEventType} EventUnstablePollStart = Type{Type: "org.matrix.msc3381.poll.start", Class: MessageEventType}
EventUnstablePollResponse = Type{Type: "org.matrix.msc3381.poll.response", Class: MessageEventType} EventUnstablePollResponse = Type{Type: "org.matrix.msc3381.poll.response", Class: MessageEventType}

View file

@ -57,7 +57,7 @@ type FilterPart struct {
// Validate checks if the filter contains valid property values // Validate checks if the filter contains valid property values
func (filter *Filter) Validate() error { func (filter *Filter) Validate() error {
if filter.EventFormat != EventFormatClient && filter.EventFormat != EventFormatFederation { if filter.EventFormat != EventFormatClient && filter.EventFormat != EventFormatFederation {
return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]") return errors.New("bad event_format value")
} }
return nil return nil
} }

View file

@ -17,8 +17,14 @@ import (
) )
var ( var (
InvalidContentURI = errors.New("invalid Matrix content URI") ErrInvalidContentURI = errors.New("invalid Matrix content URI")
InputNotJSONString = errors.New("input doesn't look like a JSON string") ErrInputNotJSONString = errors.New("input doesn't look like a JSON string")
)
// Deprecated: use variables prefixed with Err
var (
InvalidContentURI = ErrInvalidContentURI
InputNotJSONString = ErrInputNotJSONString
) )
// ContentURIString is a string that's expected to be a Matrix content URI. // ContentURIString is a string that's expected to be a Matrix content URI.
@ -55,9 +61,9 @@ func ParseContentURI(uri string) (parsed ContentURI, err error) {
if len(uri) == 0 { if len(uri) == 0 {
return return
} else if !strings.HasPrefix(uri, "mxc://") { } else if !strings.HasPrefix(uri, "mxc://") {
err = InvalidContentURI err = ErrInvalidContentURI
} else if index := strings.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 { } else if index := strings.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 {
err = InvalidContentURI err = ErrInvalidContentURI
} else { } else {
parsed.Homeserver = uri[6 : 6+index] parsed.Homeserver = uri[6 : 6+index]
parsed.FileID = uri[6+index+1:] parsed.FileID = uri[6+index+1:]
@ -71,9 +77,9 @@ func ParseContentURIBytes(uri []byte) (parsed ContentURI, err error) {
if len(uri) == 0 { if len(uri) == 0 {
return return
} else if !bytes.HasPrefix(uri, mxcBytes) { } else if !bytes.HasPrefix(uri, mxcBytes) {
err = InvalidContentURI err = ErrInvalidContentURI
} else if index := bytes.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 { } else if index := bytes.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 {
err = InvalidContentURI err = ErrInvalidContentURI
} else { } else {
parsed.Homeserver = string(uri[6 : 6+index]) parsed.Homeserver = string(uri[6 : 6+index])
parsed.FileID = string(uri[6+index+1:]) parsed.FileID = string(uri[6+index+1:])
@ -86,7 +92,7 @@ func (uri *ContentURI) UnmarshalJSON(raw []byte) (err error) {
*uri = ContentURI{} *uri = ContentURI{}
return nil return nil
} else if len(raw) < 2 || raw[0] != '"' || raw[len(raw)-1] != '"' { } else if len(raw) < 2 || raw[0] != '"' || raw[len(raw)-1] != '"' {
return InputNotJSONString return ErrInputNotJSONString
} }
parsed, err := ParseContentURIBytes(raw[1 : len(raw)-1]) parsed, err := ParseContentURIBytes(raw[1 : len(raw)-1])
if err != nil { if err != nil {

View file

@ -54,7 +54,7 @@ var SigilToPathSegment = map[rune]string{
func (uri *MatrixURI) getQuery() url.Values { func (uri *MatrixURI) getQuery() url.Values {
q := make(url.Values) q := make(url.Values)
if uri.Via != nil && len(uri.Via) > 0 { if len(uri.Via) > 0 {
q["via"] = uri.Via q["via"] = uri.Via
} }
if len(uri.Action) > 0 { if len(uri.Action) > 0 {

View file

@ -219,15 +219,15 @@ func DecodeUserLocalpart(str string) (string, error) {
for i := 0; i < len(strBytes); i++ { for i := 0; i < len(strBytes); i++ {
b := strBytes[i] b := strBytes[i]
if !isValidByte(b) { if !isValidByte(b) {
return "", fmt.Errorf("Byte pos %d: Invalid byte", i) return "", fmt.Errorf("invalid encoded byte at position %d: %c", i, b)
} }
if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _ if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _
if i+1 >= len(strBytes) { if i+1 >= len(strBytes) {
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i) return "", fmt.Errorf("unexpected end of string after underscore at %d", i)
} }
if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i) return "", fmt.Errorf("unexpected byte %c after underscore at %d", strBytes[i+1], i)
} }
if strBytes[i+1] == '_' { if strBytes[i+1] == '_' {
outputBuffer.WriteByte('_') outputBuffer.WriteByte('_')
@ -237,7 +237,7 @@ func DecodeUserLocalpart(str string) (string, error) {
i++ // skip next byte since we just handled it i++ // skip next byte since we just handled it
} else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8 } else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8
if i+2 >= len(strBytes) { if i+2 >= len(strBytes) {
return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i) return "", fmt.Errorf("unexpected end of string after equals sign at %d", i)
} }
dst := make([]byte, 1) dst := make([]byte, 1)
_, err := hex.Decode(dst, strBytes[i+1:i+3]) _, err := hex.Decode(dst, strBytes[i+1:i+3])

View file

@ -105,7 +105,7 @@ func (action *PushAction) UnmarshalJSON(raw []byte) error {
if ok { if ok {
action.Action = ActionSetTweak action.Action = ActionSetTweak
action.Tweak = PushActionTweak(tweak) action.Tweak = PushActionTweak(tweak)
action.Value, _ = val["value"] action.Value = val["value"]
} }
} }
return nil return nil

View file

@ -263,10 +263,7 @@ type RespMutualRooms struct {
type RespRoomSummary struct { type RespRoomSummary struct {
PublicRoomInfo PublicRoomInfo
Membership event.Membership `json:"membership,omitempty"` Membership event.Membership `json:"membership,omitempty"`
RoomVersion id.RoomVersion `json:"room_version,omitempty"`
Encryption id.Algorithm `json:"encryption,omitempty"`
AllowedRoomIDs []id.RoomID `json:"allowed_room_ids,omitempty"`
UnstableRoomVersion id.RoomVersion `json:"im.nheko.summary.room_version,omitempty"` UnstableRoomVersion id.RoomVersion `json:"im.nheko.summary.room_version,omitempty"`
UnstableRoomVersionOld id.RoomVersion `json:"im.nheko.summary.version,omitempty"` UnstableRoomVersionOld id.RoomVersion `json:"im.nheko.summary.version,omitempty"`
@ -685,6 +682,10 @@ type PublicRoomInfo struct {
RoomType event.RoomType `json:"room_type"` RoomType event.RoomType `json:"room_type"`
Topic string `json:"topic,omitempty"` Topic string `json:"topic,omitempty"`
WorldReadable bool `json:"world_readable"` WorldReadable bool `json:"world_readable"`
RoomVersion id.RoomVersion `json:"room_version,omitempty"`
Encryption id.Algorithm `json:"encryption,omitempty"`
AllowedRoomIDs []id.RoomID `json:"allowed_room_ids,omitempty"`
} }
// RespHierarchy is the JSON response for https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv1roomsroomidhierarchy // RespHierarchy is the JSON response for https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv1roomsroomidhierarchy

View file

@ -5,8 +5,6 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
) )
type RoomStateMap = map[event.Type]map[string]*event.Event
// Room represents a single Matrix room. // Room represents a single Matrix room.
type Room struct { type Room struct {
ID id.RoomID ID id.RoomID
@ -25,8 +23,8 @@ func (room Room) UpdateState(evt *event.Event) {
// GetStateEvent returns the state event for the given type/state_key combo, or nil. // GetStateEvent returns the state event for the given type/state_key combo, or nil.
func (room Room) GetStateEvent(eventType event.Type, stateKey string) *event.Event { func (room Room) GetStateEvent(eventType event.Type, stateKey string) *event.Event {
stateEventMap, _ := room.State[eventType] stateEventMap := room.State[eventType]
evt, _ := stateEventMap[stateKey] evt := stateEventMap[stateKey]
return evt return evt
} }

View file

@ -129,6 +129,7 @@ func NewMemoryStateStore() StateStore {
PowerLevels: make(map[id.RoomID]*event.PowerLevelsEventContent), PowerLevels: make(map[id.RoomID]*event.PowerLevelsEventContent),
Encryption: make(map[id.RoomID]*event.EncryptionEventContent), Encryption: make(map[id.RoomID]*event.EncryptionEventContent),
Create: make(map[id.RoomID]*event.Event), Create: make(map[id.RoomID]*event.Event),
JoinRules: make(map[id.RoomID]*event.JoinRulesEventContent),
} }
} }

View file

@ -98,10 +98,8 @@ func (saup SynapseAdminURLPath) FullPath() []any {
// and appservice user ID set already. // and appservice user ID set already.
func (cli *Client) BuildURLWithQuery(urlPath PrefixableURLPath, urlQuery map[string]string) string { func (cli *Client) BuildURLWithQuery(urlPath PrefixableURLPath, urlQuery map[string]string) string {
return cli.BuildURLWithFullQuery(urlPath, func(q url.Values) { return cli.BuildURLWithFullQuery(urlPath, func(q url.Values) {
if urlQuery != nil { for k, v := range urlQuery {
for k, v := range urlQuery { q.Set(k, v)
q.Set(k, v)
}
} }
}) })
} }

View file

@ -8,7 +8,7 @@ import (
"strings" "strings"
) )
const Version = "v0.26.0" const Version = "v0.26.1"
var GoModVersion = "" var GoModVersion = ""
var Commit = "" var Commit = ""

2
vendor/modernc.org/sqlite/AUTHORS generated vendored
View file

@ -29,4 +29,4 @@ Saed SayedAhmed <saadmtsa@gmail.com>
Steffen Butzer <steffen(dot)butzer@outlook.com> Steffen Butzer <steffen(dot)butzer@outlook.com>
Toni Spets <toni.spets@beeper.com> Toni Spets <toni.spets@beeper.com>
W. Michael Petullo <mike@flyn.org> W. Michael Petullo <mike@flyn.org>
SUSE LLC <moio@suse.com> SUSE LLC <moio@suse.com>

View file

@ -10,6 +10,7 @@ Alexander Menzhinsky <amenzhinsky@gmail.com>
Alexey Palazhchenko <alexey.palazhchenko@gmail.com> Alexey Palazhchenko <alexey.palazhchenko@gmail.com>
Angus Dippenaar <angusdippenaar@gmail.com> Angus Dippenaar <angusdippenaar@gmail.com>
Artyom Pervukhin <github@artyom.dev> Artyom Pervukhin <github@artyom.dev>
Adrian Witas <adrianwit@gmail.com>
Dan Kortschak <dan@kortschak.io> Dan Kortschak <dan@kortschak.io>
Dan Peterson <danp@danp.net> Dan Peterson <danp@danp.net>
David Skinner <skinner.david@gmail.com> David Skinner <skinner.david@gmail.com>

41
vendor/modernc.org/sqlite/README.md generated vendored
View file

@ -1,7 +1,18 @@
# ⚠️ Important: Repository Mirroring
**This project is primarily developed on GitLab.** The repository you are currently viewing might be a mirror. Please review the guidelines below based on where you are viewing this:
| Platform | Role | Contributing Guidelines |
| :--- | :--- | :--- |
| **GitLab** | **Primary Source** | This is the canonical repository (`cznic/sqlite`). CI pipelines and main development happen here. |
| **GitHub** | **Mirror** | This is a mirror (`modernc-org/sqlite`). We **do accept** Issues and Pull Requests here for your convenience! <br> *Note: PRs submitted here will be manually merged into the GitLab source, so please allow extra time for processing.* |
---
![logo](logo.png) ![logo](logo.png)
[![Go Reference](https://pkg.go.dev/badge/modernc.org/sqlite.svg)](https://pkg.go.dev/modernc.org/sqlite) [![Go Reference](https://pkg.go.dev/badge/modernc.org/sqlite.svg)](https://pkg.go.dev/modernc.org/sqlite)
[![LiberaPay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/jnml/donate) [![LiberaPay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/jnml/donate)
[![receives](https://img.shields.io/liberapay/receives/jnml.svg?logo=liberapay)](https://liberapay.com/jnml/donate) [![receives](https://img.shields.io/liberapay/receives/jnml.svg?logo=liberapay)](https://liberapay.com/jnml/donate)
[![patrons](https://img.shields.io/liberapay/patrons/jnml.svg?logo=liberapay)](https://liberapay.com/jnml/donate) [![patrons](https://img.shields.io/liberapay/patrons/jnml.svg?logo=liberapay)](https://liberapay.com/jnml/donate)
@ -9,3 +20,31 @@
[The SQLite Drivers Benchmarks Game] [The SQLite Drivers Benchmarks Game]
[The SQLite Drivers Benchmarks Game]: https://pkg.go.dev/modernc.org/sqlite-bench#readme-tl-dr-scorecard [The SQLite Drivers Benchmarks Game]: https://pkg.go.dev/modernc.org/sqlite-bench#readme-tl-dr-scorecard
Virtual Tables (vtab)
---------------------
The driver exposes a Go API to implement SQLite virtual table modules in pure Go via the `modernc.org/sqlite/vtab` package. This lets you back SQL tables with arbitrary data sources (e.g., vector indexes, CSV files, remote APIs) and integrate with SQLites planner.
- Register: `vtab.RegisterModule(db, name, module)`. Registration applies to new connections only.
- Schema declaration: Call `ctx.Declare("CREATE TABLE <name>(<cols...>)")` within `Create` or `Connect`. The driver does not auto-declare schemas, enabling dynamic schemas.
- Module arguments: `args []string` passed to `Create/Connect` are configuration parsed from `USING module(...)`. They are not treated as columns unless your module chooses to.
- Planning (BestIndex):
- Inspect `info.Constraints` (with `Column`, `Op`, `Usable`, 0-based `ArgIndex`, and `Omit`), `info.OrderBy`, and `info.ColUsed` (bitmask of referenced columns).
- Set `ArgIndex` (0-based) to populate `Filter`s `vals` in the chosen order; set `Omit` to ask SQLite not to re-check a constraint you fully handle.
- Execution: `Cursor.Filter(idxNum, idxStr, vals)` receives arguments in the order implied by `ArgIndex`.
- Operators: Common SQLite operators map to `ConstraintOp` (EQ/NE/GT/GE/LT/LE/MATCH/IS/ISNOT/ISNULL/ISNOTNULL/LIKE/GLOB/REGEXP/FUNCTION/LIMIT/OFFSET). Unknown operators map to `OpUnknown`.
- Errors: Returning an error from vtab methods surfaces a descriptive message to SQLite (e.g., `zErrMsg` for xCreate/xConnect/xBestIndex/xFilter; `sqlite3_result_error` for xColumn).
Examples
--------
- Vector search (sqlite-vec style):
- `CREATE VIRTUAL TABLE vec_docs USING vec(dim=128, metric="cosine")`
- Module reads args (e.g., `dim`, `metric`), calls `ctx.Declare("CREATE TABLE vec_docs(id, embedding, content HIDDEN)")`, and implements search via `BestIndex`/`Filter`.
- CSV loader:
- `CREATE VIRTUAL TABLE csv_users USING csv(filename="/tmp/users.csv", delimiter=",", header=true)`
- Module reads the file header to compute columns, declares them via `ctx.Declare("CREATE TABLE csv_users(name, email, ...)")`, and streams rows via a cursor.
See `vtab` package docs for full API details.

65
vendor/modernc.org/sqlite/backup.go generated vendored Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2025 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sqlite // import "modernc.org/sqlite"
import (
"database/sql/driver"
sqlite3 "modernc.org/sqlite/lib"
)
// Backup object is used to manage progress and cleanup an online backup. It
// is returned by NewBackup or NewRestore.
type Backup struct {
srcConn *conn // source database connection
dstConn *conn // destination database connection
pBackup uintptr // sqlite3_backup object pointer
}
// Step will copy up to n pages between the source and destination databases
// specified by the backup object. If n is negative, all remaining source
// pages are copied.
// If it successfully copies n pages and there are still more pages to be
// copied, then the function returns true with no error. If it successfully
// finishes copying all pages from source to destination, then it returns
// false with no error. If an error occurs while running, then an error is
// returned.
func (b *Backup) Step(n int32) (bool, error) {
rc := sqlite3.Xsqlite3_backup_step(b.srcConn.tls, b.pBackup, n)
if rc == sqlite3.SQLITE_OK {
return true, nil
} else if rc == sqlite3.SQLITE_DONE {
return false, nil
} else {
return false, b.srcConn.errstr(rc)
}
}
// Finish releases all resources associated with the Backup object. The Backup
// object is invalid and may not be used following a call to Finish.
func (b *Backup) Finish() error {
rc := sqlite3.Xsqlite3_backup_finish(b.srcConn.tls, b.pBackup)
b.dstConn.Close()
if rc == sqlite3.SQLITE_OK {
return nil
} else {
return b.srcConn.errstr(rc)
}
}
// Commit releases all resources associated with the Backup object but does not
// close the destination database connection.
//
// The destination database connection is returned to the caller or an error if raised.
// It is the responsibility of the caller to handle the connection closure.
func (b *Backup) Commit() (driver.Conn, error) {
rc := sqlite3.Xsqlite3_backup_finish(b.srcConn.tls, b.pBackup)
if rc == sqlite3.SQLITE_OK {
return b.dstConn, nil
} else {
return nil, b.srcConn.errstr(rc)
}
}

1070
vendor/modernc.org/sqlite/conn.go generated vendored Normal file

File diff suppressed because it is too large Load diff

2
vendor/modernc.org/sqlite/doc.go generated vendored
View file

@ -58,7 +58,7 @@
// - 2025-10-10 v1.39.1: Upgrade to SQLite 3.50.4. // - 2025-10-10 v1.39.1: Upgrade to SQLite 3.50.4.
// //
// - 2025-06-09 v1.38.0: Upgrade to SQLite 3.50.1. // - 2025-06-09 v1.38.0: Upgrade to SQLite 3.50.1.
// //
// - 2025-02-26 v1.36.0: Upgrade to SQLite 3.49.0. // - 2025-02-26 v1.36.0: Upgrade to SQLite 3.49.0.
// //
// - 2024-11-16 v1.34.0: Implement ResetSession and IsValid methods in connection // - 2024-11-16 v1.34.0: Implement ResetSession and IsValid methods in connection

120
vendor/modernc.org/sqlite/driver.go generated vendored Normal file
View file

@ -0,0 +1,120 @@
// Copyright 2025 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sqlite // import "modernc.org/sqlite"
import (
"database/sql/driver"
"fmt"
"modernc.org/sqlite/vtab"
)
// Driver implements database/sql/driver.Driver.
type Driver struct {
// user defined functions that are added to every new connection on Open
udfs map[string]*userDefinedFunction
// collations that are added to every new connection on Open
collations map[string]*collation
// connection hooks are called after a connection is opened
connectionHooks []ConnectionHookFn
// modules holds registered virtual table modules that should be added to
// every new connection on Open.
modules map[string]vtab.Module
}
var d = &Driver{
udfs: make(map[string]*userDefinedFunction, 0),
collations: make(map[string]*collation, 0),
connectionHooks: make([]ConnectionHookFn, 0),
modules: make(map[string]vtab.Module, 0),
}
func newDriver() *Driver { return d }
// Open returns a new connection to the database. The name is a string in a
// driver-specific format.
//
// Open may return a cached connection (one previously closed), but doing so is
// unnecessary; the sql package maintains a pool of idle connections for
// efficient re-use.
//
// The returned connection is only used by one goroutine at a time.
//
// The name may be a filename, e.g., "/tmp/mydata.sqlite", or a URI, in which
// case it may include a '?' followed by one or more query parameters.
// For example, "file:///tmp/mydata.sqlite?_pragma=foreign_keys(1)&_time_format=sqlite".
// The supported query parameters are:
//
// _pragma: Each value will be run as a "PRAGMA ..." statement (with the PRAGMA
// keyword added for you). May be specified more than once, '&'-separated. For more
// information on supported PRAGMAs see: https://www.sqlite.org/pragma.html
//
// _time_format: The name of a format to use when writing time values to the
// database. Currently the only supported value is "sqlite", which corresponds
// to format 7 from https://www.sqlite.org/lang_datefunc.html#time_values,
// including the timezone specifier. If this parameter is not specified, then
// the default String() format will be used.
//
// _time_integer_format: The name of a integer format to use when writing time values.
// By default, the time is stored as string and the format can be set with _time_format
// parameter. If _time_integer_format is set, the time will be stored as an integer and
// the integer value will depend on the integer format.
// If you decide to set both _time_format and _time_integer_format, the time will be
// converted as integer and the _time_format value will be ignored.
// Currently the supported value are "unix","unix_milli", "unix_micro" and "unix_nano",
// which corresponds to seconds, milliseconds, microseconds or nanoseconds
// since unixepoch (1 January 1970 00:00:00 UTC).
//
// _inttotime: Enable conversion of time column (DATE, DATETIME,TIMESTAMP) from integer
// to time if the field contain integer (int64).
//
// _txlock: The locking behavior to use when beginning a transaction. May be
// "deferred" (the default), "immediate", or "exclusive" (case insensitive). See:
// https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
func (d *Driver) Open(name string) (conn driver.Conn, err error) {
if dmesgs {
defer func() {
dmesg("name %q: (driver.Conn %p, err %v)", name, conn, err)
}()
}
c, err := newConn(name)
if err != nil {
return nil, err
}
for _, udf := range d.udfs {
if err = c.createFunctionInternal(udf); err != nil {
c.Close()
return nil, err
}
}
for _, coll := range d.collations {
if err = c.createCollationInternal(coll); err != nil {
c.Close()
return nil, err
}
}
for _, connHookFn := range d.connectionHooks {
if err = connHookFn(c, name); err != nil {
c.Close()
return nil, fmt.Errorf("connection hook: %w", err)
}
}
// Register any vtab modules with this connection.
// Note: vtab module registration applies to new connections only. If a
// module is registered after a connection has been opened, that existing
// connection will not see the module; open a new connection to use it.
if err := c.registerModules(); err != nil {
c.Close()
return nil, err
}
return c, nil
}
// RegisterConnectionHook registers a function to be called after each connection
// is opened. This is called after all the connection has been set up.
func (d *Driver) RegisterConnectionHook(fn ConnectionHookFn) {
d.connectionHooks = append(d.connectionHooks, fn)
}

75
vendor/modernc.org/sqlite/error.go generated vendored Normal file
View file

@ -0,0 +1,75 @@
// Copyright 2025 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sqlite // import "modernc.org/sqlite"
import (
sqlite3 "modernc.org/sqlite/lib"
)
// Error represents sqlite library error code.
type Error struct {
msg string
code int
}
// Error implements error.
func (e *Error) Error() string { return e.msg }
// Code returns the sqlite result code for this error.
func (e *Error) Code() int { return e.code }
var (
// ErrorCodeString maps Error.Code() to its string representation.
ErrorCodeString = map[int]string{
sqlite3.SQLITE_ABORT: "Callback routine requested an abort (SQLITE_ABORT)",
sqlite3.SQLITE_AUTH: "Authorization denied (SQLITE_AUTH)",
sqlite3.SQLITE_BUSY: "The database file is locked (SQLITE_BUSY)",
sqlite3.SQLITE_CANTOPEN: "Unable to open the database file (SQLITE_CANTOPEN)",
sqlite3.SQLITE_CONSTRAINT: "Abort due to constraint violation (SQLITE_CONSTRAINT)",
sqlite3.SQLITE_CORRUPT: "The database disk image is malformed (SQLITE_CORRUPT)",
sqlite3.SQLITE_DONE: "sqlite3_step() has finished executing (SQLITE_DONE)",
sqlite3.SQLITE_EMPTY: "Internal use only (SQLITE_EMPTY)",
sqlite3.SQLITE_ERROR: "Generic error (SQLITE_ERROR)",
sqlite3.SQLITE_FORMAT: "Not used (SQLITE_FORMAT)",
sqlite3.SQLITE_FULL: "Insertion failed because database is full (SQLITE_FULL)",
sqlite3.SQLITE_INTERNAL: "Internal logic error in SQLite (SQLITE_INTERNAL)",
sqlite3.SQLITE_INTERRUPT: "Operation terminated by sqlite3_interrupt()(SQLITE_INTERRUPT)",
sqlite3.SQLITE_IOERR | (1 << 8): "(SQLITE_IOERR_READ)",
sqlite3.SQLITE_IOERR | (10 << 8): "(SQLITE_IOERR_DELETE)",
sqlite3.SQLITE_IOERR | (11 << 8): "(SQLITE_IOERR_BLOCKED)",
sqlite3.SQLITE_IOERR | (12 << 8): "(SQLITE_IOERR_NOMEM)",
sqlite3.SQLITE_IOERR | (13 << 8): "(SQLITE_IOERR_ACCESS)",
sqlite3.SQLITE_IOERR | (14 << 8): "(SQLITE_IOERR_CHECKRESERVEDLOCK)",
sqlite3.SQLITE_IOERR | (15 << 8): "(SQLITE_IOERR_LOCK)",
sqlite3.SQLITE_IOERR | (16 << 8): "(SQLITE_IOERR_CLOSE)",
sqlite3.SQLITE_IOERR | (17 << 8): "(SQLITE_IOERR_DIR_CLOSE)",
sqlite3.SQLITE_IOERR | (2 << 8): "(SQLITE_IOERR_SHORT_READ)",
sqlite3.SQLITE_IOERR | (3 << 8): "(SQLITE_IOERR_WRITE)",
sqlite3.SQLITE_IOERR | (4 << 8): "(SQLITE_IOERR_FSYNC)",
sqlite3.SQLITE_IOERR | (5 << 8): "(SQLITE_IOERR_DIR_FSYNC)",
sqlite3.SQLITE_IOERR | (6 << 8): "(SQLITE_IOERR_TRUNCATE)",
sqlite3.SQLITE_IOERR | (7 << 8): "(SQLITE_IOERR_FSTAT)",
sqlite3.SQLITE_IOERR | (8 << 8): "(SQLITE_IOERR_UNLOCK)",
sqlite3.SQLITE_IOERR | (9 << 8): "(SQLITE_IOERR_RDLOCK)",
sqlite3.SQLITE_IOERR: "Some kind of disk I/O error occurred (SQLITE_IOERR)",
sqlite3.SQLITE_LOCKED | (1 << 8): "(SQLITE_LOCKED_SHAREDCACHE)",
sqlite3.SQLITE_LOCKED: "A table in the database is locked (SQLITE_LOCKED)",
sqlite3.SQLITE_MISMATCH: "Data type mismatch (SQLITE_MISMATCH)",
sqlite3.SQLITE_MISUSE: "Library used incorrectly (SQLITE_MISUSE)",
sqlite3.SQLITE_NOLFS: "Uses OS features not supported on host (SQLITE_NOLFS)",
sqlite3.SQLITE_NOMEM: "A malloc() failed (SQLITE_NOMEM)",
sqlite3.SQLITE_NOTADB: "File opened that is not a database file (SQLITE_NOTADB)",
sqlite3.SQLITE_NOTFOUND: "Unknown opcode in sqlite3_file_control() (SQLITE_NOTFOUND)",
sqlite3.SQLITE_NOTICE: "Notifications from sqlite3_log() (SQLITE_NOTICE)",
sqlite3.SQLITE_PERM: "Access permission denied (SQLITE_PERM)",
sqlite3.SQLITE_PROTOCOL: "Database lock protocol error (SQLITE_PROTOCOL)",
sqlite3.SQLITE_RANGE: "2nd parameter to sqlite3_bind out of range (SQLITE_RANGE)",
sqlite3.SQLITE_READONLY: "Attempt to write a readonly database (SQLITE_READONLY)",
sqlite3.SQLITE_ROW: "sqlite3_step() has another row ready (SQLITE_ROW)",
sqlite3.SQLITE_SCHEMA: "The database schema changed (SQLITE_SCHEMA)",
sqlite3.SQLITE_TOOBIG: "String or BLOB exceeds size limit (SQLITE_TOOBIG)",
sqlite3.SQLITE_WARNING: "Warnings from sqlite3_log() (SQLITE_WARNING)",
}
)

View file

@ -227735,4 +227735,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -227308,4 +227308,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221564,4 +221564,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221577,4 +221577,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221713,4 +221713,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221726,4 +221726,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -222010,4 +222010,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221716,4 +221716,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221815,4 +221815,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221763,4 +221763,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221709,4 +221709,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -221663,4 +221663,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -295017,4 +295017,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

View file

@ -294909,4 +294909,3 @@ type Sqlite3_index_info = sqlite3_index_info
type Sqlite3_module = sqlite3_module type Sqlite3_module = sqlite3_module
type Sqlite3_vtab = sqlite3_vtab type Sqlite3_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

Some files were not shown because too many files have changed in this diff Show more