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-validator/v2 v2.2.6
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/jhillyerd/enmime/v2 v2.2.0
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/rs/zerolog v1.34.0
github.com/swaggo/swag v1.16.3
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39
maunium.net/go/mautrix v0.26.0
modernc.org/sqlite v1.40.1
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
maunium.net/go/mautrix v0.26.1
modernc.org/sqlite v1.41.0
)
require (
@ -39,7 +39,7 @@ require (
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/buger/jsonparser v1.1.1 // 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/uax29/v2 v2.3.0 // 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/sjson v1.2.5 // 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/net v0.48.0 // indirect
golang.org/x/sys v0.39.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
modernc.org/libc v1.67.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/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/clipperhouse/displaywidth v0.6.1 h1:/zMlAezfDzT2xy6acHBzwIfyu2ic0hgkT83UX5EY2gY=
github.com/clipperhouse/displaywidth v0.6.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
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/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
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.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
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/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
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/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
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.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ=
go.mau.fi/util v0.9.4 h1:gWdUff+K2rCynRPysXalqqQyr2ahkSWaestH6YhSpso=
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/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
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/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
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-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
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/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
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.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
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 v1.0.0-20180628173108-788fd7840127/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q=
maunium.net/go/mautrix v0.26.1 h1:FWCC1xY5vwJ5ou3duEBjB6w9IIlwfc9el3q3Mju3Dlg=
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/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
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/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY=
modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/sqlite v1.41.0 h1:bJXddp4ZpsqMsNN1vS0jWo4IJTZzb8nWpcgvyCFG9Ck=
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/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
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
func (b *Bot) onBotJoin(ctx context.Context) {
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,
// as described in this bug report: https://github.com/matrix-org/synapse/issues/9768
_, ok := b.handledMembershipEvents.LoadOrStore(evt.ID, true)

View file

@ -10,14 +10,10 @@ type property uint8
const (
// Always 0 width, includes combining marks, control characters, non-printable, etc
_Zero_Width property = iota + 1
// Always 2 wide (East Asian Wide F/W)
_East_Asian_Wide
// Always 2 wide (East Asian Wide F/W, Emoji, Regional Indicator)
_Wide
// Width depends on EastAsianWidth option
_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
@ -83,7 +79,7 @@ func lookup[T stringish.Interface](s T) (v uint8, sz int) {
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 { }
// 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.
var stringWidthValues = [15872]uint8{
var stringWidthValues = [15744]uint8{
// Block 0x0, offset 0x0
// Block 0x1, offset 0x40
// Block 0x2, offset 0x80
@ -579,13 +575,13 @@ var stringWidthValues = [15872]uint8{
0x167f: 0x0003,
// Block 0x5a, offset 0x1680
0x1692: 0x0003,
0x169a: 0x0004, 0x169b: 0x0004,
0x169a: 0x0002, 0x169b: 0x0002,
0x16a9: 0x0002,
0x16aa: 0x0002,
// Block 0x5b, offset 0x16c0
0x16e9: 0x0004,
0x16ea: 0x0004, 0x16eb: 0x0004, 0x16ec: 0x0004,
0x16f0: 0x0004, 0x16f3: 0x0004,
0x16e9: 0x0002,
0x16ea: 0x0002, 0x16eb: 0x0002, 0x16ec: 0x0002,
0x16f0: 0x0002, 0x16f3: 0x0002,
// Block 0x5c, offset 0x1700
0x1720: 0x0003, 0x1721: 0x0003, 0x1722: 0x0003, 0x1723: 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,
0x1864: 0x0003, 0x1865: 0x0003,
0x186f: 0x0003,
0x187d: 0x0004, 0x187e: 0x0004,
0x187d: 0x0002, 0x187e: 0x0002,
// Block 0x62, offset 0x1880
0x1885: 0x0003,
0x1886: 0x0003, 0x1889: 0x0003,
0x188e: 0x0003, 0x188f: 0x0003,
0x1894: 0x0004, 0x1895: 0x0004,
0x1894: 0x0002, 0x1895: 0x0002,
0x189c: 0x0003,
0x189e: 0x0003,
0x18b0: 0x0002, 0x18b1: 0x0002, 0x18b2: 0x0002, 0x18b3: 0x0002, 0x18b4: 0x0002, 0x18b5: 0x0002,
0x18b6: 0x0002, 0x18b7: 0x0002,
// Block 0x63, offset 0x18c0
0x18c0: 0x0003, 0x18c2: 0x0003,
0x18c8: 0x0004, 0x18c9: 0x0004, 0x18ca: 0x0004, 0x18cb: 0x0004,
0x18cc: 0x0004, 0x18cd: 0x0004, 0x18ce: 0x0004, 0x18cf: 0x0004, 0x18d0: 0x0004, 0x18d1: 0x0004,
0x18d2: 0x0004, 0x18d3: 0x0004,
0x18c8: 0x0002, 0x18c9: 0x0002, 0x18ca: 0x0002, 0x18cb: 0x0002,
0x18cc: 0x0002, 0x18cd: 0x0002, 0x18ce: 0x0002, 0x18cf: 0x0002, 0x18d0: 0x0002, 0x18d1: 0x0002,
0x18d2: 0x0002, 0x18d3: 0x0002,
0x18e0: 0x0003, 0x18e1: 0x0003, 0x18e3: 0x0003,
0x18e4: 0x0003, 0x18e5: 0x0003, 0x18e7: 0x0003, 0x18e8: 0x0003, 0x18e9: 0x0003,
0x18ea: 0x0003, 0x18ec: 0x0003, 0x18ed: 0x0003, 0x18ef: 0x0003,
0x18ff: 0x0004,
0x18ff: 0x0002,
// Block 0x64, offset 0x1900
0x190a: 0x0002, 0x190b: 0x0002,
0x190c: 0x0002, 0x190d: 0x0002, 0x190e: 0x0002, 0x190f: 0x0002,
0x1913: 0x0004,
0x191e: 0x0003, 0x191f: 0x0003, 0x1921: 0x0004,
0x192a: 0x0004, 0x192b: 0x0004,
0x193d: 0x0004, 0x193e: 0x0004, 0x193f: 0x0003,
0x1913: 0x0002,
0x191e: 0x0003, 0x191f: 0x0003, 0x1921: 0x0002,
0x192a: 0x0002, 0x192b: 0x0002,
0x193d: 0x0002, 0x193e: 0x0002, 0x193f: 0x0003,
// Block 0x65, offset 0x1940
0x1944: 0x0004, 0x1945: 0x0004,
0x1944: 0x0002, 0x1945: 0x0002,
0x1946: 0x0003, 0x1947: 0x0003, 0x1948: 0x0003, 0x1949: 0x0003, 0x194a: 0x0003, 0x194b: 0x0003,
0x194c: 0x0003, 0x194d: 0x0003, 0x194e: 0x0004, 0x194f: 0x0003, 0x1950: 0x0003, 0x1951: 0x0003,
0x1952: 0x0003, 0x1953: 0x0003, 0x1954: 0x0004, 0x1955: 0x0003, 0x1956: 0x0003, 0x1957: 0x0003,
0x194c: 0x0003, 0x194d: 0x0003, 0x194e: 0x0002, 0x194f: 0x0003, 0x1950: 0x0003, 0x1951: 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,
0x195e: 0x0003, 0x195f: 0x0003, 0x1960: 0x0003, 0x1961: 0x0003, 0x1963: 0x0003,
0x1968: 0x0003, 0x1969: 0x0003,
0x196a: 0x0004, 0x196b: 0x0003, 0x196c: 0x0003, 0x196d: 0x0003, 0x196e: 0x0003, 0x196f: 0x0003,
0x1970: 0x0003, 0x1971: 0x0003, 0x1972: 0x0004, 0x1973: 0x0004, 0x1974: 0x0003, 0x1975: 0x0004,
0x1976: 0x0003, 0x1977: 0x0003, 0x1978: 0x0003, 0x1979: 0x0003, 0x197a: 0x0004, 0x197b: 0x0003,
0x197c: 0x0003, 0x197d: 0x0004, 0x197e: 0x0003, 0x197f: 0x0003,
0x196a: 0x0002, 0x196b: 0x0003, 0x196c: 0x0003, 0x196d: 0x0003, 0x196e: 0x0003, 0x196f: 0x0003,
0x1970: 0x0003, 0x1971: 0x0003, 0x1972: 0x0002, 0x1973: 0x0002, 0x1974: 0x0003, 0x1975: 0x0002,
0x1976: 0x0003, 0x1977: 0x0003, 0x1978: 0x0003, 0x1979: 0x0003, 0x197a: 0x0002, 0x197b: 0x0003,
0x197c: 0x0003, 0x197d: 0x0002, 0x197e: 0x0003, 0x197f: 0x0003,
// Block 0x66, offset 0x1980
0x1985: 0x0004,
0x198a: 0x0004, 0x198b: 0x0004,
0x19a8: 0x0004,
0x1985: 0x0002,
0x198a: 0x0002, 0x198b: 0x0002,
0x19a8: 0x0002,
0x19bd: 0x0003,
// Block 0x67, offset 0x19c0
0x19cc: 0x0004, 0x19ce: 0x0004,
0x19d3: 0x0004, 0x19d4: 0x0004, 0x19d5: 0x0004, 0x19d7: 0x0004,
0x19cc: 0x0002, 0x19ce: 0x0002,
0x19d3: 0x0002, 0x19d4: 0x0002, 0x19d5: 0x0002, 0x19d7: 0x0002,
0x19f6: 0x0003, 0x19f7: 0x0003, 0x19f8: 0x0003, 0x19f9: 0x0003, 0x19fa: 0x0003, 0x19fb: 0x0003,
0x19fc: 0x0003, 0x19fd: 0x0003, 0x19fe: 0x0003, 0x19ff: 0x0003,
// Block 0x68, offset 0x1a00
0x1a15: 0x0004, 0x1a16: 0x0004, 0x1a17: 0x0004,
0x1a30: 0x0004,
0x1a3f: 0x0004,
0x1a15: 0x0002, 0x1a16: 0x0002, 0x1a17: 0x0002,
0x1a30: 0x0002,
0x1a3f: 0x0002,
// Block 0x69, offset 0x1a40
0x1a5b: 0x0004, 0x1a5c: 0x0004,
0x1a5b: 0x0002, 0x1a5c: 0x0002,
// Block 0x6a, offset 0x1a80
0x1a90: 0x0004,
0x1a95: 0x0004, 0x1a96: 0x0003, 0x1a97: 0x0003,
0x1a90: 0x0002,
0x1a95: 0x0002, 0x1a96: 0x0003, 0x1a97: 0x0003,
0x1a98: 0x0003, 0x1a99: 0x0003,
// Block 0x6b, offset 0x1ac0
0x1aef: 0x0001,
@ -1275,9 +1271,9 @@ var stringWidthValues = [15872]uint8{
0x3604: 0x0001, 0x3605: 0x0001,
0x3606: 0x0001, 0x3607: 0x0001, 0x3608: 0x0001, 0x3609: 0x0001, 0x360a: 0x0001,
// Block 0xd9, offset 0x3640
0x3644: 0x0004,
0x3644: 0x0002,
// Block 0xda, offset 0x3680
0x368f: 0x0004,
0x368f: 0x0002,
// Block 0xdb, offset 0x36c0
0x36c0: 0x0003, 0x36c1: 0x0003, 0x36c2: 0x0003, 0x36c3: 0x0003, 0x36c4: 0x0003, 0x36c5: 0x0003,
0x36c6: 0x0003, 0x36c7: 0x0003, 0x36c8: 0x0003, 0x36c9: 0x0003, 0x36ca: 0x0003,
@ -1304,252 +1300,228 @@ var stringWidthValues = [15872]uint8{
// Block 0xdd, offset 0x3740
0x3740: 0x0003, 0x3741: 0x0003, 0x3742: 0x0003, 0x3743: 0x0003, 0x3744: 0x0003, 0x3745: 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,
0x3752: 0x0004, 0x3753: 0x0004, 0x3754: 0x0004, 0x3755: 0x0004, 0x3756: 0x0004, 0x3757: 0x0004,
0x3758: 0x0004, 0x3759: 0x0004, 0x375a: 0x0004, 0x375b: 0x0003, 0x375c: 0x0003, 0x375d: 0x0003,
0x374c: 0x0003, 0x374d: 0x0003, 0x374e: 0x0002, 0x374f: 0x0003, 0x3750: 0x0003, 0x3751: 0x0002,
0x3752: 0x0002, 0x3753: 0x0002, 0x3754: 0x0002, 0x3755: 0x0002, 0x3756: 0x0002, 0x3757: 0x0002,
0x3758: 0x0002, 0x3759: 0x0002, 0x375a: 0x0002, 0x375b: 0x0003, 0x375c: 0x0003, 0x375d: 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,
0x376a: 0x0003, 0x376b: 0x0003, 0x376c: 0x0003,
// Block 0xde, offset 0x3780
0x37a6: 0x0005, 0x37a7: 0x0005, 0x37a8: 0x0005, 0x37a9: 0x0005,
0x37aa: 0x0005, 0x37ab: 0x0005, 0x37ac: 0x0005, 0x37ad: 0x0005, 0x37ae: 0x0005, 0x37af: 0x0005,
0x37b0: 0x0005, 0x37b1: 0x0005, 0x37b2: 0x0005, 0x37b3: 0x0005, 0x37b4: 0x0005, 0x37b5: 0x0005,
0x37b6: 0x0005, 0x37b7: 0x0005, 0x37b8: 0x0005, 0x37b9: 0x0005, 0x37ba: 0x0005, 0x37bb: 0x0005,
0x37bc: 0x0005, 0x37bd: 0x0005, 0x37be: 0x0005, 0x37bf: 0x0005,
0x37a6: 0x0002, 0x37a7: 0x0002, 0x37a8: 0x0002, 0x37a9: 0x0002,
0x37aa: 0x0002, 0x37ab: 0x0002, 0x37ac: 0x0002, 0x37ad: 0x0002, 0x37ae: 0x0002, 0x37af: 0x0002,
0x37b0: 0x0002, 0x37b1: 0x0002, 0x37b2: 0x0002, 0x37b3: 0x0002, 0x37b4: 0x0002, 0x37b5: 0x0002,
0x37b6: 0x0002, 0x37b7: 0x0002, 0x37b8: 0x0002, 0x37b9: 0x0002, 0x37ba: 0x0002, 0x37bb: 0x0002,
0x37bc: 0x0002, 0x37bd: 0x0002, 0x37be: 0x0002, 0x37bf: 0x0002,
// Block 0xdf, offset 0x37c0
0x37c0: 0x0002, 0x37c1: 0x0004, 0x37c2: 0x0002,
0x37c0: 0x0002, 0x37c1: 0x0002, 0x37c2: 0x0002,
0x37d0: 0x0002, 0x37d1: 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,
0x37e4: 0x0002, 0x37e5: 0x0002, 0x37e6: 0x0002, 0x37e7: 0x0002, 0x37e8: 0x0002, 0x37e9: 0x0002,
0x37ea: 0x0002, 0x37eb: 0x0002, 0x37ec: 0x0002, 0x37ed: 0x0002, 0x37ee: 0x0002, 0x37ef: 0x0004,
0x37f0: 0x0002, 0x37f1: 0x0002, 0x37f2: 0x0004, 0x37f3: 0x0004, 0x37f4: 0x0004, 0x37f5: 0x0004,
0x37f6: 0x0004, 0x37f7: 0x0002, 0x37f8: 0x0004, 0x37f9: 0x0004, 0x37fa: 0x0004, 0x37fb: 0x0002,
0x37ea: 0x0002, 0x37eb: 0x0002, 0x37ec: 0x0002, 0x37ed: 0x0002, 0x37ee: 0x0002, 0x37ef: 0x0002,
0x37f0: 0x0002, 0x37f1: 0x0002, 0x37f2: 0x0002, 0x37f3: 0x0002, 0x37f4: 0x0002, 0x37f5: 0x0002,
0x37f6: 0x0002, 0x37f7: 0x0002, 0x37f8: 0x0002, 0x37f9: 0x0002, 0x37fa: 0x0002, 0x37fb: 0x0002,
// Block 0xe0, offset 0x3800
0x3800: 0x0002, 0x3801: 0x0002, 0x3802: 0x0002, 0x3803: 0x0002, 0x3804: 0x0002, 0x3805: 0x0002,
0x3806: 0x0002, 0x3807: 0x0002, 0x3808: 0x0002,
0x3810: 0x0004, 0x3811: 0x0004,
0x3810: 0x0002, 0x3811: 0x0002,
0x3820: 0x0002, 0x3821: 0x0002, 0x3822: 0x0002, 0x3823: 0x0002,
0x3824: 0x0002, 0x3825: 0x0002,
// Block 0xe1, offset 0x3840
0x3840: 0x0004, 0x3841: 0x0004, 0x3842: 0x0004, 0x3843: 0x0004, 0x3844: 0x0004, 0x3845: 0x0004,
0x3846: 0x0004, 0x3847: 0x0004, 0x3848: 0x0004, 0x3849: 0x0004, 0x384a: 0x0004, 0x384b: 0x0004,
0x384c: 0x0004, 0x384d: 0x0004, 0x384e: 0x0004, 0x384f: 0x0004, 0x3850: 0x0004, 0x3851: 0x0004,
0x3852: 0x0004, 0x3853: 0x0004, 0x3854: 0x0004, 0x3855: 0x0004, 0x3856: 0x0004, 0x3857: 0x0004,
0x3858: 0x0004, 0x3859: 0x0004, 0x385a: 0x0004, 0x385b: 0x0004, 0x385c: 0x0004, 0x385d: 0x0004,
0x385e: 0x0004, 0x385f: 0x0004, 0x3860: 0x0004,
0x386d: 0x0004, 0x386e: 0x0004, 0x386f: 0x0004,
0x3870: 0x0004, 0x3871: 0x0004, 0x3872: 0x0004, 0x3873: 0x0004, 0x3874: 0x0004, 0x3875: 0x0004,
0x3877: 0x0004, 0x3878: 0x0004, 0x3879: 0x0004, 0x387a: 0x0004, 0x387b: 0x0004,
0x387c: 0x0004, 0x387d: 0x0004, 0x387e: 0x0004, 0x387f: 0x0004,
0x3840: 0x0002, 0x3841: 0x0002, 0x3842: 0x0002, 0x3843: 0x0002, 0x3844: 0x0002, 0x3845: 0x0002,
0x3846: 0x0002, 0x3847: 0x0002, 0x3848: 0x0002, 0x3849: 0x0002, 0x384a: 0x0002, 0x384b: 0x0002,
0x384c: 0x0002, 0x384d: 0x0002, 0x384e: 0x0002, 0x384f: 0x0002, 0x3850: 0x0002, 0x3851: 0x0002,
0x3852: 0x0002, 0x3853: 0x0002, 0x3854: 0x0002, 0x3855: 0x0002, 0x3856: 0x0002, 0x3857: 0x0002,
0x3858: 0x0002, 0x3859: 0x0002, 0x385a: 0x0002, 0x385b: 0x0002, 0x385c: 0x0002, 0x385d: 0x0002,
0x385e: 0x0002, 0x385f: 0x0002, 0x3860: 0x0002,
0x386d: 0x0002, 0x386e: 0x0002, 0x386f: 0x0002,
0x3870: 0x0002, 0x3871: 0x0002, 0x3872: 0x0002, 0x3873: 0x0002, 0x3874: 0x0002, 0x3875: 0x0002,
0x3877: 0x0002, 0x3878: 0x0002, 0x3879: 0x0002, 0x387a: 0x0002, 0x387b: 0x0002,
0x387c: 0x0002, 0x387d: 0x0002, 0x387e: 0x0002, 0x387f: 0x0002,
// Block 0xe2, offset 0x3880
0x3880: 0x0004, 0x3881: 0x0004, 0x3882: 0x0004, 0x3883: 0x0004, 0x3884: 0x0004, 0x3885: 0x0004,
0x3886: 0x0004, 0x3887: 0x0004, 0x3888: 0x0004, 0x3889: 0x0004, 0x388a: 0x0004, 0x388b: 0x0004,
0x388c: 0x0004, 0x388d: 0x0004, 0x388e: 0x0004, 0x388f: 0x0004, 0x3890: 0x0004, 0x3891: 0x0004,
0x3892: 0x0004, 0x3893: 0x0004, 0x3894: 0x0004, 0x3895: 0x0004, 0x3896: 0x0004, 0x3897: 0x0004,
0x3898: 0x0004, 0x3899: 0x0004, 0x389a: 0x0004, 0x389b: 0x0004, 0x389c: 0x0004, 0x389d: 0x0004,
0x389e: 0x0004, 0x389f: 0x0004, 0x38a0: 0x0004, 0x38a1: 0x0004, 0x38a2: 0x0004, 0x38a3: 0x0004,
0x38a4: 0x0004, 0x38a5: 0x0004, 0x38a6: 0x0004, 0x38a7: 0x0004, 0x38a8: 0x0004, 0x38a9: 0x0004,
0x38aa: 0x0004, 0x38ab: 0x0004, 0x38ac: 0x0004, 0x38ad: 0x0004, 0x38ae: 0x0004, 0x38af: 0x0004,
0x38b0: 0x0004, 0x38b1: 0x0004, 0x38b2: 0x0004, 0x38b3: 0x0004, 0x38b4: 0x0004, 0x38b5: 0x0004,
0x38b6: 0x0004, 0x38b7: 0x0004, 0x38b8: 0x0004, 0x38b9: 0x0004, 0x38ba: 0x0004, 0x38bb: 0x0004,
0x38bc: 0x0004, 0x38be: 0x0004, 0x38bf: 0x0004,
0x3880: 0x0002, 0x3881: 0x0002, 0x3882: 0x0002, 0x3883: 0x0002, 0x3884: 0x0002, 0x3885: 0x0002,
0x3886: 0x0002, 0x3887: 0x0002, 0x3888: 0x0002, 0x3889: 0x0002, 0x388a: 0x0002, 0x388b: 0x0002,
0x388c: 0x0002, 0x388d: 0x0002, 0x388e: 0x0002, 0x388f: 0x0002, 0x3890: 0x0002, 0x3891: 0x0002,
0x3892: 0x0002, 0x3893: 0x0002, 0x3894: 0x0002, 0x3895: 0x0002, 0x3896: 0x0002, 0x3897: 0x0002,
0x3898: 0x0002, 0x3899: 0x0002, 0x389a: 0x0002, 0x389b: 0x0002, 0x389c: 0x0002, 0x389d: 0x0002,
0x389e: 0x0002, 0x389f: 0x0002, 0x38a0: 0x0002, 0x38a1: 0x0002, 0x38a2: 0x0002, 0x38a3: 0x0002,
0x38a4: 0x0002, 0x38a5: 0x0002, 0x38a6: 0x0002, 0x38a7: 0x0002, 0x38a8: 0x0002, 0x38a9: 0x0002,
0x38aa: 0x0002, 0x38ab: 0x0002, 0x38ac: 0x0002, 0x38ad: 0x0002, 0x38ae: 0x0002, 0x38af: 0x0002,
0x38b0: 0x0002, 0x38b1: 0x0002, 0x38b2: 0x0002, 0x38b3: 0x0002, 0x38b4: 0x0002, 0x38b5: 0x0002,
0x38b6: 0x0002, 0x38b7: 0x0002, 0x38b8: 0x0002, 0x38b9: 0x0002, 0x38ba: 0x0002, 0x38bb: 0x0002,
0x38bc: 0x0002, 0x38be: 0x0002, 0x38bf: 0x0002,
// Block 0xe3, offset 0x38c0
0x38c0: 0x0004, 0x38c1: 0x0004, 0x38c2: 0x0004, 0x38c3: 0x0004, 0x38c4: 0x0004, 0x38c5: 0x0004,
0x38c6: 0x0004, 0x38c7: 0x0004, 0x38c8: 0x0004, 0x38c9: 0x0004, 0x38ca: 0x0004, 0x38cb: 0x0004,
0x38cc: 0x0004, 0x38cd: 0x0004, 0x38ce: 0x0004, 0x38cf: 0x0004, 0x38d0: 0x0004, 0x38d1: 0x0004,
0x38d2: 0x0004, 0x38d3: 0x0004,
0x38e0: 0x0004, 0x38e1: 0x0004, 0x38e2: 0x0004, 0x38e3: 0x0004,
0x38e4: 0x0004, 0x38e5: 0x0004, 0x38e6: 0x0004, 0x38e7: 0x0004, 0x38e8: 0x0004, 0x38e9: 0x0004,
0x38ea: 0x0004, 0x38eb: 0x0004, 0x38ec: 0x0004, 0x38ed: 0x0004, 0x38ee: 0x0004, 0x38ef: 0x0004,
0x38f0: 0x0004, 0x38f1: 0x0004, 0x38f2: 0x0004, 0x38f3: 0x0004, 0x38f4: 0x0004, 0x38f5: 0x0004,
0x38f6: 0x0004, 0x38f7: 0x0004, 0x38f8: 0x0004, 0x38f9: 0x0004, 0x38fa: 0x0004, 0x38fb: 0x0004,
0x38fc: 0x0004, 0x38fd: 0x0004, 0x38fe: 0x0004, 0x38ff: 0x0004,
0x38c0: 0x0002, 0x38c1: 0x0002, 0x38c2: 0x0002, 0x38c3: 0x0002, 0x38c4: 0x0002, 0x38c5: 0x0002,
0x38c6: 0x0002, 0x38c7: 0x0002, 0x38c8: 0x0002, 0x38c9: 0x0002, 0x38ca: 0x0002, 0x38cb: 0x0002,
0x38cc: 0x0002, 0x38cd: 0x0002, 0x38ce: 0x0002, 0x38cf: 0x0002, 0x38d0: 0x0002, 0x38d1: 0x0002,
0x38d2: 0x0002, 0x38d3: 0x0002,
0x38e0: 0x0002, 0x38e1: 0x0002, 0x38e2: 0x0002, 0x38e3: 0x0002,
0x38e4: 0x0002, 0x38e5: 0x0002, 0x38e6: 0x0002, 0x38e7: 0x0002, 0x38e8: 0x0002, 0x38e9: 0x0002,
0x38ea: 0x0002, 0x38eb: 0x0002, 0x38ec: 0x0002, 0x38ed: 0x0002, 0x38ee: 0x0002, 0x38ef: 0x0002,
0x38f0: 0x0002, 0x38f1: 0x0002, 0x38f2: 0x0002, 0x38f3: 0x0002, 0x38f4: 0x0002, 0x38f5: 0x0002,
0x38f6: 0x0002, 0x38f7: 0x0002, 0x38f8: 0x0002, 0x38f9: 0x0002, 0x38fa: 0x0002, 0x38fb: 0x0002,
0x38fc: 0x0002, 0x38fd: 0x0002, 0x38fe: 0x0002, 0x38ff: 0x0002,
// Block 0xe4, offset 0x3900
0x3900: 0x0004, 0x3901: 0x0004, 0x3902: 0x0004, 0x3903: 0x0004, 0x3904: 0x0004, 0x3905: 0x0004,
0x3906: 0x0004, 0x3907: 0x0004, 0x3908: 0x0004, 0x3909: 0x0004, 0x390a: 0x0004,
0x390f: 0x0004, 0x3910: 0x0004, 0x3911: 0x0004,
0x3912: 0x0004, 0x3913: 0x0004,
0x3920: 0x0004, 0x3921: 0x0004, 0x3922: 0x0004, 0x3923: 0x0004,
0x3924: 0x0004, 0x3925: 0x0004, 0x3926: 0x0004, 0x3927: 0x0004, 0x3928: 0x0004, 0x3929: 0x0004,
0x392a: 0x0004, 0x392b: 0x0004, 0x392c: 0x0004, 0x392d: 0x0004, 0x392e: 0x0004, 0x392f: 0x0004,
0x3930: 0x0004, 0x3934: 0x0004,
0x3938: 0x0004, 0x3939: 0x0004, 0x393a: 0x0004, 0x393b: 0x0002,
0x3900: 0x0002, 0x3901: 0x0002, 0x3902: 0x0002, 0x3903: 0x0002, 0x3904: 0x0002, 0x3905: 0x0002,
0x3906: 0x0002, 0x3907: 0x0002, 0x3908: 0x0002, 0x3909: 0x0002, 0x390a: 0x0002,
0x390f: 0x0002, 0x3910: 0x0002, 0x3911: 0x0002,
0x3912: 0x0002, 0x3913: 0x0002,
0x3920: 0x0002, 0x3921: 0x0002, 0x3922: 0x0002, 0x3923: 0x0002,
0x3924: 0x0002, 0x3925: 0x0002, 0x3926: 0x0002, 0x3927: 0x0002, 0x3928: 0x0002, 0x3929: 0x0002,
0x392a: 0x0002, 0x392b: 0x0002, 0x392c: 0x0002, 0x392d: 0x0002, 0x392e: 0x0002, 0x392f: 0x0002,
0x3930: 0x0002, 0x3934: 0x0002,
0x3938: 0x0002, 0x3939: 0x0002, 0x393a: 0x0002, 0x393b: 0x0002,
0x393c: 0x0002, 0x393d: 0x0002, 0x393e: 0x0002, 0x393f: 0x0002,
// Block 0xe5, offset 0x3940
0x3940: 0x0004, 0x3941: 0x0004, 0x3942: 0x0004, 0x3943: 0x0004, 0x3944: 0x0004, 0x3945: 0x0004,
0x3946: 0x0004, 0x3947: 0x0004, 0x3948: 0x0004, 0x3949: 0x0004, 0x394a: 0x0004, 0x394b: 0x0004,
0x394c: 0x0004, 0x394d: 0x0004, 0x394e: 0x0004, 0x394f: 0x0004, 0x3950: 0x0004, 0x3951: 0x0004,
0x3952: 0x0004, 0x3953: 0x0004, 0x3954: 0x0004, 0x3955: 0x0004, 0x3956: 0x0004, 0x3957: 0x0004,
0x3958: 0x0004, 0x3959: 0x0004, 0x395a: 0x0004, 0x395b: 0x0004, 0x395c: 0x0004, 0x395d: 0x0004,
0x395e: 0x0004, 0x395f: 0x0004, 0x3960: 0x0004, 0x3961: 0x0004, 0x3962: 0x0004, 0x3963: 0x0004,
0x3964: 0x0004, 0x3965: 0x0004, 0x3966: 0x0004, 0x3967: 0x0004, 0x3968: 0x0004, 0x3969: 0x0004,
0x396a: 0x0004, 0x396b: 0x0004, 0x396c: 0x0004, 0x396d: 0x0004, 0x396e: 0x0004, 0x396f: 0x0004,
0x3970: 0x0004, 0x3971: 0x0004, 0x3972: 0x0004, 0x3973: 0x0004, 0x3974: 0x0004, 0x3975: 0x0004,
0x3976: 0x0004, 0x3977: 0x0004, 0x3978: 0x0004, 0x3979: 0x0004, 0x397a: 0x0004, 0x397b: 0x0004,
0x397c: 0x0004, 0x397d: 0x0004, 0x397e: 0x0004,
0x3940: 0x0002, 0x3941: 0x0002, 0x3942: 0x0002, 0x3943: 0x0002, 0x3944: 0x0002, 0x3945: 0x0002,
0x3946: 0x0002, 0x3947: 0x0002, 0x3948: 0x0002, 0x3949: 0x0002, 0x394a: 0x0002, 0x394b: 0x0002,
0x394c: 0x0002, 0x394d: 0x0002, 0x394e: 0x0002, 0x394f: 0x0002, 0x3950: 0x0002, 0x3951: 0x0002,
0x3952: 0x0002, 0x3953: 0x0002, 0x3954: 0x0002, 0x3955: 0x0002, 0x3956: 0x0002, 0x3957: 0x0002,
0x3958: 0x0002, 0x3959: 0x0002, 0x395a: 0x0002, 0x395b: 0x0002, 0x395c: 0x0002, 0x395d: 0x0002,
0x395e: 0x0002, 0x395f: 0x0002, 0x3960: 0x0002, 0x3961: 0x0002, 0x3962: 0x0002, 0x3963: 0x0002,
0x3964: 0x0002, 0x3965: 0x0002, 0x3966: 0x0002, 0x3967: 0x0002, 0x3968: 0x0002, 0x3969: 0x0002,
0x396a: 0x0002, 0x396b: 0x0002, 0x396c: 0x0002, 0x396d: 0x0002, 0x396e: 0x0002, 0x396f: 0x0002,
0x3970: 0x0002, 0x3971: 0x0002, 0x3972: 0x0002, 0x3973: 0x0002, 0x3974: 0x0002, 0x3975: 0x0002,
0x3976: 0x0002, 0x3977: 0x0002, 0x3978: 0x0002, 0x3979: 0x0002, 0x397a: 0x0002, 0x397b: 0x0002,
0x397c: 0x0002, 0x397d: 0x0002, 0x397e: 0x0002,
// Block 0xe6, offset 0x3980
0x3980: 0x0004, 0x3982: 0x0004, 0x3983: 0x0004, 0x3984: 0x0004, 0x3985: 0x0004,
0x3986: 0x0004, 0x3987: 0x0004, 0x3988: 0x0004, 0x3989: 0x0004, 0x398a: 0x0004, 0x398b: 0x0004,
0x398c: 0x0004, 0x398d: 0x0004, 0x398e: 0x0004, 0x398f: 0x0004, 0x3990: 0x0004, 0x3991: 0x0004,
0x3992: 0x0004, 0x3993: 0x0004, 0x3994: 0x0004, 0x3995: 0x0004, 0x3996: 0x0004, 0x3997: 0x0004,
0x3998: 0x0004, 0x3999: 0x0004, 0x399a: 0x0004, 0x399b: 0x0004, 0x399c: 0x0004, 0x399d: 0x0004,
0x399e: 0x0004, 0x399f: 0x0004, 0x39a0: 0x0004, 0x39a1: 0x0004, 0x39a2: 0x0004, 0x39a3: 0x0004,
0x39a4: 0x0004, 0x39a5: 0x0004, 0x39a6: 0x0004, 0x39a7: 0x0004, 0x39a8: 0x0004, 0x39a9: 0x0004,
0x39aa: 0x0004, 0x39ab: 0x0004, 0x39ac: 0x0004, 0x39ad: 0x0004, 0x39ae: 0x0004, 0x39af: 0x0004,
0x39b0: 0x0004, 0x39b1: 0x0004, 0x39b2: 0x0004, 0x39b3: 0x0004, 0x39b4: 0x0004, 0x39b5: 0x0004,
0x39b6: 0x0004, 0x39b7: 0x0004, 0x39b8: 0x0004, 0x39b9: 0x0004, 0x39ba: 0x0004, 0x39bb: 0x0004,
0x39bc: 0x0004, 0x39bd: 0x0004, 0x39be: 0x0004, 0x39bf: 0x0004,
0x3980: 0x0002, 0x3982: 0x0002, 0x3983: 0x0002, 0x3984: 0x0002, 0x3985: 0x0002,
0x3986: 0x0002, 0x3987: 0x0002, 0x3988: 0x0002, 0x3989: 0x0002, 0x398a: 0x0002, 0x398b: 0x0002,
0x398c: 0x0002, 0x398d: 0x0002, 0x398e: 0x0002, 0x398f: 0x0002, 0x3990: 0x0002, 0x3991: 0x0002,
0x3992: 0x0002, 0x3993: 0x0002, 0x3994: 0x0002, 0x3995: 0x0002, 0x3996: 0x0002, 0x3997: 0x0002,
0x3998: 0x0002, 0x3999: 0x0002, 0x399a: 0x0002, 0x399b: 0x0002, 0x399c: 0x0002, 0x399d: 0x0002,
0x399e: 0x0002, 0x399f: 0x0002, 0x39a0: 0x0002, 0x39a1: 0x0002, 0x39a2: 0x0002, 0x39a3: 0x0002,
0x39a4: 0x0002, 0x39a5: 0x0002, 0x39a6: 0x0002, 0x39a7: 0x0002, 0x39a8: 0x0002, 0x39a9: 0x0002,
0x39aa: 0x0002, 0x39ab: 0x0002, 0x39ac: 0x0002, 0x39ad: 0x0002, 0x39ae: 0x0002, 0x39af: 0x0002,
0x39b0: 0x0002, 0x39b1: 0x0002, 0x39b2: 0x0002, 0x39b3: 0x0002, 0x39b4: 0x0002, 0x39b5: 0x0002,
0x39b6: 0x0002, 0x39b7: 0x0002, 0x39b8: 0x0002, 0x39b9: 0x0002, 0x39ba: 0x0002, 0x39bb: 0x0002,
0x39bc: 0x0002, 0x39bd: 0x0002, 0x39be: 0x0002, 0x39bf: 0x0002,
// Block 0xe7, offset 0x39c0
0x39c0: 0x0004, 0x39c1: 0x0004, 0x39c2: 0x0004, 0x39c3: 0x0004, 0x39c4: 0x0004, 0x39c5: 0x0004,
0x39c6: 0x0004, 0x39c7: 0x0004, 0x39c8: 0x0004, 0x39c9: 0x0004, 0x39ca: 0x0004, 0x39cb: 0x0004,
0x39cc: 0x0004, 0x39cd: 0x0004, 0x39ce: 0x0004, 0x39cf: 0x0004, 0x39d0: 0x0004, 0x39d1: 0x0004,
0x39d2: 0x0004, 0x39d3: 0x0004, 0x39d4: 0x0004, 0x39d5: 0x0004, 0x39d6: 0x0004, 0x39d7: 0x0004,
0x39d8: 0x0004, 0x39d9: 0x0004, 0x39da: 0x0004, 0x39db: 0x0004, 0x39dc: 0x0004, 0x39dd: 0x0004,
0x39de: 0x0004, 0x39df: 0x0004, 0x39e0: 0x0004, 0x39e1: 0x0004, 0x39e2: 0x0004, 0x39e3: 0x0004,
0x39e4: 0x0004, 0x39e5: 0x0004, 0x39e6: 0x0004, 0x39e7: 0x0004, 0x39e8: 0x0004, 0x39e9: 0x0004,
0x39ea: 0x0004, 0x39eb: 0x0004, 0x39ec: 0x0004, 0x39ed: 0x0004, 0x39ee: 0x0004, 0x39ef: 0x0004,
0x39f0: 0x0004, 0x39f1: 0x0004, 0x39f2: 0x0004, 0x39f3: 0x0004, 0x39f4: 0x0004, 0x39f5: 0x0004,
0x39f6: 0x0004, 0x39f7: 0x0004, 0x39f8: 0x0004, 0x39f9: 0x0004, 0x39fa: 0x0004, 0x39fb: 0x0004,
0x39fc: 0x0004, 0x39fd: 0x0004, 0x39fe: 0x0004, 0x39ff: 0x0004,
0x39c0: 0x0002, 0x39c1: 0x0002, 0x39c2: 0x0002, 0x39c3: 0x0002, 0x39c4: 0x0002, 0x39c5: 0x0002,
0x39c6: 0x0002, 0x39c7: 0x0002, 0x39c8: 0x0002, 0x39c9: 0x0002, 0x39ca: 0x0002, 0x39cb: 0x0002,
0x39cc: 0x0002, 0x39cd: 0x0002, 0x39ce: 0x0002, 0x39cf: 0x0002, 0x39d0: 0x0002, 0x39d1: 0x0002,
0x39d2: 0x0002, 0x39d3: 0x0002, 0x39d4: 0x0002, 0x39d5: 0x0002, 0x39d6: 0x0002, 0x39d7: 0x0002,
0x39d8: 0x0002, 0x39d9: 0x0002, 0x39da: 0x0002, 0x39db: 0x0002, 0x39dc: 0x0002, 0x39dd: 0x0002,
0x39de: 0x0002, 0x39df: 0x0002, 0x39e0: 0x0002, 0x39e1: 0x0002, 0x39e2: 0x0002, 0x39e3: 0x0002,
0x39e4: 0x0002, 0x39e5: 0x0002, 0x39e6: 0x0002, 0x39e7: 0x0002, 0x39e8: 0x0002, 0x39e9: 0x0002,
0x39ea: 0x0002, 0x39eb: 0x0002, 0x39ec: 0x0002, 0x39ed: 0x0002, 0x39ee: 0x0002, 0x39ef: 0x0002,
0x39f0: 0x0002, 0x39f1: 0x0002, 0x39f2: 0x0002, 0x39f3: 0x0002, 0x39f4: 0x0002, 0x39f5: 0x0002,
0x39f6: 0x0002, 0x39f7: 0x0002, 0x39f8: 0x0002, 0x39f9: 0x0002, 0x39fa: 0x0002, 0x39fb: 0x0002,
0x39fc: 0x0002, 0x39ff: 0x0002,
// Block 0xe8, offset 0x3a00
0x3a00: 0x0004, 0x3a01: 0x0004, 0x3a02: 0x0004, 0x3a03: 0x0004, 0x3a04: 0x0004, 0x3a05: 0x0004,
0x3a06: 0x0004, 0x3a07: 0x0004, 0x3a08: 0x0004, 0x3a09: 0x0004, 0x3a0a: 0x0004, 0x3a0b: 0x0004,
0x3a0c: 0x0004, 0x3a0d: 0x0004, 0x3a0e: 0x0004, 0x3a0f: 0x0004, 0x3a10: 0x0004, 0x3a11: 0x0004,
0x3a12: 0x0004, 0x3a13: 0x0004, 0x3a14: 0x0004, 0x3a15: 0x0004, 0x3a16: 0x0004, 0x3a17: 0x0004,
0x3a18: 0x0004, 0x3a19: 0x0004, 0x3a1a: 0x0004, 0x3a1b: 0x0004, 0x3a1c: 0x0004, 0x3a1d: 0x0004,
0x3a1e: 0x0004, 0x3a1f: 0x0004, 0x3a20: 0x0004, 0x3a21: 0x0004, 0x3a22: 0x0004, 0x3a23: 0x0004,
0x3a24: 0x0004, 0x3a25: 0x0004, 0x3a26: 0x0004, 0x3a27: 0x0004, 0x3a28: 0x0004, 0x3a29: 0x0004,
0x3a2a: 0x0004, 0x3a2b: 0x0004, 0x3a2c: 0x0004, 0x3a2d: 0x0004, 0x3a2e: 0x0004, 0x3a2f: 0x0004,
0x3a30: 0x0004, 0x3a31: 0x0004, 0x3a32: 0x0004, 0x3a33: 0x0004, 0x3a34: 0x0004, 0x3a35: 0x0004,
0x3a36: 0x0004, 0x3a37: 0x0004, 0x3a38: 0x0004, 0x3a39: 0x0004, 0x3a3a: 0x0004, 0x3a3b: 0x0004,
0x3a3c: 0x0004, 0x3a3f: 0x0004,
0x3a00: 0x0002, 0x3a01: 0x0002, 0x3a02: 0x0002, 0x3a03: 0x0002, 0x3a04: 0x0002, 0x3a05: 0x0002,
0x3a06: 0x0002, 0x3a07: 0x0002, 0x3a08: 0x0002, 0x3a09: 0x0002, 0x3a0a: 0x0002, 0x3a0b: 0x0002,
0x3a0c: 0x0002, 0x3a0d: 0x0002, 0x3a0e: 0x0002, 0x3a0f: 0x0002, 0x3a10: 0x0002, 0x3a11: 0x0002,
0x3a12: 0x0002, 0x3a13: 0x0002, 0x3a14: 0x0002, 0x3a15: 0x0002, 0x3a16: 0x0002, 0x3a17: 0x0002,
0x3a18: 0x0002, 0x3a19: 0x0002, 0x3a1a: 0x0002, 0x3a1b: 0x0002, 0x3a1c: 0x0002, 0x3a1d: 0x0002,
0x3a1e: 0x0002, 0x3a1f: 0x0002, 0x3a20: 0x0002, 0x3a21: 0x0002, 0x3a22: 0x0002, 0x3a23: 0x0002,
0x3a24: 0x0002, 0x3a25: 0x0002, 0x3a26: 0x0002, 0x3a27: 0x0002, 0x3a28: 0x0002, 0x3a29: 0x0002,
0x3a2a: 0x0002, 0x3a2b: 0x0002, 0x3a2c: 0x0002, 0x3a2d: 0x0002, 0x3a2e: 0x0002, 0x3a2f: 0x0002,
0x3a30: 0x0002, 0x3a31: 0x0002, 0x3a32: 0x0002, 0x3a33: 0x0002, 0x3a34: 0x0002, 0x3a35: 0x0002,
0x3a36: 0x0002, 0x3a37: 0x0002, 0x3a38: 0x0002, 0x3a39: 0x0002, 0x3a3a: 0x0002, 0x3a3b: 0x0002,
0x3a3c: 0x0002, 0x3a3d: 0x0002,
// Block 0xe9, offset 0x3a40
0x3a40: 0x0004, 0x3a41: 0x0004, 0x3a42: 0x0004, 0x3a43: 0x0004, 0x3a44: 0x0004, 0x3a45: 0x0004,
0x3a46: 0x0004, 0x3a47: 0x0004, 0x3a48: 0x0004, 0x3a49: 0x0004, 0x3a4a: 0x0004, 0x3a4b: 0x0004,
0x3a4c: 0x0004, 0x3a4d: 0x0004, 0x3a4e: 0x0004, 0x3a4f: 0x0004, 0x3a50: 0x0004, 0x3a51: 0x0004,
0x3a52: 0x0004, 0x3a53: 0x0004, 0x3a54: 0x0004, 0x3a55: 0x0004, 0x3a56: 0x0004, 0x3a57: 0x0004,
0x3a58: 0x0004, 0x3a59: 0x0004, 0x3a5a: 0x0004, 0x3a5b: 0x0004, 0x3a5c: 0x0004, 0x3a5d: 0x0004,
0x3a5e: 0x0004, 0x3a5f: 0x0004, 0x3a60: 0x0004, 0x3a61: 0x0004, 0x3a62: 0x0004, 0x3a63: 0x0004,
0x3a64: 0x0004, 0x3a65: 0x0004, 0x3a66: 0x0004, 0x3a67: 0x0004, 0x3a68: 0x0004, 0x3a69: 0x0004,
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,
0x3a4b: 0x0002,
0x3a4c: 0x0002, 0x3a4d: 0x0002, 0x3a4e: 0x0002, 0x3a50: 0x0002, 0x3a51: 0x0002,
0x3a52: 0x0002, 0x3a53: 0x0002, 0x3a54: 0x0002, 0x3a55: 0x0002, 0x3a56: 0x0002, 0x3a57: 0x0002,
0x3a58: 0x0002, 0x3a59: 0x0002, 0x3a5a: 0x0002, 0x3a5b: 0x0002, 0x3a5c: 0x0002, 0x3a5d: 0x0002,
0x3a5e: 0x0002, 0x3a5f: 0x0002, 0x3a60: 0x0002, 0x3a61: 0x0002, 0x3a62: 0x0002, 0x3a63: 0x0002,
0x3a64: 0x0002, 0x3a65: 0x0002, 0x3a66: 0x0002, 0x3a67: 0x0002,
0x3a7a: 0x0002,
// Block 0xea, offset 0x3a80
0x3a8b: 0x0004,
0x3a8c: 0x0004, 0x3a8d: 0x0004, 0x3a8e: 0x0004, 0x3a90: 0x0004, 0x3a91: 0x0004,
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,
0x3a95: 0x0002, 0x3a96: 0x0002,
0x3aa4: 0x0002,
// Block 0xeb, offset 0x3ac0
0x3ad5: 0x0004, 0x3ad6: 0x0004,
0x3ae4: 0x0004,
0x3afb: 0x0002,
0x3afc: 0x0002, 0x3afd: 0x0002, 0x3afe: 0x0002, 0x3aff: 0x0002,
// Block 0xec, offset 0x3b00
0x3b3b: 0x0004,
0x3b3c: 0x0004, 0x3b3d: 0x0004, 0x3b3e: 0x0004, 0x3b3f: 0x0004,
0x3b00: 0x0002, 0x3b01: 0x0002, 0x3b02: 0x0002, 0x3b03: 0x0002, 0x3b04: 0x0002, 0x3b05: 0x0002,
0x3b06: 0x0002, 0x3b07: 0x0002, 0x3b08: 0x0002, 0x3b09: 0x0002, 0x3b0a: 0x0002, 0x3b0b: 0x0002,
0x3b0c: 0x0002, 0x3b0d: 0x0002, 0x3b0e: 0x0002, 0x3b0f: 0x0002,
// Block 0xed, offset 0x3b40
0x3b40: 0x0004, 0x3b41: 0x0004, 0x3b42: 0x0004, 0x3b43: 0x0004, 0x3b44: 0x0004, 0x3b45: 0x0004,
0x3b46: 0x0004, 0x3b47: 0x0004, 0x3b48: 0x0004, 0x3b49: 0x0004, 0x3b4a: 0x0004, 0x3b4b: 0x0004,
0x3b4c: 0x0004, 0x3b4d: 0x0004, 0x3b4e: 0x0004, 0x3b4f: 0x0004,
0x3b40: 0x0002, 0x3b41: 0x0002, 0x3b42: 0x0002, 0x3b43: 0x0002, 0x3b44: 0x0002, 0x3b45: 0x0002,
0x3b4c: 0x0002, 0x3b50: 0x0002, 0x3b51: 0x0002,
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
0x3b80: 0x0004, 0x3b81: 0x0004, 0x3b82: 0x0004, 0x3b83: 0x0004, 0x3b84: 0x0004, 0x3b85: 0x0004,
0x3b8c: 0x0004, 0x3b90: 0x0004, 0x3b91: 0x0004,
0x3b92: 0x0004, 0x3b95: 0x0004, 0x3b96: 0x0004, 0x3b97: 0x0004,
0x3b9c: 0x0004, 0x3b9d: 0x0004,
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,
0x3ba0: 0x0002, 0x3ba1: 0x0002, 0x3ba2: 0x0002, 0x3ba3: 0x0002,
0x3ba4: 0x0002, 0x3ba5: 0x0002, 0x3ba6: 0x0002, 0x3ba7: 0x0002, 0x3ba8: 0x0002, 0x3ba9: 0x0002,
0x3baa: 0x0002, 0x3bab: 0x0002,
0x3bb0: 0x0002,
// Block 0xef, offset 0x3bc0
0x3be0: 0x0004, 0x3be1: 0x0004, 0x3be2: 0x0004, 0x3be3: 0x0004,
0x3be4: 0x0004, 0x3be5: 0x0004, 0x3be6: 0x0004, 0x3be7: 0x0004, 0x3be8: 0x0004, 0x3be9: 0x0004,
0x3bea: 0x0004, 0x3beb: 0x0004,
0x3bf0: 0x0004,
0x3bcc: 0x0002, 0x3bcd: 0x0002, 0x3bce: 0x0002, 0x3bcf: 0x0002, 0x3bd0: 0x0002, 0x3bd1: 0x0002,
0x3bd2: 0x0002, 0x3bd3: 0x0002, 0x3bd4: 0x0002, 0x3bd5: 0x0002, 0x3bd6: 0x0002, 0x3bd7: 0x0002,
0x3bd8: 0x0002, 0x3bd9: 0x0002, 0x3bda: 0x0002, 0x3bdb: 0x0002, 0x3bdc: 0x0002, 0x3bdd: 0x0002,
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
0x3c0c: 0x0004, 0x3c0d: 0x0004, 0x3c0e: 0x0004, 0x3c0f: 0x0004, 0x3c10: 0x0004, 0x3c11: 0x0004,
0x3c12: 0x0004, 0x3c13: 0x0004, 0x3c14: 0x0004, 0x3c15: 0x0004, 0x3c16: 0x0004, 0x3c17: 0x0004,
0x3c18: 0x0004, 0x3c19: 0x0004, 0x3c1a: 0x0004, 0x3c1b: 0x0004, 0x3c1c: 0x0004, 0x3c1d: 0x0004,
0x3c1e: 0x0004, 0x3c1f: 0x0004, 0x3c20: 0x0004, 0x3c21: 0x0004, 0x3c22: 0x0004, 0x3c23: 0x0004,
0x3c24: 0x0004, 0x3c25: 0x0004, 0x3c26: 0x0004, 0x3c27: 0x0004, 0x3c28: 0x0004, 0x3c29: 0x0004,
0x3c2a: 0x0004, 0x3c2b: 0x0004, 0x3c2c: 0x0004, 0x3c2d: 0x0004, 0x3c2e: 0x0004, 0x3c2f: 0x0004,
0x3c30: 0x0004, 0x3c31: 0x0004, 0x3c32: 0x0004, 0x3c33: 0x0004, 0x3c34: 0x0004, 0x3c35: 0x0004,
0x3c36: 0x0004, 0x3c37: 0x0004, 0x3c38: 0x0004, 0x3c39: 0x0004, 0x3c3a: 0x0004,
0x3c3c: 0x0004, 0x3c3d: 0x0004, 0x3c3e: 0x0004, 0x3c3f: 0x0004,
0x3c00: 0x0002, 0x3c01: 0x0002, 0x3c02: 0x0002, 0x3c03: 0x0002, 0x3c04: 0x0002, 0x3c05: 0x0002,
0x3c07: 0x0002, 0x3c08: 0x0002, 0x3c09: 0x0002, 0x3c0a: 0x0002, 0x3c0b: 0x0002,
0x3c0c: 0x0002, 0x3c0d: 0x0002, 0x3c0e: 0x0002, 0x3c0f: 0x0002, 0x3c10: 0x0002, 0x3c11: 0x0002,
0x3c12: 0x0002, 0x3c13: 0x0002, 0x3c14: 0x0002, 0x3c15: 0x0002, 0x3c16: 0x0002, 0x3c17: 0x0002,
0x3c18: 0x0002, 0x3c19: 0x0002, 0x3c1a: 0x0002, 0x3c1b: 0x0002, 0x3c1c: 0x0002, 0x3c1d: 0x0002,
0x3c1e: 0x0002, 0x3c1f: 0x0002, 0x3c20: 0x0002, 0x3c21: 0x0002, 0x3c22: 0x0002, 0x3c23: 0x0002,
0x3c24: 0x0002, 0x3c25: 0x0002, 0x3c26: 0x0002, 0x3c27: 0x0002, 0x3c28: 0x0002, 0x3c29: 0x0002,
0x3c2a: 0x0002, 0x3c2b: 0x0002, 0x3c2c: 0x0002, 0x3c2d: 0x0002, 0x3c2e: 0x0002, 0x3c2f: 0x0002,
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
0x3c40: 0x0004, 0x3c41: 0x0004, 0x3c42: 0x0004, 0x3c43: 0x0004, 0x3c44: 0x0004, 0x3c45: 0x0004,
0x3c47: 0x0004, 0x3c48: 0x0004, 0x3c49: 0x0004, 0x3c4a: 0x0004, 0x3c4b: 0x0004,
0x3c4c: 0x0004, 0x3c4d: 0x0004, 0x3c4e: 0x0004, 0x3c4f: 0x0004, 0x3c50: 0x0004, 0x3c51: 0x0004,
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,
0x3c70: 0x0002, 0x3c71: 0x0002, 0x3c72: 0x0002, 0x3c73: 0x0002, 0x3c74: 0x0002, 0x3c75: 0x0002,
0x3c76: 0x0002, 0x3c77: 0x0002, 0x3c78: 0x0002, 0x3c79: 0x0002, 0x3c7a: 0x0002, 0x3c7b: 0x0002,
0x3c7c: 0x0002,
// Block 0xf2, offset 0x3c80
0x3cb0: 0x0004, 0x3cb1: 0x0004, 0x3cb2: 0x0004, 0x3cb3: 0x0004, 0x3cb4: 0x0004, 0x3cb5: 0x0004,
0x3cb6: 0x0004, 0x3cb7: 0x0004, 0x3cb8: 0x0004, 0x3cb9: 0x0004, 0x3cba: 0x0004, 0x3cbb: 0x0004,
0x3cbc: 0x0004,
0x3c80: 0x0002, 0x3c81: 0x0002, 0x3c82: 0x0002, 0x3c83: 0x0002, 0x3c84: 0x0002, 0x3c85: 0x0002,
0x3c86: 0x0002, 0x3c87: 0x0002, 0x3c88: 0x0002, 0x3c89: 0x0002,
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
0x3cc0: 0x0004, 0x3cc1: 0x0004, 0x3cc2: 0x0004, 0x3cc3: 0x0004, 0x3cc4: 0x0004, 0x3cc5: 0x0004,
0x3cc6: 0x0004, 0x3cc7: 0x0004, 0x3cc8: 0x0004, 0x3cc9: 0x0004,
0x3ccf: 0x0004, 0x3cd0: 0x0004, 0x3cd1: 0x0004,
0x3cd2: 0x0004, 0x3cd3: 0x0004, 0x3cd4: 0x0004, 0x3cd5: 0x0004, 0x3cd6: 0x0004, 0x3cd7: 0x0004,
0x3cd8: 0x0004, 0x3cd9: 0x0004, 0x3cda: 0x0004, 0x3cdb: 0x0004, 0x3cdc: 0x0004, 0x3cdd: 0x0004,
0x3cde: 0x0004, 0x3cdf: 0x0004, 0x3ce0: 0x0004, 0x3ce1: 0x0004, 0x3ce2: 0x0004, 0x3ce3: 0x0004,
0x3ce4: 0x0004, 0x3ce5: 0x0004, 0x3ce6: 0x0004, 0x3ce7: 0x0004, 0x3ce8: 0x0004, 0x3ce9: 0x0004,
0x3cea: 0x0004, 0x3ceb: 0x0004, 0x3cec: 0x0004, 0x3ced: 0x0004, 0x3cee: 0x0004, 0x3cef: 0x0004,
0x3cf0: 0x0004, 0x3cf1: 0x0004, 0x3cf2: 0x0004, 0x3cf3: 0x0004, 0x3cf4: 0x0004, 0x3cf5: 0x0004,
0x3cf6: 0x0004, 0x3cf7: 0x0004, 0x3cf8: 0x0004, 0x3cf9: 0x0004, 0x3cfa: 0x0004, 0x3cfb: 0x0004,
0x3cfc: 0x0004, 0x3cfd: 0x0004, 0x3cfe: 0x0004, 0x3cff: 0x0004,
0x3cc0: 0x0002, 0x3cc1: 0x0002, 0x3cc2: 0x0002, 0x3cc3: 0x0002, 0x3cc4: 0x0002, 0x3cc5: 0x0002,
0x3cc6: 0x0002,
0x3cce: 0x0002, 0x3ccf: 0x0002, 0x3cd0: 0x0002, 0x3cd1: 0x0002,
0x3cd2: 0x0002, 0x3cd3: 0x0002, 0x3cd4: 0x0002, 0x3cd5: 0x0002, 0x3cd6: 0x0002, 0x3cd7: 0x0002,
0x3cd8: 0x0002, 0x3cd9: 0x0002, 0x3cda: 0x0002, 0x3cdb: 0x0002, 0x3cdc: 0x0002,
0x3cdf: 0x0002, 0x3ce0: 0x0002, 0x3ce1: 0x0002, 0x3ce2: 0x0002, 0x3ce3: 0x0002,
0x3ce4: 0x0002, 0x3ce5: 0x0002, 0x3ce6: 0x0002, 0x3ce7: 0x0002, 0x3ce8: 0x0002, 0x3ce9: 0x0002,
0x3cf0: 0x0002, 0x3cf1: 0x0002, 0x3cf2: 0x0002, 0x3cf3: 0x0002, 0x3cf4: 0x0002, 0x3cf5: 0x0002,
0x3cf6: 0x0002, 0x3cf7: 0x0002, 0x3cf8: 0x0002,
// Block 0xf4, offset 0x3d00
0x3d00: 0x0004, 0x3d01: 0x0004, 0x3d02: 0x0004, 0x3d03: 0x0004, 0x3d04: 0x0004, 0x3d05: 0x0004,
0x3d06: 0x0004,
0x3d0e: 0x0004, 0x3d0f: 0x0004, 0x3d10: 0x0004, 0x3d11: 0x0004,
0x3d12: 0x0004, 0x3d13: 0x0004, 0x3d14: 0x0004, 0x3d15: 0x0004, 0x3d16: 0x0004, 0x3d17: 0x0004,
0x3d18: 0x0004, 0x3d19: 0x0004, 0x3d1a: 0x0004, 0x3d1b: 0x0004, 0x3d1c: 0x0004,
0x3d1f: 0x0004, 0x3d20: 0x0004, 0x3d21: 0x0004, 0x3d22: 0x0004, 0x3d23: 0x0004,
0x3d24: 0x0004, 0x3d25: 0x0004, 0x3d26: 0x0004, 0x3d27: 0x0004, 0x3d28: 0x0004, 0x3d29: 0x0004,
0x3d30: 0x0004, 0x3d31: 0x0004, 0x3d32: 0x0004, 0x3d33: 0x0004, 0x3d34: 0x0004, 0x3d35: 0x0004,
0x3d36: 0x0004, 0x3d37: 0x0004, 0x3d38: 0x0004,
0x3d01: 0x0001,
0x3d20: 0x0001, 0x3d21: 0x0001, 0x3d22: 0x0001, 0x3d23: 0x0001,
0x3d24: 0x0001, 0x3d25: 0x0001, 0x3d26: 0x0001, 0x3d27: 0x0001, 0x3d28: 0x0001, 0x3d29: 0x0001,
0x3d2a: 0x0001, 0x3d2b: 0x0001, 0x3d2c: 0x0001, 0x3d2d: 0x0001, 0x3d2e: 0x0001, 0x3d2f: 0x0001,
0x3d30: 0x0001, 0x3d31: 0x0001, 0x3d32: 0x0001, 0x3d33: 0x0001, 0x3d34: 0x0001, 0x3d35: 0x0001,
0x3d36: 0x0001, 0x3d37: 0x0001, 0x3d38: 0x0001, 0x3d39: 0x0001, 0x3d3a: 0x0001, 0x3d3b: 0x0001,
0x3d3c: 0x0001, 0x3d3d: 0x0001, 0x3d3e: 0x0001, 0x3d3f: 0x0001,
// Block 0xf5, offset 0x3d40
0x3d40: 0x0002, 0x3d41: 0x0002, 0x3d42: 0x0002, 0x3d43: 0x0002, 0x3d44: 0x0002, 0x3d45: 0x0002,
0x3d46: 0x0002, 0x3d47: 0x0002, 0x3d48: 0x0002, 0x3d49: 0x0002, 0x3d4a: 0x0002, 0x3d4b: 0x0002,
0x3d4c: 0x0002, 0x3d4d: 0x0002, 0x3d4e: 0x0002, 0x3d4f: 0x0002, 0x3d50: 0x0002, 0x3d51: 0x0002,
0x3d52: 0x0002, 0x3d53: 0x0002, 0x3d54: 0x0002, 0x3d55: 0x0002, 0x3d56: 0x0002, 0x3d57: 0x0002,
0x3d58: 0x0002, 0x3d59: 0x0002, 0x3d5a: 0x0002, 0x3d5b: 0x0002, 0x3d5c: 0x0002, 0x3d5d: 0x0002,
0x3d5e: 0x0002, 0x3d5f: 0x0002, 0x3d60: 0x0002, 0x3d61: 0x0002, 0x3d62: 0x0002, 0x3d63: 0x0002,
0x3d64: 0x0002, 0x3d65: 0x0002, 0x3d66: 0x0002, 0x3d67: 0x0002, 0x3d68: 0x0002, 0x3d69: 0x0002,
0x3d6a: 0x0002, 0x3d6b: 0x0002, 0x3d6c: 0x0002, 0x3d6d: 0x0002, 0x3d6e: 0x0002, 0x3d6f: 0x0002,
0x3d70: 0x0002, 0x3d71: 0x0002, 0x3d72: 0x0002, 0x3d73: 0x0002, 0x3d74: 0x0002, 0x3d75: 0x0002,
0x3d76: 0x0002, 0x3d77: 0x0002, 0x3d78: 0x0002, 0x3d79: 0x0002, 0x3d7a: 0x0002, 0x3d7b: 0x0002,
0x3d7c: 0x0002, 0x3d7d: 0x0002,
// 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,
0x3d40: 0x0003, 0x3d41: 0x0003, 0x3d42: 0x0003, 0x3d43: 0x0003, 0x3d44: 0x0003, 0x3d45: 0x0003,
0x3d46: 0x0003, 0x3d47: 0x0003, 0x3d48: 0x0003, 0x3d49: 0x0003, 0x3d4a: 0x0003, 0x3d4b: 0x0003,
0x3d4c: 0x0003, 0x3d4d: 0x0003, 0x3d4e: 0x0003, 0x3d4f: 0x0003, 0x3d50: 0x0003, 0x3d51: 0x0003,
0x3d52: 0x0003, 0x3d53: 0x0003, 0x3d54: 0x0003, 0x3d55: 0x0003, 0x3d56: 0x0003, 0x3d57: 0x0003,
0x3d58: 0x0003, 0x3d59: 0x0003, 0x3d5a: 0x0003, 0x3d5b: 0x0003, 0x3d5c: 0x0003, 0x3d5d: 0x0003,
0x3d5e: 0x0003, 0x3d5f: 0x0003, 0x3d60: 0x0003, 0x3d61: 0x0003, 0x3d62: 0x0003, 0x3d63: 0x0003,
0x3d64: 0x0003, 0x3d65: 0x0003, 0x3d66: 0x0003, 0x3d67: 0x0003, 0x3d68: 0x0003, 0x3d69: 0x0003,
0x3d6a: 0x0003, 0x3d6b: 0x0003, 0x3d6c: 0x0003, 0x3d6d: 0x0003, 0x3d6e: 0x0003, 0x3d6f: 0x0003,
0x3d70: 0x0003, 0x3d71: 0x0003, 0x3d72: 0x0003, 0x3d73: 0x0003, 0x3d74: 0x0003, 0x3d75: 0x0003,
0x3d76: 0x0003, 0x3d77: 0x0003, 0x3d78: 0x0003, 0x3d79: 0x0003, 0x3d7a: 0x0003, 0x3d7b: 0x0003,
0x3d7c: 0x0003, 0x3d7d: 0x0003,
}
// stringWidthIndex: 30 blocks, 1920 entries, 1920 bytes
@ -1683,10 +1655,10 @@ var stringWidthIndex = [1920]uint8{
// Block 0x17, offset 0x5c0
0x5c0: 0xd7, 0x5c3: 0xd8, 0x5c4: 0xd9, 0x5c5: 0xda, 0x5c6: 0xdb, 0x5c7: 0xdc,
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,
0x5d8: 0xe5, 0x5d9: 0xeb, 0x5da: 0xe5, 0x5db: 0xec, 0x5df: 0xed,
0x5e4: 0xee, 0x5e5: 0xef, 0x5e6: 0xe5, 0x5e7: 0xe5,
0x5e9: 0xf0, 0x5ea: 0xf1, 0x5eb: 0xf2,
0x5d0: 0xe3, 0x5d1: 0xe4, 0x5d2: 0x39, 0x5d3: 0xe5, 0x5d4: 0xe6, 0x5d5: 0xe7, 0x5d6: 0xe8, 0x5d7: 0xe9,
0x5d8: 0x39, 0x5d9: 0xea, 0x5da: 0x39, 0x5db: 0xeb, 0x5df: 0xec,
0x5e4: 0xed, 0x5e5: 0xee, 0x5e6: 0x39, 0x5e7: 0x39,
0x5e9: 0xef, 0x5ea: 0xf0, 0x5eb: 0xf1,
// Block 0x18, offset 0x600
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,
@ -1695,7 +1667,7 @@ var stringWidthIndex = [1920]uint8{
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,
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
0x650: 0x0b, 0x651: 0x0c, 0x653: 0x0d, 0x656: 0x0e, 0x657: 0x06,
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,
0x678: 0x06, 0x679: 0x06, 0x67a: 0x06, 0x67b: 0x06, 0x67c: 0x06, 0x67d: 0x06, 0x67e: 0x06, 0x67f: 0x16,
// 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
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,
@ -1713,7 +1685,7 @@ var stringWidthIndex = [1920]uint8{
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,
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
0x720: 0x18,
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)
// 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]
if isVS16(vs) {
prop = _Emoji
prop = _Wide
}
// VS15 (0x8E) requests text presentation but does not affect width,
// in my reading of Unicode TR51. Falls through to return the base
// 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 {
prop = _East_Asian_Wide
prop = _Wide
}
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
var propertyWidths = [6]int{
var propertyWidths = [4]int{
_Default: 1,
_Zero_Width: 0,
_East_Asian_Wide: 2,
_Wide: 2,
_East_Asian_Ambiguous: 1,
_Emoji: 2,
_Regional_Indicator: 2,
}
const upperBound = property(len(propertyWidths) - 1)

View file

@ -1,5 +1,41 @@
version: "2"
run:
timeout: 5m
linters:
exclusions:
presets:
- 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
In addition to unit tests,
[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
[here](https://github.com/gabriel-vasile/mimetype_tests/actions).
## Benchmarks
Benchmarks for each file format are performed when a PR is open. The results can
be seen on the [workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml).
Benchmarks are performed when a PR is open. The results can be seen on the
[workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml).
Performance improvements are welcome but correctness is prioritized.
## Structure

View file

@ -94,20 +94,6 @@ func eq(path1, path2 [][]byte) bool {
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
// to parse raw bytes as JSON.
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")) {
return false
}
pubkeyLen := binary.LittleEndian.Uint32(raw[8:12])
sigLen := binary.LittleEndian.Uint32(raw[12:16])
pubkeyLen := int64(binary.LittleEndian.Uint32(raw[8:12]))
sigLen := int64(binary.LittleEndian.Uint32(raw[12:16]))
zipOffset := minHeaderLen + pubkeyLen + sigLen
if uint32(len(raw)) < zipOffset {
if zipOffset < 0 || int64(len(raw)) < zipOffset {
return false
}
return Zip(raw[zipOffset:], limit)
@ -209,3 +209,13 @@ func tarChksum(b []byte) (unsigned, signed int64) {
}
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.
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
}

View file

@ -283,7 +283,7 @@ func Shell(raw []byte, _ uint32) bool {
// Text matches a plain text file.
//
// 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 {
// First look for BOM.
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)
}
func jsonHelper(raw []byte, limit uint32, q string, wantTok int) bool {
if !json.LooksLikeObjectOrArray(raw) {
func jsonHelper(raw scan.Bytes, limit uint32, q string, wantToks ...int) bool {
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
}
lraw := len(raw)
parsed, inspected, firstToken, querySatisfied := json.Parse(q, raw)
if !querySatisfied || firstToken&wantTok == 0 {
parsed, inspected, _, querySatisfied := json.Parse(q, raw)
if !querySatisfied {
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.
// 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
}
@ -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"
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
func vintWidth(v int) int {
mask, max, num := 128, 8, 1
for num < max && v&mask == 0 {
mask = mask >> 1
mask, nTimes, num := 128, 8, 1
for num < nTimes && v&mask == 0 {
mask >>= 1
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.)
func Jar(raw []byte, limit uint32) bool {
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{{
name: []byte("META-INF/MANIFEST.MF"),
}, {
name: []byte("META-INF/"),
}, {
name: []byte("META-INF/MANIFEST.MF"),
}}, 1)
}
@ -127,11 +131,14 @@ type zipEntries []struct {
func (z zipEntries) match(file []byte) bool {
for i := range z {
if z[i].dir && bytes.HasPrefix(file, z[i].name) {
return true
}
if bytes.Equal(file, z[i].name) {
return true
if z[i].dir {
if bytes.HasPrefix(file, z[i].name) {
return true
}
} else {
if bytes.Equal(file, z[i].name) {
return true
}
}
}
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.
func (b *Bytes) Peek() byte {
if len(*b) > 0 {
@ -63,8 +76,8 @@ func (b *Bytes) PopN(n int) []byte {
return nil
}
// PopUntil will advance b until, but not including, the first occurence of stopAt
// character. If no occurence is found, then it will advance until the end of b.
// PopUntil will advance b until, but not including, the first occurrence of stopAt
// 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.
func (b *Bytes) PopUntil(stopAt ...byte) Bytes {
if len(*b) == 0 {
@ -77,7 +90,7 @@ func (b *Bytes) PopUntil(stopAt ...byte) Bytes {
prefix := (*b)[:i]
*b = (*b)[i:]
return Bytes(prefix)
return prefix
}
// 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]
*b = (*b)[i:]
return Bytes(prefix)
return prefix
}
// 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
// and proceed with dropping the last line.
func (b *Bytes) DropLastLine(readLimit uint32) {
if readLimit == 0 || uint32(len(*b)) < readLimit {
if readLimit == 0 || uint64(len(*b)) < uint64(readLimit) {
return
}
@ -151,7 +164,7 @@ const (
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
// for matching the pattern.
func (b Bytes) Search(p []byte, flags Flags) (i int, l int) {

View file

@ -2,6 +2,7 @@ package mimetype
import (
"mime"
"slices"
"strings"
"github.com/gabriel-vasile/mimetype/internal/charset"
@ -58,10 +59,8 @@ func (m *MIME) Is(expectedMIME string) bool {
return true
}
for _, alias := range m.aliases {
if alias == expectedMIME {
return true
}
if slices.Contains(m.aliases, expectedMIME) {
return true
}
return false
@ -180,10 +179,8 @@ func (m *MIME) lookup(mime string) *MIME {
if mime == m.mime {
return m
}
for _, n := range m.aliases {
if n == mime {
return m
}
if slices.Contains(m.aliases, mime) {
return m
}
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.
Extension | MIME type <br> Aliases | Hierarchy
@ -152,6 +152,8 @@ Extension | MIME type <br> Aliases | Hierarchy
**.chm** | **application/vnd.ms-htmlhelp** | chm>root
**.wpd** | **application/vnd.wordperfect** | wpd>root
**.dxf** | **image/vnd.dxf** | dxf>root
**.grb** | **application/grib** | grb>root
**n/a** | **application/zlib** | zlib>root
**.txt** | **text/plain** | txt>root
**.svg** | **image/svg+xml** | svg>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
**.ppm** | **image/x-portable-pixmap** | ppm>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,
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,
oneNote, chm, wpd, dxf,
oneNote, chm, wpd, dxf, grib, zlib,
// Keep text last because it is the slowest check.
text,
)
@ -82,7 +82,7 @@ var (
alias("application/x-ogg")
oggAudio = newMIME("audio/ogg", ".oga", magic.OggAudio)
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).
alias("application/xml")
xhtml = newMIME("application/xhtml+xml", ".html", magic.XHTML)
@ -287,4 +287,7 @@ var (
chm = newMIME("application/vnd.ms-htmlhelp", ".chm", magic.CHM)
wpd = newMIME("application/vnd.wordperfect", ".wpd", magic.WPD)
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 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 {
return db.upgradeVersionTable(ctx)
}
@ -143,7 +155,7 @@ func (db *Database) checkDatabaseOwner(ctx context.Context) error {
} else if err != nil {
return fmt.Errorf("failed to check database owner: %w", err)
} else if owner != db.Owner {
return fmt.Errorf("%w %s", ErrNotOwned, owner)
return NotOwnedError{owner}
}
return nil
}

View file

@ -82,6 +82,23 @@ func (sm *Map[Key, Value]) GetDefault(key Key, def Value) Value {
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.
//
// 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()
}
// 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.
func (sm *Map[Key, Value]) Clone() *Map[Key, Value] {
return NewMapWithData(sm.CopyData())

View file

@ -9,6 +9,7 @@ import (
"fmt"
"go/ast"
"go/token"
"reflect"
"slices"
"strconv"
"strings"
@ -149,7 +150,7 @@ func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (added
if newImport.Name != nil {
newImport.Name.NamePos = pos
}
newImport.Path.ValuePos = pos
updateBasicLitPos(newImport.Path, pos)
newImport.EndPos = pos
// 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()
// Move the imports of the other import declaration to the first one.
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)
}
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
}
// 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:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
exclude_types: [markdown]
@ -9,7 +9,7 @@ repos:
- id: check-added-large-files
- repo: https://github.com/tekwizely/pre-commit-golang
rev: v1.0.0-rc.1
rev: v1.0.0-rc.4
hooks:
- id: go-imports-repo
args:
@ -18,8 +18,7 @@ repos:
- "-w"
- id: go-vet-repo-mod
- 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
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)
* *(client,appservice)* Deprecated `SendMassagedStateEvent` as `SendStateEvent`
@ -43,7 +75,7 @@
* *(federation)* Fixed validating auth for requests with query params.
* *(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
[#420]: github.com/mautrix/go/pull/420
[#426]: github.com/mautrix/go/pull/426

View file

@ -614,7 +614,9 @@ func (cli *Client) doRetry(
select {
case <-time.After(backoff):
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 {
req = cli.UpdateRequestOnRetry(req, cause)
@ -740,12 +742,15 @@ func (cli *Client) executeCompiledRequest(
cli.RequestStart(req)
startTime := time.Now()
res, err := client.Do(req)
duration := time.Now().Sub(startTime)
duration := time.Since(startTime)
if res != nil && !dontReadResponse {
defer res.Body.Close()
}
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(
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()
_, err = cli.MakeFullRequest(ctx, fullReq)
duration := time.Now().Sub(start)
duration := time.Since(start)
timeout := time.Duration(req.Timeout) * time.Millisecond
buffer := 10 * time.Second
if req.Since == "" {
@ -961,7 +966,7 @@ func (cli *Client) RegisterGuest(ctx context.Context, req *ReqRegister) (*RespRe
// }
// token := res.AccessToken
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 {
return nil, err
} 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")
}
req.Auth = BaseAuthData{Type: AuthTypeDummy, Session: uia.Session}
res, _, err = cli.Register(ctx, req)
res, _, err := cli.Register(ctx, req)
if err != nil {
return nil, err
}
@ -1746,6 +1751,8 @@ func parseRoomStateArray(req *http.Request, res *http.Response, responseJSON any
return nil, nil
}
type RoomStateMap = map[event.Type]map[string]*event.Event
// State gets all state in a room.
// 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) {
@ -2014,8 +2021,16 @@ func (cli *Client) uploadMediaToURL(ctx context.Context, data ReqUploadMedia) (*
Msg("Error uploading media to external URL, not retrying")
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")
select {
case <-time.After(backoff):
case <-ctx.Done():
return nil, ctx.Err()
}
retries--
_, err = readerSeeker.Seek(0, io.SeekStart)
if err != nil {

View file

@ -21,13 +21,24 @@ import (
)
var (
HashMismatch = errors.New("mismatching SHA-256 digest")
UnsupportedVersion = errors.New("unsupported Matrix file encryption version")
UnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm")
InvalidKey = errors.New("failed to decode key")
InvalidInitVector = errors.New("failed to decode initialization vector")
InvalidHash = errors.New("failed to decode SHA-256 hash")
ReaderClosed = errors.New("encrypting reader was already closed")
ErrHashMismatch = errors.New("mismatching SHA-256 digest")
ErrUnsupportedVersion = errors.New("unsupported Matrix file encryption version")
ErrUnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm")
ErrInvalidKey = errors.New("failed to decode key")
ErrInvalidInitVector = errors.New("failed to decode initialization vector")
ErrInvalidHash = errors.New("failed to decode SHA-256 hash")
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 (
@ -85,25 +96,25 @@ func (ef *EncryptedFile) decodeKeys(includeHash bool) error {
if ef.decoded != nil {
return nil
} else if len(ef.Key.Key) != keyBase64Length {
return InvalidKey
return ErrInvalidKey
} else if len(ef.InitVector) != ivBase64Length {
return InvalidInitVector
return ErrInvalidInitVector
} else if includeHash && len(ef.Hashes.SHA256) != hashBase64Length {
return InvalidHash
return ErrInvalidHash
}
ef.decoded = &decodedKeys{}
_, err := base64.RawURLEncoding.Decode(ef.decoded.key[:], []byte(ef.Key.Key))
if err != nil {
return InvalidKey
return ErrInvalidKey
}
_, err = base64.RawStdEncoding.Decode(ef.decoded.iv[:], []byte(ef.InitVector))
if err != nil {
return InvalidInitVector
return ErrInvalidInitVector
}
if includeHash {
_, err = base64.RawStdEncoding.Decode(ef.decoded.sha256[:], []byte(ef.Hashes.SHA256))
if err != nil {
return InvalidHash
return ErrInvalidHash
}
}
return nil
@ -179,7 +190,7 @@ var _ io.ReadSeekCloser = (*encryptingReader)(nil)
func (r *encryptingReader) Seek(offset int64, whence int) (int64, error) {
if r.closed {
return 0, ReaderClosed
return 0, ErrReaderClosed
}
if offset != 0 || whence != io.SeekStart {
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) {
if r.closed {
return 0, ReaderClosed
return 0, ErrReaderClosed
} else if r.isDecrypting && r.file.decoded == nil {
if err = r.file.PrepareForDecryption(); err != nil {
return
@ -224,7 +235,7 @@ func (r *encryptingReader) Close() (err error) {
}
if r.isDecrypting {
if !hmac.Equal(r.hash.Sum(nil), r.file.decoded.sha256[:]) {
return HashMismatch
return ErrHashMismatch
}
} else {
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.
func (ef *EncryptedFile) PrepareForDecryption() error {
if ef.Version != "v2" {
return UnsupportedVersion
return ErrUnsupportedVersion
} else if ef.Key.Algorithm != "A256CTR" {
return UnsupportedAlgorithm
return ErrUnsupportedAlgorithm
} else if err := ef.decodeKeys(true); err != nil {
return err
}
@ -281,7 +292,7 @@ func (ef *EncryptedFile) DecryptInPlace(data []byte) error {
}
dataHash := sha256.Sum256(data)
if !hmac.Equal(ef.decoded.sha256[:], dataHash[:]) {
return HashMismatch
return ErrHashMismatch
}
utils.XorA256CTR(data, ef.decoded.key, ef.decoded.iv)
return nil

View file

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

View file

@ -26,24 +26,22 @@ func (mach *OlmMachine) storeCrossSigningKeys(ctx context.Context, crossSigningK
log.Error().Err(err).
Msg("Error fetching current cross-signing keys of user")
}
if currentKeys != nil {
for curKeyUsage, curKey := range currentKeys {
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
for _, newKeyUsage := range userKeys.Usage {
if newKeyUsage == curKeyUsage {
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
if count, err := mach.CryptoStore.DropSignaturesByKey(ctx, userID, curKey.Key); err != nil {
log.Error().Err(err).Msg("Error deleting old signatures made by user")
} else {
log.Debug().
Int64("signature_count", count).
Msg("Dropped signatures made by old key as it has been replaced")
}
for curKeyUsage, curKey := range currentKeys {
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
for _, newKeyUsage := range userKeys.Usage {
if newKeyUsage == curKeyUsage {
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
if count, err := mach.CryptoStore.DropSignaturesByKey(ctx, userID, curKey.Key); err != nil {
log.Error().Err(err).Msg("Error deleting old signatures made by user")
} else {
log.Debug().
Int64("signature_count", count).
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 extendedSessionWaitTimeout = 22 * time.Second
@ -371,6 +371,7 @@ func (helper *CryptoHelper) waitLongerForSession(ctx context.Context, evt *event
content := evt.Content.AsEncrypted()
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)
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()
encrypted, err = helper.mach.EncryptMegolmEventWithStateKey(ctx, roomID, evtType, stateKey, content)
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
}
helper.log.Debug().

View file

@ -24,13 +24,24 @@ import (
)
var (
IncorrectEncryptedContentType = errors.New("event content is not instance of *event.EncryptedEventContent")
NoSessionFound = errors.New("failed to decrypt megolm event: no session with given ID found")
DuplicateMessageIndex = errors.New("duplicate megolm message index")
WrongRoom = 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")
SenderKeyMismatch = errors.New("sender keys in content and megolm session do not match")
RatchetError = errors.New("failed to ratchet session after use")
ErrIncorrectEncryptedContentType = errors.New("event content is not instance of *event.EncryptedEventContent")
ErrNoSessionFound = errors.New("failed to decrypt megolm event: no session with given ID found")
ErrDuplicateMessageIndex = errors.New("duplicate megolm message index")
ErrWrongRoom = errors.New("encrypted megolm event is not intended for this room")
ErrDeviceKeyMismatch = errors.New("device keys in event and verified device info do not match")
ErrSenderKeyMismatch = errors.New("sender keys in content and megolm session do not match")
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 {
@ -49,9 +60,9 @@ var (
func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event) (*event.Event, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
if !ok {
return nil, IncorrectEncryptedContentType
return nil, ErrIncorrectEncryptedContentType
} else if content.Algorithm != id.AlgorithmMegolmV1 {
return nil, UnsupportedAlgorithm
return nil, ErrUnsupportedAlgorithm
}
log := mach.machOrContextLog(ctx).With().
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")
trustLevel = id.TrustStateUnknownDevice
} else if device.SigningKey != sess.SigningKey || device.IdentityKey != sess.SenderKey {
return nil, DeviceKeyMismatch
return nil, ErrDeviceKeyMismatch
} else {
trustLevel, err = mach.ResolveTrustContext(ctx, device)
if err != nil {
@ -147,7 +158,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
if err != nil {
return nil, fmt.Errorf("failed to parse megolm payload: %w", err)
} else if megolmEvt.RoomID != encryptionRoomID {
return nil, WrongRoom
return nil, ErrWrongRoom
}
if evt.StateKey != nil && megolmEvt.StateKey != nil && mach.AllowEncryptedState {
megolmEvt.Type.Class = event.StateEventType
@ -201,19 +212,19 @@ func (mach *OlmMachine) checkUndecryptableMessageIndexDuplication(ctx context.Co
messageIndex, decodeErr := ParseMegolmMessageIndex(content.MegolmCiphertext)
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")
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()
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 {
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 {
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")
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) {
@ -224,13 +235,13 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
if err != nil {
return nil, nil, 0, fmt.Errorf("failed to get group session: %w", err)
} 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 {
return sess, nil, 0, SenderKeyMismatch
return sess, nil, 0, ErrSenderKeyMismatch
}
plaintext, messageIndex, err := sess.Internal.Decrypt(content.MegolmCiphertext)
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)
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 {
return sess, nil, messageIndex, fmt.Errorf("failed to check if message index is duplicate: %w", err)
} 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
@ -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")
if err != nil {
log.Err(err).Msg("Failed to delete fully used session")
return sess, plaintext, messageIndex, RatchetError
return sess, plaintext, messageIndex, ErrRatchetError
} else {
log.Info().Msg("Deleted fully used session")
}
} else if ratchetCurrentIndex < ratchetTargetIndex && mach.RatchetKeysOnDecrypt {
if err = sess.RatchetTo(ratchetTargetIndex); err != nil {
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 {
log.Err(err).Msg("Failed to store ratcheted session")
return sess, plaintext, messageIndex, RatchetError
return sess, plaintext, messageIndex, ErrRatchetError
} else {
log.Info().Msg("Ratcheted session forward")
}
} else if didModify {
if err = mach.CryptoStore.PutGroupSession(ctx, sess); err != nil {
log.Err(err).Msg("Failed to store updated ratchet safety data")
return sess, plaintext, messageIndex, RatchetError
return sess, plaintext, messageIndex, ErrRatchetError
} else {
log.Debug().Msg("Ratchet safety data changed (ratchet state didn't change)")
}

View file

@ -26,15 +26,27 @@ import (
)
var (
UnsupportedAlgorithm = errors.New("unsupported event encryption algorithm")
NotEncryptedForMe = errors.New("olm event doesn't contain ciphertext for this device")
UnsupportedOlmMessageType = errors.New("unsupported olm message type")
DecryptionFailedWithMatchingSession = errors.New("decryption failed with matching session")
DecryptionFailedForNormalMessage = errors.New("decryption failed for normal message")
SenderMismatch = errors.New("mismatched sender in olm payload")
RecipientMismatch = errors.New("mismatched recipient in olm payload")
RecipientKeyMismatch = errors.New("mismatched recipient key in olm payload")
ErrDuplicateMessage = errors.New("duplicate olm message")
ErrUnsupportedAlgorithm = errors.New("unsupported event encryption algorithm")
ErrNotEncryptedForMe = errors.New("olm event doesn't contain ciphertext for this device")
ErrUnsupportedOlmMessageType = errors.New("unsupported olm message type")
ErrDecryptionFailedWithMatchingSession = errors.New("decryption failed with matching session")
ErrDecryptionFailedForNormalMessage = errors.New("decryption failed for normal message")
ErrSenderMismatch = errors.New("mismatched sender in olm payload")
ErrRecipientMismatch = errors.New("mismatched recipient in olm payload")
ErrRecipientKeyMismatch = errors.New("mismatched recipient key in olm payload")
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.
@ -56,13 +68,13 @@ type DecryptedOlmEvent struct {
func (mach *OlmMachine) decryptOlmEvent(ctx context.Context, evt *event.Event) (*DecryptedOlmEvent, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
if !ok {
return nil, IncorrectEncryptedContentType
return nil, ErrIncorrectEncryptedContentType
} else if content.Algorithm != id.AlgorithmOlmV1 {
return nil, UnsupportedAlgorithm
return nil, ErrUnsupportedAlgorithm
}
ownContent, ok := content.OlmCiphertext[mach.account.IdentityKey()]
if !ok {
return nil, NotEncryptedForMe
return nil, ErrNotEncryptedForMe
}
decrypted, err := mach.decryptAndParseOlmCiphertext(ctx, evt, content.SenderKey, ownContent.Type, ownContent.Body)
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) {
if olmType != id.OlmMsgTypePreKey && olmType != id.OlmMsgTypeMsg {
return nil, UnsupportedOlmMessageType
return nil, ErrUnsupportedOlmMessageType
}
log := mach.machOrContextLog(ctx).With().
@ -102,11 +114,11 @@ func (mach *OlmMachine) decryptAndParseOlmCiphertext(ctx context.Context, evt *e
}
olmEvt.Type.Class = evt.Type.Class
if evt.Sender != olmEvt.Sender {
return nil, SenderMismatch
return nil, ErrSenderMismatch
} else if mach.Client.UserID != olmEvt.Recipient {
return nil, RecipientMismatch
return nil, ErrRecipientMismatch
} else if mach.account.SigningKey() != olmEvt.RecipientKeys.Ed25519 {
return nil, RecipientKeyMismatch
return nil, ErrRecipientKeyMismatch
}
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)
if err != nil {
if err == DecryptionFailedWithMatchingSession {
if err == ErrDecryptionFailedWithMatchingSession {
log.Warn().Msg("Found matching session, but decryption failed")
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 olmType != id.OlmMsgTypePreKey {
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")
endTimeTrace = mach.timeTrace(ctx, "creating inbound olm session", time.Second)
session, err := mach.createInboundSession(ctx, senderKey, ciphertext)
@ -302,7 +314,7 @@ func (mach *OlmMachine) tryDecryptOlmCiphertextWithExistingSession(
Str("session_description", session.Describe()).
Msg("Failed to decrypt olm message")
if olmType == id.OlmMsgTypePreKey {
return nil, DecryptionFailedWithMatchingSession
return nil, ErrDecryptionFailedWithMatchingSession
}
} else {
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)
mach.recentlyUnwedgedLock.Lock()
prevUnwedge, ok := mach.recentlyUnwedged[senderKey]
delta := time.Now().Sub(prevUnwedge)
delta := time.Since(prevUnwedge)
if ok && delta < MinUnwedgeInterval {
log.Debug().
Str("previous_recreation", delta.String()).

View file

@ -22,14 +22,23 @@ import (
)
var (
MismatchingDeviceID = errors.New("mismatching device ID in parameter and keys object")
MismatchingUserID = errors.New("mismatching user ID in parameter and keys object")
MismatchingSigningKey = errors.New("received update for device with different signing key")
NoSigningKeyFound = errors.New("didn't find ed25519 signing key")
NoIdentityKeyFound = errors.New("didn't find curve25519 identity key")
InvalidKeySignature = errors.New("invalid signature on device keys")
ErrMismatchingDeviceID = errors.New("mismatching device ID in parameter and keys object")
ErrMismatchingUserID = errors.New("mismatching user ID in parameter and keys object")
ErrMismatchingSigningKey = errors.New("received update for device with different signing key")
ErrNoSigningKeyFound = errors.New("didn't find ed25519 signing key")
ErrNoIdentityKeyFound = errors.New("didn't find curve25519 identity key")
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) {
@ -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) {
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 {
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)
identityKey := deviceKeys.Keys.GetCurve25519(deviceID)
if signingKey == "" {
return nil, NoSigningKeyFound
return nil, ErrNoSigningKeyFound
} else if identityKey == "" {
return nil, NoIdentityKeyFound
return nil, ErrNoIdentityKeyFound
}
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)
if err != nil {
return existing, fmt.Errorf("failed to verify signature: %w", err)
} else if !ok {
return existing, InvalidKeySignature
return existing, ErrInvalidKeySignature
}
name, ok := deviceKeys.Unsigned["device_display_name"].(string)

View file

@ -25,7 +25,12 @@ import (
)
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 {
@ -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
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) {
@ -120,7 +125,7 @@ func (mach *OlmMachine) EncryptMegolmEventWithStateKey(ctx context.Context, room
if err != nil {
return nil, fmt.Errorf("failed to get outbound group session: %w", err)
} else if session == nil {
return nil, NoGroupSession
return nil, ErrNoGroupSession
}
plaintext, err := json.Marshal(&rawMegolmEvent{
RoomID: roomID,

View file

@ -334,7 +334,7 @@ func (a *Account) UnpickleLibOlm(buf []byte) error {
if err != nil {
return err
} 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
return err
} else if err = a.IdKeys.Curve25519.UnpickleLibOlm(decoder); err != nil { // read curve25519 key pair

View file

@ -4,7 +4,8 @@ import (
"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) {
decoded := make([]byte, base64.RawStdEncoding.DecodedLen(len(input)))
writtenBytes, err := base64.RawStdEncoding.Decode(decoded, input)
@ -14,7 +15,6 @@ func Decode(input []byte) ([]byte, error) {
return decoded[:writtenBytes], nil
}
// Deprecated: base64.RawStdEncoding should be used directly
func Encode(input []byte) []byte {
encoded := make([]byte, base64.RawStdEncoding.EncodedLen(len(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 {
return fmt.Errorf("unpickle: %w", olm.ErrWrongPickleVersion)
return fmt.Errorf("unpickle: %w", olm.ErrUnknownJSONPickleVersion)
}
err = json.Unmarshal(decrypted[1:], object)
if err != nil {

View file

@ -39,7 +39,7 @@ func (r *GroupMessage) Decode(input []byte) (err error) {
return
}
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 {

View file

@ -43,7 +43,7 @@ func (r *Message) Decode(input []byte) (err error) {
return
}
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 {

View file

@ -48,7 +48,7 @@ func (r *PreKeyMessage) Decode(input []byte) (err error) {
return
}
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 {

View file

@ -35,7 +35,7 @@ func (s *MegolmSessionExport) Decode(input []byte) error {
return fmt.Errorf("decrypt: %w", olm.ErrBadInput)
}
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])
copy(s.RatchetData[:], input[5:133])

View file

@ -42,7 +42,7 @@ func (s *MegolmSessionSharing) VerifyAndDecode(input []byte) error {
}
s.PublicKey = publicKey
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])
copy(s.RatchetData[:], input[5:133])

View file

@ -103,7 +103,7 @@ func (a *Decryption) UnpickleLibOlm(unpickled []byte) error {
if pickledVersion == decryptionPickleVersionLibOlm {
return a.KeyPair.UnpickleLibOlm(decoder)
} 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
}
cipher, err := aessha2.NewAESSHA2(sharedSecret, nil)
if err != nil {
return nil, nil, err
}
ciphertext, err = cipher.Encrypt(plaintext)
if err != nil {
return nil, nil, err

View file

@ -142,7 +142,7 @@ func (r *Ratchet) Decrypt(input []byte) ([]byte, error) {
return nil, err
}
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 {
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) {
// 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
copiedRatchet := o.InitialRatchet
@ -126,7 +126,7 @@ func (o *MegolmInboundSession) Decrypt(ciphertext []byte) ([]byte, uint, error)
return nil, 0, err
}
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 {
return nil, 0, fmt.Errorf("decrypt: %w", olm.ErrBadMessageFormat)
@ -206,7 +206,7 @@ func (o *MegolmInboundSession) UnpickleLibOlm(value []byte) error {
return err
}
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 {

View file

@ -101,8 +101,10 @@ func (o *MegolmOutboundSession) Unpickle(pickled, key []byte) error {
func (o *MegolmOutboundSession) UnpickleLibOlm(buf []byte) error {
decoder := libolmpickle.NewDecoder(buf)
pickledVersion, err := decoder.ReadUInt32()
if pickledVersion != megolmOutboundSessionPickleVersionLibOlm {
return fmt.Errorf("unpickle MegolmInboundSession: %w (found version %d)", olm.ErrBadVersion, pickledVersion)
if err != nil {
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 {
return err

View file

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

View file

@ -14,7 +14,7 @@ func Register() {
// Inbound Session
olm.InitInboundGroupSessionFromPickled = func(pickled, key []byte) (olm.InboundGroupSession, error) {
if len(pickled) == 0 {
return nil, olm.EmptyInput
return nil, olm.ErrEmptyInput
}
if len(key) == 0 {
key = []byte(" ")
@ -23,13 +23,13 @@ func Register() {
}
olm.InitNewInboundGroupSession = func(sessionKey []byte) (olm.InboundGroupSession, error) {
if len(sessionKey) == 0 {
return nil, olm.EmptyInput
return nil, olm.ErrEmptyInput
}
return NewMegolmInboundSession(sessionKey)
}
olm.InitInboundGroupSessionImport = func(sessionKey []byte) (olm.InboundGroupSession, error) {
if len(sessionKey) == 0 {
return nil, olm.EmptyInput
return nil, olm.ErrEmptyInput
}
return NewMegolmInboundSessionFromExport(sessionKey)
}
@ -40,7 +40,7 @@ func Register() {
// Outbound Session
olm.InitNewOutboundGroupSessionFromPickled = func(pickled, key []byte) (olm.OutboundGroupSession, error) {
if len(pickled) == 0 {
return nil, olm.EmptyInput
return nil, olm.ErrEmptyInput
}
lenKey := len(key)
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
// 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."
megolmBackupDerivedPublicKey := id.Ed25519(base64.RawStdEncoding.EncodeToString(megolmBackupKey.PublicKey().Bytes()))
if megolmBackupKey != nil && versionInfo.AuthData.PublicKey == megolmBackupDerivedPublicKey {
log.Debug().Msg("key backup is trusted based on derived public key")
return versionInfo, nil
} else {
if megolmBackupKey != nil {
megolmBackupDerivedPublicKey := id.Ed25519(base64.RawStdEncoding.EncodeToString(megolmBackupKey.PublicKey().Bytes()))
if versionInfo.AuthData.PublicKey == megolmBackupDerivedPublicKey {
log.Debug().Msg("Key backup is trusted based on derived public key")
return versionInfo, nil
}
log.Debug().
Stringer("expected_key", megolmBackupDerivedPublicKey).
Stringer("actual_key", versionInfo.AuthData.PublicKey).

View file

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

View file

@ -11,21 +11,21 @@ import (
)
var errorMap = map[string]error{
"NOT_ENOUGH_RANDOM": olm.NotEnoughRandom,
"OUTPUT_BUFFER_TOO_SMALL": olm.OutputBufferTooSmall,
"BAD_MESSAGE_VERSION": olm.BadMessageVersion,
"BAD_MESSAGE_FORMAT": olm.BadMessageFormat,
"BAD_MESSAGE_MAC": olm.BadMessageMAC,
"BAD_MESSAGE_KEY_ID": olm.BadMessageKeyID,
"INVALID_BASE64": olm.InvalidBase64,
"BAD_ACCOUNT_KEY": olm.BadAccountKey,
"UNKNOWN_PICKLE_VERSION": olm.UnknownPickleVersion,
"CORRUPTED_PICKLE": olm.CorruptedPickle,
"BAD_SESSION_KEY": olm.BadSessionKey,
"UNKNOWN_MESSAGE_INDEX": olm.UnknownMessageIndex,
"BAD_LEGACY_ACCOUNT_PICKLE": olm.BadLegacyAccountPickle,
"BAD_SIGNATURE": olm.BadSignature,
"INPUT_BUFFER_TOO_SMALL": olm.InputBufferTooSmall,
"NOT_ENOUGH_RANDOM": olm.ErrLibolmNotEnoughRandom,
"OUTPUT_BUFFER_TOO_SMALL": olm.ErrLibolmOutputBufferTooSmall,
"BAD_MESSAGE_VERSION": olm.ErrWrongProtocolVersion,
"BAD_MESSAGE_FORMAT": olm.ErrBadMessageFormat,
"BAD_MESSAGE_MAC": olm.ErrBadMAC,
"BAD_MESSAGE_KEY_ID": olm.ErrBadMessageKeyID,
"INVALID_BASE64": olm.ErrLibolmInvalidBase64,
"BAD_ACCOUNT_KEY": olm.ErrLibolmBadAccountKey,
"UNKNOWN_PICKLE_VERSION": olm.ErrUnknownOlmPickleVersion,
"CORRUPTED_PICKLE": olm.ErrLibolmCorruptedPickle,
"BAD_SESSION_KEY": olm.ErrLibolmBadSessionKey,
"UNKNOWN_MESSAGE_INDEX": olm.ErrUnknownMessageIndex,
"BAD_LEGACY_ACCOUNT_PICKLE": olm.ErrLibolmBadLegacyAccountPickle,
"BAD_SIGNATURE": olm.ErrBadSignature,
"INPUT_BUFFER_TOO_SMALL": olm.ErrInputToSmall,
}
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".
func InboundGroupSessionFromPickled(pickled, key []byte) (*InboundGroupSession, error) {
if len(pickled) == 0 {
return nil, olm.EmptyInput
return nil, olm.ErrEmptyInput
}
lenKey := len(key)
if lenKey == 0 {
@ -48,7 +48,7 @@ func InboundGroupSessionFromPickled(pickled, key []byte) (*InboundGroupSession,
// "OLM_BAD_SESSION_KEY".
func NewInboundGroupSession(sessionKey []byte) (*InboundGroupSession, error) {
if len(sessionKey) == 0 {
return nil, olm.EmptyInput
return nil, olm.ErrEmptyInput
}
s := NewBlankInboundGroupSession()
r := C.olm_init_inbound_group_session(
@ -69,7 +69,7 @@ func NewInboundGroupSession(sessionKey []byte) (*InboundGroupSession, error) {
// error will be "OLM_BAD_SESSION_KEY".
func InboundGroupSessionImport(sessionKey []byte) (*InboundGroupSession, error) {
if len(sessionKey) == 0 {
return nil, olm.EmptyInput
return nil, olm.ErrEmptyInput
}
s := NewBlankInboundGroupSession()
r := C.olm_import_inbound_group_session(
@ -124,7 +124,7 @@ func (s *InboundGroupSession) pickleLen() uint {
// InboundGroupSession using the supplied key.
func (s *InboundGroupSession) Pickle(key []byte) ([]byte, error) {
if len(key) == 0 {
return nil, olm.NoKeyProvided
return nil, olm.ErrNoKeyProvided
}
pickled := make([]byte, s.pickleLen())
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 {
if len(key) == 0 {
return olm.NoKeyProvided
return olm.ErrNoKeyProvided
} else if len(pickled) == 0 {
return olm.EmptyInput
return olm.ErrEmptyInput
}
r := C.olm_unpickle_inbound_group_session(
(*C.OlmInboundGroupSession)(s.int),
@ -200,7 +200,7 @@ func (s *InboundGroupSession) MarshalJSON() ([]byte, error) {
// Deprecated
func (s *InboundGroupSession) UnmarshalJSON(data []byte) error {
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
return olm.InputNotJSONString
return olm.ErrInputNotJSONString
}
if s == nil || s.int == nil {
*s = *NewBlankInboundGroupSession()
@ -217,7 +217,7 @@ func (s *InboundGroupSession) UnmarshalJSON(data []byte) error {
// will be "BAD_MESSAGE_FORMAT".
func (s *InboundGroupSession) decryptMaxPlaintextLen(message []byte) (uint, error) {
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
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".
func (s *InboundGroupSession) Decrypt(message []byte) ([]byte, uint, error) {
if len(message) == 0 {
return nil, 0, olm.EmptyInput
return nil, 0, olm.ErrEmptyInput
}
decryptMaxPlaintextLen, err := s.decryptMaxPlaintextLen(message)
if err != nil {

View file

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

View file

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

View file

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

View file

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

View file

@ -10,50 +10,67 @@ import "errors"
// Those are the most common used errors
var (
ErrBadSignature = errors.New("bad signature")
ErrBadMAC = errors.New("bad mac")
ErrBadMessageFormat = errors.New("bad message format")
ErrBadVerification = errors.New("bad verification")
ErrWrongProtocolVersion = errors.New("wrong protocol version")
ErrEmptyInput = errors.New("empty input")
ErrNoKeyProvided = errors.New("no key")
ErrBadMessageKeyID = errors.New("bad message key id")
ErrRatchetNotAvailable = errors.New("ratchet not available: attempt to decode a message whose index is earlier than our earliest known session key")
ErrMsgIndexTooHigh = errors.New("message index too high")
ErrProtocolViolation = errors.New("not protocol message order")
ErrMessageKeyNotFound = errors.New("message key not found")
ErrChainTooHigh = errors.New("chain index too high")
ErrBadInput = errors.New("bad input")
ErrBadVersion = errors.New("wrong version")
ErrWrongPickleVersion = errors.New("wrong pickle version")
ErrInputToSmall = errors.New("input too small (truncated?)")
ErrOverflow = errors.New("overflow")
ErrBadSignature = errors.New("bad signature")
ErrBadMAC = errors.New("the message couldn't be decrypted (bad mac)")
ErrBadMessageFormat = errors.New("the message couldn't be decoded")
ErrBadVerification = errors.New("bad verification")
ErrWrongProtocolVersion = errors.New("wrong protocol version")
ErrEmptyInput = errors.New("empty input")
ErrNoKeyProvided = errors.New("no key provided")
ErrBadMessageKeyID = errors.New("the message references an unknown key ID")
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")
ErrProtocolViolation = errors.New("not protocol message order")
ErrMessageKeyNotFound = errors.New("message key not found")
ErrChainTooHigh = errors.New("chain index too high")
ErrBadInput = errors.New("bad input")
ErrUnknownOlmPickleVersion = errors.New("unknown olm pickle version")
ErrUnknownJSONPickleVersion = errors.New("unknown JSON pickle version")
ErrInputToSmall = errors.New("input too small (truncated?)")
)
// Error codes from go-olm
var (
EmptyInput = errors.New("empty input")
NoKeyProvided = errors.New("no pickle key provided")
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")
ErrNotEnoughGoRandom = errors.New("couldn't get enough randomness from crypto/rand")
ErrInputNotJSONString = errors.New("input doesn't look like a JSON string")
)
// Error codes from olm code
var (
NotEnoughRandom = errors.New("not enough entropy was supplied")
OutputBufferTooSmall = errors.New("supplied output buffer is too small")
BadMessageVersion = errors.New("the message version is unsupported")
BadMessageFormat = errors.New("the message couldn't be decoded")
BadMessageMAC = errors.New("the message couldn't be decrypted")
BadMessageKeyID = errors.New("the message references an unknown key ID")
InvalidBase64 = errors.New("the input base64 was invalid")
BadAccountKey = errors.New("the supplied account key is invalid")
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")
UnknownMessageIndex = errors.New("attempt to decode a message whose index is earlier than our earliest known session key")
BadLegacyAccountPickle = errors.New("attempt to unpickle an account which uses pickle version 1")
BadSignature = errors.New("received message had a bad signature")
InputBufferTooSmall = errors.New("the input data was too small to be valid")
ErrLibolmInvalidBase64 = errors.New("the input base64 was invalid")
ErrLibolmNotEnoughRandom = errors.New("not enough entropy was supplied")
ErrLibolmOutputBufferTooSmall = errors.New("supplied output buffer is too small")
ErrLibolmBadAccountKey = errors.New("the supplied account key is invalid")
ErrLibolmCorruptedPickle = errors.New("the pickled object couldn't be decoded")
ErrLibolmBadSessionKey = errors.New("attempt to initialise an inbound group session from an invalid session key")
ErrLibolmBadLegacyAccountPickle = errors.New("attempt to unpickle an account which uses pickle version 1")
)
// Deprecated: use variables prefixed with Err
var (
EmptyInput = ErrEmptyInput
BadSignature = ErrBadSignature
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 (
SessionNotShared = errors.New("session has not been shared")
SessionExpired = errors.New("session has expired")
ErrSessionNotShared = errors.New("session has not been shared")
ErrSessionExpired = errors.New("session has expired")
)
// Deprecated: use variables prefixed with Err
var (
SessionNotShared = ErrSessionNotShared
SessionExpired = ErrSessionExpired
)
// OlmSessionList is a list of OlmSessions.
@ -255,9 +261,9 @@ func (ogs *OutboundGroupSession) Expired() bool {
func (ogs *OutboundGroupSession) Encrypt(plaintext []byte) ([]byte, error) {
if !ogs.Shared {
return nil, SessionNotShared
return nil, ErrSessionNotShared
} else if ogs.Expired() {
return nil, SessionExpired
return nil, ErrSessionExpired
}
ogs.MessageCount++
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})
}
// 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.
func (mach *Machine) GenerateAndUploadKey(ctx context.Context, passphrase string) (key *Key, err error) {
key, err = NewKey(passphrase)

View file

@ -57,6 +57,7 @@ type EncryptedKeyData struct {
type EncryptedAccountDataEventContent struct {
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) {

View file

@ -85,6 +85,10 @@ var (
ErrResponseTooLong = errors.New("response content length too long")
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.

View file

@ -53,6 +53,8 @@ type BeeperMessageStatusEventContent struct {
LastRetry id.EventID `json:"last_retry,omitempty"`
TargetTxnID string `json:"relates_to_txn_id,omitempty"`
MutateEventKey string `json:"mutate_event_key,omitempty"`
// Indicates the set of users to whom the event was delivered. If nil, then
@ -87,7 +89,17 @@ type BeeperRoomKeyAckEventContent 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

View file

@ -77,6 +77,11 @@ export interface RoomFeatures {
delete_chat?: boolean
/** Whether deleting the chat for all participants is supported. */
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

View file

@ -61,6 +61,8 @@ type RoomFeatures struct {
DeleteChat bool `json:"delete_chat,omitempty"`
DeleteChatForEveryone bool `json:"delete_chat_for_everyone,omitempty"`
MessageRequest *MessageRequestFeatures `json:"message_request,omitempty"`
PerMessageProfileRelay bool `json:"-"`
}
@ -84,6 +86,7 @@ func (rf *RoomFeatures) Clone() *RoomFeatures {
clone.DeleteMaxAge = ptr.Clone(clone.DeleteMaxAge)
clone.DisappearingTimer = clone.DisappearingTimer.Clone()
clone.AllowedReactions = slices.Clone(clone.AllowedReactions)
clone.MessageRequest = clone.MessageRequest.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))
}
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
// 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, "delete_chat", rf.DeleteChat)
hashBool(hasher, "delete_chat_for_everyone", rf.DeleteChatForEveryone)
hashValue(hasher, "message_request", rf.MessageRequest)
return hasher.Sum(nil)
}

View file

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

View file

@ -63,7 +63,7 @@ func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error {
return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext)
case id.AlgorithmMegolmV1:
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]
}

View file

@ -231,7 +231,8 @@ type BridgeInfoSection struct {
AvatarURL id.ContentURIString `json:"avatar_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.

View file

@ -128,7 +128,7 @@ func (et *Type) GuessClass() TypeClass {
InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type,
CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.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
case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type, ToDeviceRoomKeyWithheld.Type,
ToDeviceBeeperRoomKeyAck.Type:
@ -234,9 +234,11 @@ var (
CallNegotiate = Type{"m.call.negotiate", MessageEventType}
CallHangup = Type{"m.call.hangup", MessageEventType}
BeeperMessageStatus = Type{"com.beeper.message_send_status", MessageEventType}
BeeperTranscription = Type{"com.beeper.transcription", MessageEventType}
BeeperDeleteChat = Type{"com.beeper.delete_chat", MessageEventType}
BeeperMessageStatus = Type{"com.beeper.message_send_status", MessageEventType}
BeeperTranscription = Type{"com.beeper.transcription", 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}
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
func (filter *Filter) Validate() error {
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
}

View file

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

View file

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

View file

@ -219,15 +219,15 @@ func DecodeUserLocalpart(str string) (string, error) {
for i := 0; i < len(strBytes); i++ {
b := strBytes[i]
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 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
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] == '_' {
outputBuffer.WriteByte('_')
@ -237,7 +237,7 @@ func DecodeUserLocalpart(str string) (string, error) {
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
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)
_, err := hex.Decode(dst, strBytes[i+1:i+3])

View file

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

View file

@ -263,10 +263,7 @@ type RespMutualRooms struct {
type RespRoomSummary struct {
PublicRoomInfo
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"`
Membership event.Membership `json:"membership,omitempty"`
UnstableRoomVersion id.RoomVersion `json:"im.nheko.summary.room_version,omitempty"`
UnstableRoomVersionOld id.RoomVersion `json:"im.nheko.summary.version,omitempty"`
@ -685,6 +682,10 @@ type PublicRoomInfo struct {
RoomType event.RoomType `json:"room_type"`
Topic string `json:"topic,omitempty"`
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

View file

@ -5,8 +5,6 @@ import (
"maunium.net/go/mautrix/id"
)
type RoomStateMap = map[event.Type]map[string]*event.Event
// Room represents a single Matrix room.
type Room struct {
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.
func (room Room) GetStateEvent(eventType event.Type, stateKey string) *event.Event {
stateEventMap, _ := room.State[eventType]
evt, _ := stateEventMap[stateKey]
stateEventMap := room.State[eventType]
evt := stateEventMap[stateKey]
return evt
}

View file

@ -129,6 +129,7 @@ func NewMemoryStateStore() StateStore {
PowerLevels: make(map[id.RoomID]*event.PowerLevelsEventContent),
Encryption: make(map[id.RoomID]*event.EncryptionEventContent),
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.
func (cli *Client) BuildURLWithQuery(urlPath PrefixableURLPath, urlQuery map[string]string) string {
return cli.BuildURLWithFullQuery(urlPath, func(q url.Values) {
if urlQuery != nil {
for k, v := range urlQuery {
q.Set(k, v)
}
for k, v := range urlQuery {
q.Set(k, v)
}
})
}

View file

@ -8,7 +8,7 @@ import (
"strings"
)
const Version = "v0.26.0"
const Version = "v0.26.1"
var GoModVersion = ""
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>
Toni Spets <toni.spets@beeper.com>
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>
Angus Dippenaar <angusdippenaar@gmail.com>
Artyom Pervukhin <github@artyom.dev>
Adrian Witas <adrianwit@gmail.com>
Dan Kortschak <dan@kortschak.io>
Dan Peterson <danp@danp.net>
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)
[![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)
[![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)
@ -9,3 +20,31 @@
[The SQLite Drivers Benchmarks Game]
[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-06-09 v1.38.0: Upgrade to SQLite 3.50.1.
//
//
// - 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

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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
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_vtab = sqlite3_vtab
type Sqlite3_vtab_cursor = sqlite3_vtab_cursor

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