enhanced automatic banning for invalid auth requests; update deps

This commit is contained in:
Aine 2024-07-23 11:55:42 +03:00
parent bf89b8fe1b
commit a6f52fe1ee
No known key found for this signature in database
GPG key ID: 34969C908CCA2804
152 changed files with 326416 additions and 43159 deletions

26
go.mod
View file

@ -7,10 +7,10 @@ toolchain go1.22.0
// replace gitlab.com/etke.cc/linkpearl => ../linkpearl
require (
github.com/archdx/zerolog-sentry v1.8.3
github.com/archdx/zerolog-sentry v1.8.4
github.com/emersion/go-msgauth v0.6.8
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
github.com/emersion/go-smtp v0.21.2
github.com/emersion/go-smtp v0.21.3
github.com/fsnotify/fsnotify v1.7.0
github.com/gabriel-vasile/mimetype v1.4.4
github.com/getsentry/sentry-go v0.28.1
@ -28,10 +28,10 @@ require (
gitlab.com/etke.cc/go/psd v1.1.2
gitlab.com/etke.cc/go/secgen v1.2.0
gitlab.com/etke.cc/go/validator v1.0.7
gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
maunium.net/go/mautrix v0.18.1
modernc.org/sqlite v1.30.1
gitlab.com/etke.cc/linkpearl v0.0.0-20240716084747-f2a547f02d54
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
maunium.net/go/mautrix v0.19.0
modernc.org/sqlite v1.31.1
)
require (
@ -45,7 +45,7 @@ require (
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
@ -59,13 +59,13 @@ require (
github.com/tidwall/sjson v1.2.5 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
gitlab.com/etke.cc/go/trysmtp v1.1.3 // indirect
go.mau.fi/util v0.5.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
go.mau.fi/util v0.6.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.54.1 // indirect
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect

68
go.sum
View file

@ -2,8 +2,8 @@ blitiri.com.ar/go/spf v1.5.1 h1:CWUEasc44OrANJD8CzceRnRn1Jv0LttY68cYym2/pbE=
blitiri.com.ar/go/spf v1.5.1/go.mod h1:E71N92TfL4+Yyd5lpKuE9CAF2pd4JrUq1xQfkTxoNdk=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/archdx/zerolog-sentry v1.8.3 h1:d3MiV+2406pHnUodv6O00sqafIohjtsYGb8cuFAf2KQ=
github.com/archdx/zerolog-sentry v1.8.3/go.mod h1:XrFHGe1CH5DQk/XSySu/IJSi5C9XR6+zpc97zVf/c4c=
github.com/archdx/zerolog-sentry v1.8.4 h1:Thxb8Crm+JaV1kcAF2KEcpKwkMtQaj+GazhktFgGTUc=
github.com/archdx/zerolog-sentry v1.8.4/go.mod h1:XrFHGe1CH5DQk/XSySu/IJSi5C9XR6+zpc97zVf/c4c=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
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=
@ -18,8 +18,8 @@ github.com/emersion/go-msgauth v0.6.8/go.mod h1:YDwuyTCUHu9xxmAeVj0eW4INnwB6NNZo
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY=
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.21.2 h1:OLDgvZKuofk4em9fT5tFG5j4jE1/hXnX75UMvcrL4AA=
github.com/emersion/go-smtp v0.21.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY=
github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
@ -56,8 +56,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mcnijman/go-emailaddress v1.1.1 h1:AGhgVDG3tCDaL0/Vc6erlPQjDuDN3dAT7rRdgFtetr0=
@ -118,49 +118,49 @@ gitlab.com/etke.cc/go/trysmtp v1.1.3 h1:e2EHond77onMaecqCg6mWumffTSEf+ycgj88nbee
gitlab.com/etke.cc/go/trysmtp v1.1.3/go.mod h1:lOO7tTdAE0a3ETV3wN3GJ7I1Tqewu7YTpPWaOmTteV0=
gitlab.com/etke.cc/go/validator v1.0.7 h1:4BGDTa9x68vJhbyn7m8W2yX+2Nb5im9+JLRrgoLUlF4=
gitlab.com/etke.cc/go/validator v1.0.7/go.mod h1:Id0SxRj0J3IPhiKlj0w1plxVLZfHlkwipn7HfRZsDts=
gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365 h1:MZnAxKJ5Bwv6ZHfC+HUEQyTDC0HPXVB8Q63ISkZ1lHo=
gitlab.com/etke.cc/linkpearl v0.0.0-20240425105001-435ae2720365/go.mod h1:onM3Tge1SUII+oda91/ThDvVvMhkPiSRJ7EOsC2g0yc=
go.mau.fi/util v0.5.0 h1:8yELAl+1CDRrwGe9NUmREgVclSs26Z68pTWePHVxuDo=
go.mau.fi/util v0.5.0/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
gitlab.com/etke.cc/linkpearl v0.0.0-20240716084747-f2a547f02d54 h1:+ihVGkVBBWlRyugJ3dvmA1rwqC9jFXrUZSslkYFN0Gw=
gitlab.com/etke.cc/linkpearl v0.0.0-20240716084747-f2a547f02d54/go.mod h1:R/uxbOoajQVxm0qCbWYfox6B9uqnM5R0/KMJcLSr6zM=
go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg=
go.mau.fi/util v0.6.0/go.mod h1:ljYdq3sPfpICc3zMU+/mHV/sa4z0nKxc67hSBwnrk8U=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.18.1 h1:a6mUsJixegBNTXUoqC5RQ9gsumIPzKvCubKwF+zmCt4=
maunium.net/go/mautrix v0.18.1/go.mod h1:2oHaq792cSXFGvxLvYw3Gf1L4WVVP4KZcYys5HVk/h8=
maunium.net/go/mautrix v0.19.0 h1:67eSJWam93mw44Q0/1SiOG7zQzXMUknUv5UaWkrODDU=
maunium.net/go/mautrix v0.19.0/go.mod h1:UE+mSQ4sDUuJMbjN0aB9EjQSGgXd48AzMvZ6+QJV1k8=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.0 h1:f9K5VdC0nVhHKTFMvhjtZ8TbRgFQbASvE5yO1zs8eC0=
modernc.org/ccgo/v4 v4.19.0/go.mod h1:CfpAl+673iXNwMG/aqcQn+vDcu4Es/YLya7+9RHjTa4=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.54.1 h1:5vzs86ANQehsFZXqjxUam20JwgK2eODTOcGMIWcCdg4=
modernc.org/libc v1.54.1/go.mod h1:B0D6klDmSmnq26T1iocn9kzyX6NtbzjuI3+oX/xfvng=
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e h1:WPC4v0rNIFb2PY+nBBEEKyugPPRHPzUgyN3xZPpGK58=
modernc.org/gc/v3 v3.0.0-20240722195230-4a140ff9c08e/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
@ -169,8 +169,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs=
modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

70
smtp/authserver.go Normal file
View file

@ -0,0 +1,70 @@
package smtp
import (
"bytes"
"context"
"errors"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
)
// ensure that PlainAuthServer implements sasl.Server
var _ sasl.Server = (*PlainAuthServer)(nil)
// PlainAuthServer is a server implementation of the PLAIN authentication mechanism.
// It's a modified version of the original plainServer from https://github.com/emersion/go-sasl package (MIT License)
// ref: https://github.com/emersion/go-sasl/blob/e73c9f7bad438a9bf3f5b28e661b74d752ecafdd/plain.go
// The reason for modification is to extend automatic banning mechanism of Postmoogle, as the original implementation
// doesn't provide a way to return an error to the caller before the actual authentication process.
type PlainAuthServer struct {
done bool
ctx context.Context //nolint:containedctx // that's per-request structure
bot matrixbot
conn *smtp.Conn
authenticate sasl.PlainAuthenticator
}
// NewPlainAuthServer creates a new PLAIN authentication server.
func NewPlainAuthServer(ctx context.Context, bot matrixbot, conn *smtp.Conn, auth sasl.PlainAuthenticator) *PlainAuthServer {
return &PlainAuthServer{
ctx: ctx,
bot: bot,
conn: conn,
authenticate: auth,
}
}
func (a *PlainAuthServer) Next(response []byte) (challenge []byte, done bool, err error) {
if a.done {
err = sasl.ErrUnexpectedClientResponse
return
}
addr := a.conn.Conn().RemoteAddr()
if a.bot.IsBanned(a.ctx, addr) {
err = ErrBanned
return
}
// No initial response, send an empty challenge
if response == nil {
return []byte{}, false, nil
}
a.done = true
parts := bytes.Split(response, []byte("\x00"))
if len(parts) != 3 {
a.bot.BanAuth(a.ctx, addr)
err = errors.New("sasl: invalid response. Don't bother me anymore, kupo")
return
}
identity := string(parts[0])
username := string(parts[1])
password := string(parts[2])
err = a.authenticate(identity, username, password)
done = true
return
}

View file

@ -7,6 +7,7 @@ import (
"io"
"net"
"net/url"
"slices"
"strconv"
"github.com/emersion/go-msgauth/dkim"
@ -34,6 +35,8 @@ var (
ErrInvalidEmail = errors.New("please, provide valid email address")
// GraylistEnhancedCode is GraylistCode in enhanced code notation
GraylistEnhancedCode = smtp.EnhancedCode{4, 5, 1}
// ensure that session implements smtp.AuthSession
_ smtp.AuthSession = (*session)(nil)
)
type session struct {
@ -52,13 +55,20 @@ type session struct {
fromRoom id.RoomID
}
// AuthMechanisms returns the list of supported authentication mechanisms
func (s *session) AuthMechanisms() []string {
return []string{sasl.Plain}
}
//nolint:unparam // it's a part of the interface
func (s *session) Auth(_ string) (sasl.Server, error) {
return sasl.NewPlainServer(func(identity, username, password string) error {
func (s *session) Auth(mech string) (sasl.Server, error) {
if !slices.Contains(s.AuthMechanisms(), mech) {
addr := s.conn.Conn().RemoteAddr()
s.log.Info().Str("addr", addr.String()).Msg("banning due to invalid auth mechanism")
s.bot.BanAuth(s.ctx, addr)
return nil, ErrBanned
}
return NewPlainAuthServer(s.ctx, s.bot, s.conn, func(identity, username, password string) error {
return s.authPlain(identity, username, password)
}), nil
}

View file

@ -6,7 +6,6 @@ import (
"io"
"net/http"
"time"
"unsafe"
"github.com/buger/jsonparser"
"github.com/getsentry/sentry-go"
@ -147,17 +146,17 @@ func (w *Writer) parseLogEvent(data []byte) (*sentry.Event, bool) {
}
err := jsonparser.ObjectEach(data, func(key, value []byte, vt jsonparser.ValueType, offset int) error {
switch string(key) {
switch strKey := string(key); strKey {
case zerolog.MessageFieldName:
event.Message = bytesToStrUnsafe(value)
event.Message = string(value)
case zerolog.ErrorFieldName:
event.Exception = append(event.Exception, sentry.Exception{
Value: bytesToStrUnsafe(value),
Value: string(value),
Stacktrace: newStacktrace(),
})
case zerolog.LevelFieldName, zerolog.TimestampFieldName:
default:
event.Extra[string(key)] = bytesToStrUnsafe(value)
event.Extra[strKey] = string(value)
}
return nil
@ -202,10 +201,6 @@ outer:
return st
}
func bytesToStrUnsafe(data []byte) string {
return *(*string)(unsafe.Pointer(&data))
}
// WriterOption configures sentry events writer.
type WriterOption interface {
apply(*config)

View file

@ -29,6 +29,8 @@ type Client struct {
lmtp bool
ext map[string]string // supported extensions
localName string // the name to use in HELO/EHLO/LHLO
didGreet bool // whether we've received greeting from server
greetError error // the error from the greeting
didHello bool // whether we've said HELO/EHLO/LHLO
helloError error // the error from the hello
rcpts []string // recipients accumulated for the current session
@ -179,17 +181,22 @@ func (c *Client) Close() error {
}
func (c *Client) greet() error {
if c.didGreet {
return c.greetError
}
// Initial greeting timeout. RFC 5321 recommends 5 minutes.
c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
defer c.conn.SetDeadline(time.Time{})
c.didGreet = true
_, _, err := c.readResponse(220)
if err != nil {
c.greetError = err
c.text.Close()
return err
}
return nil
return c.greetError
}
// hello runs a hello exchange if needed.
@ -198,12 +205,11 @@ func (c *Client) hello() error {
return c.helloError
}
c.didHello = true
if err := c.greet(); err != nil {
c.helloError = err
return c.helloError
return err
}
c.didHello = true
if err := c.ehlo(); err != nil {
var smtpError *SMTPError
if errors.As(err, &smtpError) && (smtpError.Code == 500 || smtpError.Code == 502) {
@ -321,7 +327,8 @@ func (c *Client) startTLS(config *tls.Config) error {
testHookStartTLS(config)
}
c.setConn(tls.Client(c.conn, config))
return c.ehlo()
c.didHello = false
return nil
}
// TLSConnectionState returns the client's TLS connection state.

View file

@ -4,20 +4,21 @@ package runewidth
var combining = table{
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3},
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0D00, 0x0D01},
{0x135D, 0x135F}, {0x1A7F, 0x1A7F}, {0x1AB0, 0x1AC0},
{0x1B6B, 0x1B73}, {0x1DC0, 0x1DF9}, {0x1DFB, 0x1DFF},
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0CF3, 0x0CF3},
{0x0D00, 0x0D01}, {0x135D, 0x135F}, {0x1A7F, 0x1A7F},
{0x1AB0, 0x1ACE}, {0x1B6B, 0x1B73}, {0x1DC0, 0x1DFF},
{0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF},
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1},
{0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A},
{0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x11300, 0x11301},
{0x1133B, 0x1133C}, {0x11366, 0x1136C}, {0x11370, 0x11374},
{0x16AF0, 0x16AF4}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172},
{0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x10F82, 0x10F85},
{0x11300, 0x11301}, {0x1133B, 0x1133C}, {0x11366, 0x1136C},
{0x11370, 0x11374}, {0x16AF0, 0x16AF4}, {0x1CF00, 0x1CF2D},
{0x1CF30, 0x1CF46}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172},
{0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
{0x1D242, 0x1D244}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018},
{0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A},
{0x1E8D0, 0x1E8D6},
{0x1E08F, 0x1E08F}, {0x1E8D0, 0x1E8D6},
}
var doublewidth = table{
@ -33,33 +34,34 @@ var doublewidth = table{
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB},
{0x3000, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF},
{0x3105, 0x312F}, {0x3131, 0x318E}, {0x3190, 0x31E3},
{0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x4DBF},
{0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C},
{0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19},
{0xFE30, 0xFE52}, {0xFE54, 0xFE66}, {0xFE68, 0xFE6B},
{0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
{0x16FF0, 0x16FF1}, {0x17000, 0x187F7}, {0x18800, 0x18CD5},
{0x18D00, 0x18D08}, {0x1B000, 0x1B11E}, {0x1B150, 0x1B152},
{0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1F004, 0x1F004},
{0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A},
{0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, {0x1F240, 0x1F248},
{0x1F250, 0x1F251}, {0x1F260, 0x1F265}, {0x1F300, 0x1F320},
{0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393},
{0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0},
{0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440},
{0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E},
{0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, {0x1F595, 0x1F596},
{0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5},
{0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D7},
{0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB},
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F978},
{0x1F97A, 0x1F9CB}, {0x1F9CD, 0x1F9FF}, {0x1FA70, 0x1FA74},
{0x1FA78, 0x1FA7A}, {0x1FA80, 0x1FA86}, {0x1FA90, 0x1FAA8},
{0x1FAB0, 0x1FAB6}, {0x1FAC0, 0x1FAC2}, {0x1FAD0, 0x1FAD6},
{0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x303E},
{0x3041, 0x3096}, {0x3099, 0x30FF}, {0x3105, 0x312F},
{0x3131, 0x318E}, {0x3190, 0x31E3}, {0x31EF, 0x321E},
{0x3220, 0x3247}, {0x3250, 0x4DBF}, {0x4E00, 0xA48C},
{0xA490, 0xA4C6}, {0xA960, 0xA97C}, {0xAC00, 0xD7A3},
{0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52},
{0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60},
{0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4}, {0x16FF0, 0x16FF1},
{0x17000, 0x187F7}, {0x18800, 0x18CD5}, {0x18D00, 0x18D08},
{0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1AFFD, 0x1AFFE},
{0x1B000, 0x1B122}, {0x1B132, 0x1B132}, {0x1B150, 0x1B152},
{0x1B155, 0x1B155}, {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB},
{0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E},
{0x1F191, 0x1F19A}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23B},
{0x1F240, 0x1F248}, {0x1F250, 0x1F251}, {0x1F260, 0x1F265},
{0x1F300, 0x1F320}, {0x1F32D, 0x1F335}, {0x1F337, 0x1F37C},
{0x1F37E, 0x1F393}, {0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3},
{0x1F3E0, 0x1F3F0}, {0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E},
{0x1F440, 0x1F440}, {0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D},
{0x1F54B, 0x1F54E}, {0x1F550, 0x1F567}, {0x1F57A, 0x1F57A},
{0x1F595, 0x1F596}, {0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F},
{0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2},
{0x1F6D5, 0x1F6D7}, {0x1F6DC, 0x1F6DF}, {0x1F6EB, 0x1F6EC},
{0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB}, {0x1F7F0, 0x1F7F0},
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F9FF},
{0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA88}, {0x1FA90, 0x1FABD},
{0x1FABF, 0x1FAC5}, {0x1FACE, 0x1FADB}, {0x1FAE0, 0x1FAE8},
{0x1FAF0, 0x1FAF8}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
}
var ambiguous = table{
@ -154,43 +156,43 @@ var neutral = table{
{0x0402, 0x040F}, {0x0450, 0x0450}, {0x0452, 0x052F},
{0x0531, 0x0556}, {0x0559, 0x058A}, {0x058D, 0x058F},
{0x0591, 0x05C7}, {0x05D0, 0x05EA}, {0x05EF, 0x05F4},
{0x0600, 0x061C}, {0x061E, 0x070D}, {0x070F, 0x074A},
{0x074D, 0x07B1}, {0x07C0, 0x07FA}, {0x07FD, 0x082D},
{0x0830, 0x083E}, {0x0840, 0x085B}, {0x085E, 0x085E},
{0x0860, 0x086A}, {0x08A0, 0x08B4}, {0x08B6, 0x08C7},
{0x08D3, 0x0983}, {0x0985, 0x098C}, {0x098F, 0x0990},
{0x0993, 0x09A8}, {0x09AA, 0x09B0}, {0x09B2, 0x09B2},
{0x09B6, 0x09B9}, {0x09BC, 0x09C4}, {0x09C7, 0x09C8},
{0x09CB, 0x09CE}, {0x09D7, 0x09D7}, {0x09DC, 0x09DD},
{0x09DF, 0x09E3}, {0x09E6, 0x09FE}, {0x0A01, 0x0A03},
{0x0A05, 0x0A0A}, {0x0A0F, 0x0A10}, {0x0A13, 0x0A28},
{0x0A2A, 0x0A30}, {0x0A32, 0x0A33}, {0x0A35, 0x0A36},
{0x0A38, 0x0A39}, {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42},
{0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
{0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E}, {0x0A66, 0x0A76},
{0x0A81, 0x0A83}, {0x0A85, 0x0A8D}, {0x0A8F, 0x0A91},
{0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3},
{0x0AB5, 0x0AB9}, {0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9},
{0x0ACB, 0x0ACD}, {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3},
{0x0AE6, 0x0AF1}, {0x0AF9, 0x0AFF}, {0x0B01, 0x0B03},
{0x0B05, 0x0B0C}, {0x0B0F, 0x0B10}, {0x0B13, 0x0B28},
{0x0B2A, 0x0B30}, {0x0B32, 0x0B33}, {0x0B35, 0x0B39},
{0x0B3C, 0x0B44}, {0x0B47, 0x0B48}, {0x0B4B, 0x0B4D},
{0x0B55, 0x0B57}, {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63},
{0x0B66, 0x0B77}, {0x0B82, 0x0B83}, {0x0B85, 0x0B8A},
{0x0B8E, 0x0B90}, {0x0B92, 0x0B95}, {0x0B99, 0x0B9A},
{0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4},
{0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2},
{0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0},
{0x0BD7, 0x0BD7}, {0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C},
{0x0C0E, 0x0C10}, {0x0C12, 0x0C28}, {0x0C2A, 0x0C39},
{0x0C3D, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
{0x0C55, 0x0C56}, {0x0C58, 0x0C5A}, {0x0C60, 0x0C63},
{0x0600, 0x070D}, {0x070F, 0x074A}, {0x074D, 0x07B1},
{0x07C0, 0x07FA}, {0x07FD, 0x082D}, {0x0830, 0x083E},
{0x0840, 0x085B}, {0x085E, 0x085E}, {0x0860, 0x086A},
{0x0870, 0x088E}, {0x0890, 0x0891}, {0x0898, 0x0983},
{0x0985, 0x098C}, {0x098F, 0x0990}, {0x0993, 0x09A8},
{0x09AA, 0x09B0}, {0x09B2, 0x09B2}, {0x09B6, 0x09B9},
{0x09BC, 0x09C4}, {0x09C7, 0x09C8}, {0x09CB, 0x09CE},
{0x09D7, 0x09D7}, {0x09DC, 0x09DD}, {0x09DF, 0x09E3},
{0x09E6, 0x09FE}, {0x0A01, 0x0A03}, {0x0A05, 0x0A0A},
{0x0A0F, 0x0A10}, {0x0A13, 0x0A28}, {0x0A2A, 0x0A30},
{0x0A32, 0x0A33}, {0x0A35, 0x0A36}, {0x0A38, 0x0A39},
{0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42}, {0x0A47, 0x0A48},
{0x0A4B, 0x0A4D}, {0x0A51, 0x0A51}, {0x0A59, 0x0A5C},
{0x0A5E, 0x0A5E}, {0x0A66, 0x0A76}, {0x0A81, 0x0A83},
{0x0A85, 0x0A8D}, {0x0A8F, 0x0A91}, {0x0A93, 0x0AA8},
{0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3}, {0x0AB5, 0x0AB9},
{0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9}, {0x0ACB, 0x0ACD},
{0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3}, {0x0AE6, 0x0AF1},
{0x0AF9, 0x0AFF}, {0x0B01, 0x0B03}, {0x0B05, 0x0B0C},
{0x0B0F, 0x0B10}, {0x0B13, 0x0B28}, {0x0B2A, 0x0B30},
{0x0B32, 0x0B33}, {0x0B35, 0x0B39}, {0x0B3C, 0x0B44},
{0x0B47, 0x0B48}, {0x0B4B, 0x0B4D}, {0x0B55, 0x0B57},
{0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63}, {0x0B66, 0x0B77},
{0x0B82, 0x0B83}, {0x0B85, 0x0B8A}, {0x0B8E, 0x0B90},
{0x0B92, 0x0B95}, {0x0B99, 0x0B9A}, {0x0B9C, 0x0B9C},
{0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4}, {0x0BA8, 0x0BAA},
{0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2}, {0x0BC6, 0x0BC8},
{0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0}, {0x0BD7, 0x0BD7},
{0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C}, {0x0C0E, 0x0C10},
{0x0C12, 0x0C28}, {0x0C2A, 0x0C39}, {0x0C3C, 0x0C44},
{0x0C46, 0x0C48}, {0x0C4A, 0x0C4D}, {0x0C55, 0x0C56},
{0x0C58, 0x0C5A}, {0x0C5D, 0x0C5D}, {0x0C60, 0x0C63},
{0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90},
{0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
{0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD},
{0x0CD5, 0x0CD6}, {0x0CDE, 0x0CDE}, {0x0CE0, 0x0CE3},
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF2}, {0x0D00, 0x0D0C},
{0x0CD5, 0x0CD6}, {0x0CDD, 0x0CDE}, {0x0CE0, 0x0CE3},
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF3}, {0x0D00, 0x0D0C},
{0x0D0E, 0x0D10}, {0x0D12, 0x0D44}, {0x0D46, 0x0D48},
{0x0D4A, 0x0D4F}, {0x0D54, 0x0D63}, {0x0D66, 0x0D7F},
{0x0D81, 0x0D83}, {0x0D85, 0x0D96}, {0x0D9A, 0x0DB1},
@ -200,7 +202,7 @@ var neutral = table{
{0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B}, {0x0E81, 0x0E82},
{0x0E84, 0x0E84}, {0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3},
{0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4},
{0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECD}, {0x0ED0, 0x0ED9},
{0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECE}, {0x0ED0, 0x0ED9},
{0x0EDC, 0x0EDF}, {0x0F00, 0x0F47}, {0x0F49, 0x0F6C},
{0x0F71, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC},
{0x0FCE, 0x0FDA}, {0x1000, 0x10C5}, {0x10C7, 0x10C7},
@ -212,20 +214,19 @@ var neutral = table{
{0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A},
{0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5},
{0x13F8, 0x13FD}, {0x1400, 0x169C}, {0x16A0, 0x16F8},
{0x1700, 0x170C}, {0x170E, 0x1714}, {0x1720, 0x1736},
{0x1740, 0x1753}, {0x1760, 0x176C}, {0x176E, 0x1770},
{0x1772, 0x1773}, {0x1780, 0x17DD}, {0x17E0, 0x17E9},
{0x17F0, 0x17F9}, {0x1800, 0x180E}, {0x1810, 0x1819},
{0x1820, 0x1878}, {0x1880, 0x18AA}, {0x18B0, 0x18F5},
{0x1900, 0x191E}, {0x1920, 0x192B}, {0x1930, 0x193B},
{0x1940, 0x1940}, {0x1944, 0x196D}, {0x1970, 0x1974},
{0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x19D0, 0x19DA},
{0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, {0x1A60, 0x1A7C},
{0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, {0x1AA0, 0x1AAD},
{0x1AB0, 0x1AC0}, {0x1B00, 0x1B4B}, {0x1B50, 0x1B7C},
{0x1B80, 0x1BF3}, {0x1BFC, 0x1C37}, {0x1C3B, 0x1C49},
{0x1C4D, 0x1C88}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CC7},
{0x1CD0, 0x1CFA}, {0x1D00, 0x1DF9}, {0x1DFB, 0x1F15},
{0x1700, 0x1715}, {0x171F, 0x1736}, {0x1740, 0x1753},
{0x1760, 0x176C}, {0x176E, 0x1770}, {0x1772, 0x1773},
{0x1780, 0x17DD}, {0x17E0, 0x17E9}, {0x17F0, 0x17F9},
{0x1800, 0x1819}, {0x1820, 0x1878}, {0x1880, 0x18AA},
{0x18B0, 0x18F5}, {0x1900, 0x191E}, {0x1920, 0x192B},
{0x1930, 0x193B}, {0x1940, 0x1940}, {0x1944, 0x196D},
{0x1970, 0x1974}, {0x1980, 0x19AB}, {0x19B0, 0x19C9},
{0x19D0, 0x19DA}, {0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E},
{0x1A60, 0x1A7C}, {0x1A7F, 0x1A89}, {0x1A90, 0x1A99},
{0x1AA0, 0x1AAD}, {0x1AB0, 0x1ACE}, {0x1B00, 0x1B4C},
{0x1B50, 0x1B7E}, {0x1B80, 0x1BF3}, {0x1BFC, 0x1C37},
{0x1C3B, 0x1C49}, {0x1C4D, 0x1C88}, {0x1C90, 0x1CBA},
{0x1CBD, 0x1CC7}, {0x1CD0, 0x1CFA}, {0x1D00, 0x1F15},
{0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D},
{0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B},
{0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4},
@ -237,7 +238,7 @@ var neutral = table{
{0x2036, 0x203A}, {0x203C, 0x203D}, {0x203F, 0x2064},
{0x2066, 0x2071}, {0x2075, 0x207E}, {0x2080, 0x2080},
{0x2085, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8},
{0x20AA, 0x20AB}, {0x20AD, 0x20BF}, {0x20D0, 0x20F0},
{0x20AA, 0x20AB}, {0x20AD, 0x20C0}, {0x20D0, 0x20F0},
{0x2100, 0x2102}, {0x2104, 0x2104}, {0x2106, 0x2108},
{0x210A, 0x2112}, {0x2114, 0x2115}, {0x2117, 0x2120},
{0x2123, 0x2125}, {0x2127, 0x212A}, {0x212C, 0x2152},
@ -275,15 +276,15 @@ var neutral = table{
{0x2780, 0x2794}, {0x2798, 0x27AF}, {0x27B1, 0x27BE},
{0x27C0, 0x27E5}, {0x27EE, 0x2984}, {0x2987, 0x2B1A},
{0x2B1D, 0x2B4F}, {0x2B51, 0x2B54}, {0x2B5A, 0x2B73},
{0x2B76, 0x2B95}, {0x2B97, 0x2C2E}, {0x2C30, 0x2C5E},
{0x2C60, 0x2CF3}, {0x2CF9, 0x2D25}, {0x2D27, 0x2D27},
{0x2D2D, 0x2D2D}, {0x2D30, 0x2D67}, {0x2D6F, 0x2D70},
{0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE},
{0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6},
{0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE},
{0x2DE0, 0x2E52}, {0x303F, 0x303F}, {0x4DC0, 0x4DFF},
{0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, {0xA700, 0xA7BF},
{0xA7C2, 0xA7CA}, {0xA7F5, 0xA82C}, {0xA830, 0xA839},
{0x2B76, 0x2B95}, {0x2B97, 0x2CF3}, {0x2CF9, 0x2D25},
{0x2D27, 0x2D27}, {0x2D2D, 0x2D2D}, {0x2D30, 0x2D67},
{0x2D6F, 0x2D70}, {0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6},
{0x2DA8, 0x2DAE}, {0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE},
{0x2DC0, 0x2DC6}, {0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6},
{0x2DD8, 0x2DDE}, {0x2DE0, 0x2E5D}, {0x303F, 0x303F},
{0x4DC0, 0x4DFF}, {0xA4D0, 0xA62B}, {0xA640, 0xA6F7},
{0xA700, 0xA7CA}, {0xA7D0, 0xA7D1}, {0xA7D3, 0xA7D3},
{0xA7D5, 0xA7D9}, {0xA7F2, 0xA82C}, {0xA830, 0xA839},
{0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9},
{0xA8E0, 0xA953}, {0xA95F, 0xA95F}, {0xA980, 0xA9CD},
{0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36},
@ -294,8 +295,8 @@ var neutral = table{
{0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDFFF},
{0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36},
{0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41},
{0xFB43, 0xFB44}, {0xFB46, 0xFBC1}, {0xFBD3, 0xFD3F},
{0xFD50, 0xFD8F}, {0xFD92, 0xFDC7}, {0xFDF0, 0xFDFD},
{0xFB43, 0xFB44}, {0xFB46, 0xFBC2}, {0xFBD3, 0xFD8F},
{0xFD92, 0xFDC7}, {0xFDCF, 0xFDCF}, {0xFDF0, 0xFDFF},
{0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC},
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC}, {0x10000, 0x1000B},
{0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003C, 0x1003D},
@ -307,44 +308,48 @@ var neutral = table{
{0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5},
{0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3},
{0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563},
{0x1056F, 0x1056F}, {0x10600, 0x10736}, {0x10740, 0x10755},
{0x10760, 0x10767}, {0x10800, 0x10805}, {0x10808, 0x10808},
{0x1080A, 0x10835}, {0x10837, 0x10838}, {0x1083C, 0x1083C},
{0x1083F, 0x10855}, {0x10857, 0x1089E}, {0x108A7, 0x108AF},
{0x108E0, 0x108F2}, {0x108F4, 0x108F5}, {0x108FB, 0x1091B},
{0x1091F, 0x10939}, {0x1093F, 0x1093F}, {0x10980, 0x109B7},
{0x109BC, 0x109CF}, {0x109D2, 0x10A03}, {0x10A05, 0x10A06},
{0x10A0C, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A35},
{0x10A38, 0x10A3A}, {0x10A3F, 0x10A48}, {0x10A50, 0x10A58},
{0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6}, {0x10AEB, 0x10AF6},
{0x10B00, 0x10B35}, {0x10B39, 0x10B55}, {0x10B58, 0x10B72},
{0x10B78, 0x10B91}, {0x10B99, 0x10B9C}, {0x10BA9, 0x10BAF},
{0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2},
{0x10CFA, 0x10D27}, {0x10D30, 0x10D39}, {0x10E60, 0x10E7E},
{0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD}, {0x10EB0, 0x10EB1},
{0x10F00, 0x10F27}, {0x10F30, 0x10F59}, {0x10FB0, 0x10FCB},
{0x10FE0, 0x10FF6}, {0x11000, 0x1104D}, {0x11052, 0x1106F},
{0x1107F, 0x110C1}, {0x110CD, 0x110CD}, {0x110D0, 0x110E8},
{0x110F0, 0x110F9}, {0x11100, 0x11134}, {0x11136, 0x11147},
{0x11150, 0x11176}, {0x11180, 0x111DF}, {0x111E1, 0x111F4},
{0x11200, 0x11211}, {0x11213, 0x1123E}, {0x11280, 0x11286},
{0x11288, 0x11288}, {0x1128A, 0x1128D}, {0x1128F, 0x1129D},
{0x1129F, 0x112A9}, {0x112B0, 0x112EA}, {0x112F0, 0x112F9},
{0x11300, 0x11303}, {0x11305, 0x1130C}, {0x1130F, 0x11310},
{0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11332, 0x11333},
{0x11335, 0x11339}, {0x1133B, 0x11344}, {0x11347, 0x11348},
{0x1134B, 0x1134D}, {0x11350, 0x11350}, {0x11357, 0x11357},
{0x1135D, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374},
{0x11400, 0x1145B}, {0x1145D, 0x11461}, {0x11480, 0x114C7},
{0x114D0, 0x114D9}, {0x11580, 0x115B5}, {0x115B8, 0x115DD},
{0x11600, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C},
{0x11680, 0x116B8}, {0x116C0, 0x116C9}, {0x11700, 0x1171A},
{0x1171D, 0x1172B}, {0x11730, 0x1173F}, {0x11800, 0x1183B},
{0x118A0, 0x118F2}, {0x118FF, 0x11906}, {0x11909, 0x11909},
{0x1190C, 0x11913}, {0x11915, 0x11916}, {0x11918, 0x11935},
{0x11937, 0x11938}, {0x1193B, 0x11946}, {0x11950, 0x11959},
{0x119A0, 0x119A7}, {0x119AA, 0x119D7}, {0x119DA, 0x119E4},
{0x11A00, 0x11A47}, {0x11A50, 0x11AA2}, {0x11AC0, 0x11AF8},
{0x1056F, 0x1057A}, {0x1057C, 0x1058A}, {0x1058C, 0x10592},
{0x10594, 0x10595}, {0x10597, 0x105A1}, {0x105A3, 0x105B1},
{0x105B3, 0x105B9}, {0x105BB, 0x105BC}, {0x10600, 0x10736},
{0x10740, 0x10755}, {0x10760, 0x10767}, {0x10780, 0x10785},
{0x10787, 0x107B0}, {0x107B2, 0x107BA}, {0x10800, 0x10805},
{0x10808, 0x10808}, {0x1080A, 0x10835}, {0x10837, 0x10838},
{0x1083C, 0x1083C}, {0x1083F, 0x10855}, {0x10857, 0x1089E},
{0x108A7, 0x108AF}, {0x108E0, 0x108F2}, {0x108F4, 0x108F5},
{0x108FB, 0x1091B}, {0x1091F, 0x10939}, {0x1093F, 0x1093F},
{0x10980, 0x109B7}, {0x109BC, 0x109CF}, {0x109D2, 0x10A03},
{0x10A05, 0x10A06}, {0x10A0C, 0x10A13}, {0x10A15, 0x10A17},
{0x10A19, 0x10A35}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A48},
{0x10A50, 0x10A58}, {0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6},
{0x10AEB, 0x10AF6}, {0x10B00, 0x10B35}, {0x10B39, 0x10B55},
{0x10B58, 0x10B72}, {0x10B78, 0x10B91}, {0x10B99, 0x10B9C},
{0x10BA9, 0x10BAF}, {0x10C00, 0x10C48}, {0x10C80, 0x10CB2},
{0x10CC0, 0x10CF2}, {0x10CFA, 0x10D27}, {0x10D30, 0x10D39},
{0x10E60, 0x10E7E}, {0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD},
{0x10EB0, 0x10EB1}, {0x10EFD, 0x10F27}, {0x10F30, 0x10F59},
{0x10F70, 0x10F89}, {0x10FB0, 0x10FCB}, {0x10FE0, 0x10FF6},
{0x11000, 0x1104D}, {0x11052, 0x11075}, {0x1107F, 0x110C2},
{0x110CD, 0x110CD}, {0x110D0, 0x110E8}, {0x110F0, 0x110F9},
{0x11100, 0x11134}, {0x11136, 0x11147}, {0x11150, 0x11176},
{0x11180, 0x111DF}, {0x111E1, 0x111F4}, {0x11200, 0x11211},
{0x11213, 0x11241}, {0x11280, 0x11286}, {0x11288, 0x11288},
{0x1128A, 0x1128D}, {0x1128F, 0x1129D}, {0x1129F, 0x112A9},
{0x112B0, 0x112EA}, {0x112F0, 0x112F9}, {0x11300, 0x11303},
{0x11305, 0x1130C}, {0x1130F, 0x11310}, {0x11313, 0x11328},
{0x1132A, 0x11330}, {0x11332, 0x11333}, {0x11335, 0x11339},
{0x1133B, 0x11344}, {0x11347, 0x11348}, {0x1134B, 0x1134D},
{0x11350, 0x11350}, {0x11357, 0x11357}, {0x1135D, 0x11363},
{0x11366, 0x1136C}, {0x11370, 0x11374}, {0x11400, 0x1145B},
{0x1145D, 0x11461}, {0x11480, 0x114C7}, {0x114D0, 0x114D9},
{0x11580, 0x115B5}, {0x115B8, 0x115DD}, {0x11600, 0x11644},
{0x11650, 0x11659}, {0x11660, 0x1166C}, {0x11680, 0x116B9},
{0x116C0, 0x116C9}, {0x11700, 0x1171A}, {0x1171D, 0x1172B},
{0x11730, 0x11746}, {0x11800, 0x1183B}, {0x118A0, 0x118F2},
{0x118FF, 0x11906}, {0x11909, 0x11909}, {0x1190C, 0x11913},
{0x11915, 0x11916}, {0x11918, 0x11935}, {0x11937, 0x11938},
{0x1193B, 0x11946}, {0x11950, 0x11959}, {0x119A0, 0x119A7},
{0x119AA, 0x119D7}, {0x119DA, 0x119E4}, {0x11A00, 0x11A47},
{0x11A50, 0x11AA2}, {0x11AB0, 0x11AF8}, {0x11B00, 0x11B09},
{0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45},
{0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7},
{0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D08, 0x11D09},
@ -352,30 +357,36 @@ var neutral = table{
{0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65},
{0x11D67, 0x11D68}, {0x11D6A, 0x11D8E}, {0x11D90, 0x11D91},
{0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8},
{0x11F00, 0x11F10}, {0x11F12, 0x11F3A}, {0x11F3E, 0x11F59},
{0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399},
{0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
{0x13000, 0x1342E}, {0x13430, 0x13438}, {0x14400, 0x14646},
{0x12F90, 0x12FF2}, {0x13000, 0x13455}, {0x14400, 0x14646},
{0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69},
{0x16A6E, 0x16A6F}, {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF5},
{0x16B00, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61},
{0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16E40, 0x16E9A},
{0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, {0x16F8F, 0x16F9F},
{0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88},
{0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3}, {0x1D000, 0x1D0F5},
{0x1D100, 0x1D126}, {0x1D129, 0x1D1E8}, {0x1D200, 0x1D245},
{0x1D2E0, 0x1D2F3}, {0x1D300, 0x1D356}, {0x1D360, 0x1D378},
{0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F},
{0x1D4A2, 0x1D4A2}, {0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC},
{0x1D4AE, 0x1D4B9}, {0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3},
{0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514},
{0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E},
{0x1D540, 0x1D544}, {0x1D546, 0x1D546}, {0x1D54A, 0x1D550},
{0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B},
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
{0x1E026, 0x1E02A}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E2C0, 0x1E2F9},
{0x1E2FF, 0x1E2FF}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
{0x16A6E, 0x16ABE}, {0x16AC0, 0x16AC9}, {0x16AD0, 0x16AED},
{0x16AF0, 0x16AF5}, {0x16B00, 0x16B45}, {0x16B50, 0x16B59},
{0x16B5B, 0x16B61}, {0x16B63, 0x16B77}, {0x16B7D, 0x16B8F},
{0x16E40, 0x16E9A}, {0x16F00, 0x16F4A}, {0x16F4F, 0x16F87},
{0x16F8F, 0x16F9F}, {0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C},
{0x1BC80, 0x1BC88}, {0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3},
{0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1CF50, 0x1CFC3},
{0x1D000, 0x1D0F5}, {0x1D100, 0x1D126}, {0x1D129, 0x1D1EA},
{0x1D200, 0x1D245}, {0x1D2C0, 0x1D2D3}, {0x1D2E0, 0x1D2F3},
{0x1D300, 0x1D356}, {0x1D360, 0x1D378}, {0x1D400, 0x1D454},
{0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F}, {0x1D4A2, 0x1D4A2},
{0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC}, {0x1D4AE, 0x1D4B9},
{0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3}, {0x1D4C5, 0x1D505},
{0x1D507, 0x1D50A}, {0x1D50D, 0x1D514}, {0x1D516, 0x1D51C},
{0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E}, {0x1D540, 0x1D544},
{0x1D546, 0x1D546}, {0x1D54A, 0x1D550}, {0x1D552, 0x1D6A5},
{0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B}, {0x1DA9B, 0x1DA9F},
{0x1DAA1, 0x1DAAF}, {0x1DF00, 0x1DF1E}, {0x1DF25, 0x1DF2A},
{0x1E000, 0x1E006}, {0x1E008, 0x1E018}, {0x1E01B, 0x1E021},
{0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, {0x1E030, 0x1E06D},
{0x1E08F, 0x1E08F}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E290, 0x1E2AE},
{0x1E2C0, 0x1E2F9}, {0x1E2FF, 0x1E2FF}, {0x1E4D0, 0x1E4F9},
{0x1E7E0, 0x1E7E6}, {0x1E7E8, 0x1E7EB}, {0x1E7ED, 0x1E7EE},
{0x1E7F0, 0x1E7FE}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
{0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
{0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03},
{0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24},
@ -400,8 +411,8 @@ var neutral = table{
{0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594},
{0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F},
{0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4},
{0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F773},
{0x1F780, 0x1F7D8}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847},
{0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F776},
{0x1F77B, 0x1F7D9}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847},
{0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD},
{0x1F8B0, 0x1F8B1}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B},
{0x1F946, 0x1F946}, {0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D},

131
vendor/github.com/rs/zerolog/log/log.go generated vendored Normal file
View file

@ -0,0 +1,131 @@
// Package log provides a global logger for zerolog.
package log
import (
"context"
"fmt"
"io"
"os"
"github.com/rs/zerolog"
)
// Logger is the global logger.
var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger()
// Output duplicates the global logger and sets w as its output.
func Output(w io.Writer) zerolog.Logger {
return Logger.Output(w)
}
// With creates a child logger with the field added to its context.
func With() zerolog.Context {
return Logger.With()
}
// Level creates a child logger with the minimum accepted level set to level.
func Level(level zerolog.Level) zerolog.Logger {
return Logger.Level(level)
}
// Sample returns a logger with the s sampler.
func Sample(s zerolog.Sampler) zerolog.Logger {
return Logger.Sample(s)
}
// Hook returns a logger with the h Hook.
func Hook(h zerolog.Hook) zerolog.Logger {
return Logger.Hook(h)
}
// Err starts a new message with error level with err as a field if not nil or
// with info level if err is nil.
//
// You must call Msg on the returned event in order to send the event.
func Err(err error) *zerolog.Event {
return Logger.Err(err)
}
// Trace starts a new message with trace level.
//
// You must call Msg on the returned event in order to send the event.
func Trace() *zerolog.Event {
return Logger.Trace()
}
// Debug starts a new message with debug level.
//
// You must call Msg on the returned event in order to send the event.
func Debug() *zerolog.Event {
return Logger.Debug()
}
// Info starts a new message with info level.
//
// You must call Msg on the returned event in order to send the event.
func Info() *zerolog.Event {
return Logger.Info()
}
// Warn starts a new message with warn level.
//
// You must call Msg on the returned event in order to send the event.
func Warn() *zerolog.Event {
return Logger.Warn()
}
// Error starts a new message with error level.
//
// You must call Msg on the returned event in order to send the event.
func Error() *zerolog.Event {
return Logger.Error()
}
// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method.
//
// You must call Msg on the returned event in order to send the event.
func Fatal() *zerolog.Event {
return Logger.Fatal()
}
// Panic starts a new message with panic level. The message is also sent
// to the panic function.
//
// You must call Msg on the returned event in order to send the event.
func Panic() *zerolog.Event {
return Logger.Panic()
}
// WithLevel starts a new message with level.
//
// You must call Msg on the returned event in order to send the event.
func WithLevel(level zerolog.Level) *zerolog.Event {
return Logger.WithLevel(level)
}
// Log starts a new message with no level. Setting zerolog.GlobalLevel to
// zerolog.Disabled will still disable events produced by this method.
//
// You must call Msg on the returned event in order to send the event.
func Log() *zerolog.Event {
return Logger.Log()
}
// Print sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {
Logger.Debug().CallerSkipFrame(1).Msg(fmt.Sprint(v...))
}
// Printf sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {
Logger.Debug().CallerSkipFrame(1).Msgf(format, v...)
}
// Ctx returns the Logger associated with the ctx. If no logger
// is associated, a disabled logger is returned.
func Ctx(ctx context.Context) *zerolog.Logger {
return zerolog.Ctx(ctx)
}

View file

@ -63,7 +63,6 @@ linters-settings:
time-layout: true
crypto-hash: true
default-rpc-path: true
os-dev-null: true
sql-isolation-level: true
tls-signature-scheme: true
constant-kind: true
@ -86,7 +85,6 @@ linters:
- errchkjson
- errname
- errorlint
- execinquery
- exhaustive
- exportloopref
- forcetypeassert

View file

@ -10,6 +10,7 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"strconv"
"strings"
"time"
@ -96,6 +97,14 @@ func (le *LoggingExecable) QueryRowContext(ctx context.Context, query string, ar
return row
}
func (le *LoggingExecable) beginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
txBeginner, ok := le.UnderlyingExecable.(UnderlyingExecutableWithTx)
if !ok {
return nil, fmt.Errorf("can't start transaction with a %T", le.UnderlyingExecable)
}
return txBeginner.BeginTx(ctx, opts)
}
// loggingDB is a wrapper for LoggingExecable that allows access to BeginTx.
//
// While LoggingExecable has a pointer to the database and could use BeginTx, it's not technically safe since
@ -104,13 +113,36 @@ type loggingDB struct {
LoggingExecable
}
func (ld *loggingDB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*LoggingTxn, error) {
type internalTxnStarter interface {
beginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}
type TxnOptions struct {
Isolation sql.IsolationLevel
ReadOnly bool
Conn Conn
}
func (ld *loggingDB) BeginTx(ctx context.Context, opts *TxnOptions) (*LoggingTxn, error) {
if opts == nil {
opts = &TxnOptions{}
}
sqlOpts := &sql.TxOptions{
Isolation: opts.Isolation,
ReadOnly: opts.ReadOnly,
}
var tx *sql.Tx
var err error
start := time.Now()
if opts.Conn != nil {
tx, err = opts.Conn.beginTx(ctx, sqlOpts)
} else {
targetDB := ld.db.RawDB
if opts != nil && opts.ReadOnly && ld.db.ReadOnlyDB != nil {
if opts.ReadOnly && ld.db.ReadOnlyDB != nil {
targetDB = ld.db.ReadOnlyDB
}
start := time.Now()
tx, err := targetDB.BeginTx(ctx, opts)
tx, err = targetDB.BeginTx(ctx, sqlOpts)
}
ld.db.Log.QueryTiming(ctx, "Begin", "", nil, -1, time.Since(start), err)
if err != nil {
return nil, err

View file

@ -73,12 +73,22 @@ type UnderlyingExecable interface {
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
type UnderlyingExecutableWithTx interface {
UnderlyingExecable
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}
type Execable interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
type Conn interface {
Execable
internalTxnStarter
}
type Transaction interface {
Execable
Commit() error
@ -88,8 +98,8 @@ type Transaction interface {
// Expected implementations of Execable
var (
_ UnderlyingExecable = (*sql.Tx)(nil)
_ UnderlyingExecable = (*sql.DB)(nil)
_ UnderlyingExecable = (*sql.Conn)(nil)
_ UnderlyingExecutableWithTx = (*sql.DB)(nil)
_ UnderlyingExecutableWithTx = (*sql.Conn)(nil)
_ Execable = (*LoggingExecable)(nil)
_ Transaction = (*LoggingTxn)(nil)
)

View file

@ -6,7 +6,13 @@
package dbutil
import "errors"
import (
"errors"
"fmt"
"runtime"
"go.mau.fi/util/exzerolog"
)
var ErrAlreadyIterated = errors.New("this iterator has been already iterated")
@ -39,24 +45,38 @@ type ConvertRowFn[T any] func(Scannable) (T, error)
// )
// }
func (crf ConvertRowFn[T]) NewRowIter(rows Rows, err error) RowIter[T] {
return NewRowIterWithError(rows, crf, err)
return newRowIterWithError(rows, crf, err)
}
type rowIterImpl[T any] struct {
Rows
ConvertRow ConvertRowFn[T]
iterated bool
caller string
err error
}
// NewRowIter creates a new RowIter from the given Rows and scanner function.
func NewRowIter[T any](rows Rows, convertFn ConvertRowFn[T]) RowIter[T] {
return &rowIterImpl[T]{Rows: rows, ConvertRow: convertFn}
return newRowIterWithError(rows, convertFn, nil)
}
// NewRowIterWithError creates a new RowIter from the given Rows and scanner function with default error. If not nil, it will be returned without calling iterator function.
func NewRowIterWithError[T any](rows Rows, convertFn ConvertRowFn[T], err error) RowIter[T] {
return &rowIterImpl[T]{Rows: rows, ConvertRow: convertFn, err: err}
return newRowIterWithError(rows, convertFn, err)
}
func newRowIterWithError[T any](rows Rows, convertFn ConvertRowFn[T], err error) RowIter[T] {
ri := &rowIterImpl[T]{Rows: rows, ConvertRow: convertFn, err: err}
if err == nil {
callerSkip := 2
if pc, file, line, ok := runtime.Caller(callerSkip); ok {
ri.caller = exzerolog.CallerWithFunctionName(pc, file, line)
}
runtime.SetFinalizer(ri, (*rowIterImpl[T]).destroy)
}
return ri
}
func ScanSingleColumn[T any](rows Scannable) (val T, err error) {
@ -74,13 +94,22 @@ func ScanDataStruct[T NewableDataStruct[T]](rows Scannable) (T, error) {
return val.New().Scan(rows)
}
func (i *rowIterImpl[T]) destroy() {
if !i.iterated {
panic(fmt.Errorf("RowIter created at %s wasn't iterated", i.caller))
}
}
func (i *rowIterImpl[T]) Iter(fn func(T) (bool, error)) error {
if i == nil {
return nil
} else if i.Rows == nil || i.err != nil {
return i.err
}
defer i.Rows.Close()
defer func() {
_ = i.Rows.Close()
i.iterated = true
}()
for i.Rows.Next() {
if item, err := i.ConvertRow(i.Rows); err != nil {

View file

@ -13,7 +13,7 @@ type DatabaseLogger interface {
QueryTiming(ctx context.Context, method, query string, args []any, nrows int, duration time.Duration, err error)
WarnUnsupportedVersion(current, compat, latest int)
PrepareUpgrade(current, compat, latest int)
DoUpgrade(from, to int, message string, txn bool)
DoUpgrade(from, to int, message string, txn TxnMode)
// Deprecated: legacy warning method, return errors instead
Warn(msg string, args ...any)
}
@ -24,7 +24,7 @@ var NoopLogger DatabaseLogger = &noopLogger{}
func (n noopLogger) WarnUnsupportedVersion(_, _, _ int) {}
func (n noopLogger) PrepareUpgrade(_, _, _ int) {}
func (n noopLogger) DoUpgrade(_, _ int, _ string, _ bool) {}
func (n noopLogger) DoUpgrade(_, _ int, _ string, _ TxnMode) {}
func (n noopLogger) Warn(msg string, args ...any) {}
func (n noopLogger) QueryTiming(_ context.Context, _, _ string, _ []any, _ int, _ time.Duration, _ error) {
@ -81,11 +81,11 @@ func (z zeroLogger) PrepareUpgrade(current, compat, latest int) {
}
}
func (z zeroLogger) DoUpgrade(from, to int, message string, txn bool) {
func (z zeroLogger) DoUpgrade(from, to int, message string, txn TxnMode) {
z.l.Info().
Int("from", from).
Int("to", to).
Bool("single_txn", txn).
Str("txn_mode", string(txn)).
Str("description", message).
Msg("Upgrading database")
}

View file

@ -117,7 +117,7 @@ func (qh *QueryHelper[T]) scanNew(row Scannable) (T, error) {
func (qh *QueryHelper[T]) QueryOne(ctx context.Context, query string, args ...any) (val T, err error) {
val, err = qh.scanNew(qh.db.QueryRow(ctx, query, args...))
if errors.Is(err, sql.ErrNoRows) {
err = nil
return *new(T), nil
}
return val, err
}

View file

@ -40,25 +40,25 @@ func init() {
}
func (db *Database) Exec(ctx context.Context, query string, args ...any) (sql.Result, error) {
return db.Conn(ctx).ExecContext(ctx, query, args...)
return db.Execable(ctx).ExecContext(ctx, query, args...)
}
func (db *Database) Query(ctx context.Context, query string, args ...any) (Rows, error) {
return db.Conn(ctx).QueryContext(ctx, query, args...)
return db.Execable(ctx).QueryContext(ctx, query, args...)
}
func (db *Database) QueryRow(ctx context.Context, query string, args ...any) *sql.Row {
return db.Conn(ctx).QueryRowContext(ctx, query, args...)
return db.Execable(ctx).QueryRowContext(ctx, query, args...)
}
func (db *Database) BeginTx(ctx context.Context, opts *sql.TxOptions) (*LoggingTxn, error) {
func (db *Database) BeginTx(ctx context.Context, opts *TxnOptions) (*LoggingTxn, error) {
if ctx == nil {
panic("BeginTx() called with nil ctx")
}
return db.LoggingDB.BeginTx(ctx, opts)
}
func (db *Database) DoTxn(ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context) error) error {
func (db *Database) DoTxn(ctx context.Context, opts *TxnOptions, fn func(ctx context.Context) error) error {
if ctx == nil {
panic("DoTxn() called with nil ctx")
}
@ -133,7 +133,7 @@ func (db *Database) DoTxn(ctx context.Context, opts *sql.TxOptions, fn func(ctx
return nil
}
func (db *Database) Conn(ctx context.Context) Execable {
func (db *Database) Execable(ctx context.Context) Execable {
if ctx == nil {
panic("Conn() called with nil ctx")
}
@ -143,3 +143,21 @@ func (db *Database) Conn(ctx context.Context) Execable {
}
return &db.LoggingDB
}
func (db *Database) AcquireConn(ctx context.Context) (Conn, error) {
if ctx == nil {
return nil, fmt.Errorf("AcquireConn() called with nil ctx")
}
_, ok := ctx.Value(db.txnCtxKey).(Transaction)
if ok {
return nil, fmt.Errorf("cannot acquire connection while in a transaction")
}
conn, err := db.RawDB.Conn(ctx)
if err != nil {
return nil, err
}
return &LoggingExecable{
UnderlyingExecable: conn,
db: db,
}, nil
}

View file

@ -21,7 +21,11 @@ type upgrade struct {
upgradesTo int
compatVersion int
transaction bool
transaction TxnMode
}
func (u *upgrade) DangerouslyRun(ctx context.Context, db *Database) (upgradesTo, compat int, err error) {
return u.upgradesTo, u.compatVersion, u.fn(ctx, db)
}
var ErrUnsupportedDatabaseVersion = errors.New("unsupported database schema version")
@ -153,6 +157,37 @@ func (db *Database) setVersion(ctx context.Context, version, compat int) error {
return err
}
func (db *Database) DoSQLiteTransactionWithoutForeignKeys(ctx context.Context, doUpgrade func(context.Context) error) error {
conn, err := db.AcquireConn(ctx)
if err != nil {
return fmt.Errorf("failed to acquire connection: %w", err)
}
_, err = conn.ExecContext(ctx, "PRAGMA foreign_keys=OFF")
if err != nil {
return fmt.Errorf("failed to disable foreign keys: %w", err)
}
err = db.DoTxn(ctx, &TxnOptions{Conn: conn}, func(ctx context.Context) error {
err := doUpgrade(ctx)
if err != nil {
return err
}
_, err = conn.ExecContext(ctx, "PRAGMA foreign_key_check")
if err != nil {
return fmt.Errorf("failed to check foreign keys after upgrade: %w", err)
}
return nil
})
if err != nil {
_, _ = conn.ExecContext(ctx, "PRAGMA foreign_keys=ON")
return err
}
_, err = conn.ExecContext(ctx, "PRAGMA foreign_keys=ON")
if err != nil {
return fmt.Errorf("failed to enable foreign keys: %w", err)
}
return nil
}
func (db *Database) Upgrade(ctx context.Context) error {
err := db.checkDatabaseOwner(ctx)
if err != nil {
@ -194,10 +229,18 @@ func (db *Database) Upgrade(ctx context.Context) error {
return nil
}
db.Log.DoUpgrade(logVersion, upgradeItem.upgradesTo, upgradeItem.message, upgradeItem.transaction)
if upgradeItem.transaction {
err = db.DoTxn(ctx, nil, doUpgrade)
} else {
switch upgradeItem.transaction {
case TxnModeOff:
err = doUpgrade(ctx)
case TxnModeOn:
err = db.DoTxn(ctx, nil, doUpgrade)
case TxnModeSQLiteForeignKeysOff:
switch db.Dialect {
case SQLite:
err = db.DoSQLiteTransactionWithoutForeignKeys(ctx, doUpgrade)
default:
err = db.DoTxn(ctx, nil, doUpgrade)
}
}
if err != nil {
return err

View file

@ -18,6 +18,14 @@ import (
"strings"
)
type TxnMode string
const (
TxnModeOn TxnMode = "on"
TxnModeOff TxnMode = "off"
TxnModeSQLiteForeignKeysOff TxnMode = "sqlite-fkey-off"
)
type UpgradeTable []upgrade
func (ut *UpgradeTable) extend(toSize int) {
@ -30,7 +38,7 @@ func (ut *UpgradeTable) extend(toSize int) {
}
}
func (ut *UpgradeTable) Register(from, to, compat int, message string, txn bool, fn upgradeFunc) {
func (ut *UpgradeTable) Register(from, to, compat int, message string, txn TxnMode, fn upgradeFunc) {
if from < 0 {
from += to
}
@ -72,7 +80,7 @@ var upgradeHeaderRegex = regexp.MustCompile(`^-- (?:v(\d+) -> )?v(\d+)(?: \(comp
// // do dangerous stuff
var transactionDisableRegex = regexp.MustCompile(`^-- transaction: (\w*)`)
func parseFileHeader(file []byte) (from, to, compat int, message string, txn bool, lines [][]byte, err error) {
func parseFileHeader(file []byte) (from, to, compat int, message string, txn TxnMode, lines [][]byte, err error) {
lines = bytes.Split(file, []byte("\n"))
if len(lines) < 2 {
err = errors.New("upgrade file too short")
@ -99,14 +107,17 @@ func parseFileHeader(file []byte) (from, to, compat int, message string, txn boo
from = -1
}
message = string(match[4])
txn = true
txn = "on"
match = transactionDisableRegex.FindSubmatch(lines[0])
if match != nil {
lines = lines[1:]
if string(match[1]) != "off" {
txn = TxnMode(match[1])
switch txn {
case TxnModeOff, TxnModeOn, TxnModeSQLiteForeignKeysOff:
// ok
default:
err = fmt.Errorf("invalid value %q for transaction flag", match[1])
}
txn = false
}
}
return
@ -157,6 +168,16 @@ func (db *Database) parseDialectFilter(line []byte) (dialect Dialect, lineCount
var endLineFilter = regexp.MustCompile(`^\s*-- end only (postgres|sqlite)$`)
func (db *Database) Internals() *publishDatabaseInternals {
return (*publishDatabaseInternals)(db)
}
type publishDatabaseInternals Database
func (di *publishDatabaseInternals) FilterSQLUpgrade(lines [][]byte) (string, error) {
return (*Database)(di).filterSQLUpgrade(lines)
}
func (db *Database) filterSQLUpgrade(lines [][]byte) (string, error) {
output := make([][]byte, 0, len(lines))
for i := 0; i < len(lines); i++ {
@ -235,7 +256,7 @@ func splitSQLUpgradeFunc(sqliteData, postgresData string) upgradeFunc {
}
}
func parseSplitSQLUpgrade(name string, fs fullFS, skipNames map[string]struct{}) (from, to, compat int, message string, txn bool, fn upgradeFunc) {
func parseSplitSQLUpgrade(name string, fs fullFS, skipNames map[string]struct{}) (from, to, compat int, message string, txn TxnMode, fn upgradeFunc) {
postgresName := fmt.Sprintf("%s.postgres.sql", name)
sqliteName := fmt.Sprintf("%s.sqlite.sql", name)
skipNames[postgresName] = struct{}{}
@ -261,7 +282,7 @@ func parseSplitSQLUpgrade(name string, fs fullFS, skipNames map[string]struct{})
} else if message != sqliteMessage {
panic(fmt.Errorf("mismatching message in postgres and sqlite versions of %s: %q != %q", name, message, sqliteMessage))
} else if txn != sqliteTxn {
panic(fmt.Errorf("mismatching transaction flag in postgres and sqlite versions of %s: %t != %t", name, txn, sqliteTxn))
panic(fmt.Errorf("mismatching transaction flag in postgres and sqlite versions of %s: %s != %s", name, txn, sqliteTxn))
}
fn = splitSQLUpgradeFunc(string(sqliteData), string(postgresData))
return

View file

@ -0,0 +1,28 @@
// Copyright (c) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package exzerolog
import (
"fmt"
"runtime"
"strings"
)
// CallerWithFunctionName is an implementation for zerolog.CallerMarshalFunc that includes the caller function name
// in addition to the file and line number.
//
// Use as
//
// zerolog.CallerMarshalFunc = exzerolog.CallerWithFunctionName
func CallerWithFunctionName(pc uintptr, file string, line int) string {
files := strings.Split(file, "/")
file = files[len(files)-1]
name := runtime.FuncForPC(pc).Name()
fns := strings.Split(name, ".")
name = fns[len(fns)-1]
return fmt.Sprintf("%s:%d:%s()", file, line, name)
}

View file

@ -0,0 +1,32 @@
// Copyright (c) 2024 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package exzerolog
import (
"time"
"github.com/rs/zerolog"
deflog "github.com/rs/zerolog/log"
)
// SetupDefaults updates zerolog globals with sensible defaults.
//
// * [zerolog.TimeFieldFormat] is set to time.RFC3339Nano instead of time.RFC3339
// * [zerolog.CallerMarshalFunc] is set to [CallerWithFunctionName]
// * [zerolog.DefaultContextLogger] is set to the given logger with default_context_log=true and caller info enabled
// * The global default [log.Logger] is set to the given logger with global_log=true and caller info enabled
// * [zerolog.LevelColors] are updated to swap trace and debug level colors
func SetupDefaults(log *zerolog.Logger) {
zerolog.TimeFieldFormat = time.RFC3339Nano
zerolog.CallerMarshalFunc = CallerWithFunctionName
defaultCtxLog := log.With().Bool("default_context_log", true).Caller().Logger()
zerolog.DefaultContextLogger = &defaultCtxLog
deflog.Logger = log.With().Bool("global_log", true).Caller().Logger()
// Swap trace and debug level colors so trace pops out the least
zerolog.LevelColors[zerolog.TraceLevel] = 0
zerolog.LevelColors[zerolog.DebugLevel] = 34 // blue
}

View file

@ -0,0 +1,45 @@
// Copyright (c) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package exzerolog
import (
"fmt"
"github.com/rs/zerolog"
)
func ArrayOf[T any](slice []T, fn func(arr *zerolog.Array, item T)) *zerolog.Array {
arr := zerolog.Arr()
for _, item := range slice {
fn(arr, item)
}
return arr
}
func AddObject[T zerolog.LogObjectMarshaler](arr *zerolog.Array, obj T) {
arr.Object(obj)
}
func AddStringer[T fmt.Stringer](arr *zerolog.Array, str T) {
arr.Str(str.String())
}
func AddStr[T ~string](arr *zerolog.Array, str T) {
arr.Str(string(str))
}
func ArrayOfObjs[T zerolog.LogObjectMarshaler](slice []T) *zerolog.Array {
return ArrayOf(slice, AddObject[T])
}
func ArrayOfStringers[T fmt.Stringer](slice []T) *zerolog.Array {
return ArrayOf(slice, AddStringer[T])
}
func ArrayOfStrs[T ~string](slice []T) *zerolog.Array {
return ArrayOf(slice, AddStr[T])
}

View file

@ -0,0 +1,81 @@
// Copyright (c) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package exzerolog
import (
"bytes"
"sync"
"github.com/rs/zerolog"
)
// LogWriter wraps a zerolog.Logger and provides an io.Writer with buffering so each written line is logged separately.
type LogWriter struct {
log zerolog.Logger
level zerolog.Level
field string
mu sync.Mutex
buf bytes.Buffer
}
func NewLogWriter(log zerolog.Logger) *LogWriter {
zerolog.Nop()
return &LogWriter{
log: log,
level: zerolog.DebugLevel,
field: zerolog.MessageFieldName,
}
}
func (lw *LogWriter) WithLevel(level zerolog.Level) *LogWriter {
return &LogWriter{
log: lw.log,
level: level,
field: lw.field,
}
}
func (lw *LogWriter) WithField(field string) *LogWriter {
return &LogWriter{
log: lw.log,
level: lw.level,
field: field,
}
}
func (lw *LogWriter) writeLine(data []byte) {
if len(data) == 0 {
return
}
if data[len(data)-1] == '\n' {
data = data[:len(data)-1]
}
if lw.buf.Len() > 0 {
lw.buf.Write(data)
data = lw.buf.Bytes()
lw.buf.Reset()
}
lw.log.WithLevel(lw.level).Bytes(lw.field, data).Send()
}
func (lw *LogWriter) Write(data []byte) (n int, err error) {
lw.mu.Lock()
defer lw.mu.Unlock()
newline := bytes.IndexByte(data, '\n')
if newline == len(data)-1 {
lw.writeLine(data)
} else if newline < 0 {
lw.buf.Write(data)
} else {
lines := bytes.Split(data, []byte{'\n'})
for _, line := range lines[:len(lines)-1] {
lw.writeLine(line)
}
lw.buf.Write(lines[len(lines)-1])
}
return len(data), nil
}

26
vendor/go.mau.fi/util/ptr/ptr.go vendored Normal file
View file

@ -0,0 +1,26 @@
// Copyright (c) 2024 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package ptr
func Clone[T any](val *T) *T {
if val == nil {
return nil
}
valCopy := *val
return &valCopy
}
func Ptr[T any](val T) *T {
return &val
}
func Val[T any](ptr *T) (val T) {
if ptr != nil {
val = *ptr
}
return
}

View file

@ -11,7 +11,7 @@
// Deprecated: any new system should use AES (from crypto/aes, if necessary in
// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from
// golang.org/x/crypto/chacha20poly1305).
package blowfish // import "golang.org/x/crypto/blowfish"
package blowfish
// The code is a port of Bruce Schneier's C implementation.
// See https://www.schneier.com/blowfish.html.

View file

@ -6,9 +6,11 @@
// performs scalar multiplication on the elliptic curve known as Curve25519.
// See RFC 7748.
//
// Starting in Go 1.20, this package is a wrapper for the X25519 implementation
// This package is a wrapper for the X25519 implementation
// in the crypto/ecdh package.
package curve25519 // import "golang.org/x/crypto/curve25519"
package curve25519
import "crypto/ecdh"
// ScalarMult sets dst to the product scalar * point.
//
@ -16,7 +18,13 @@ package curve25519 // import "golang.org/x/crypto/curve25519"
// zeroes, irrespective of the scalar. Instead, use the X25519 function, which
// will return an error.
func ScalarMult(dst, scalar, point *[32]byte) {
scalarMult(dst, scalar, point)
if _, err := x25519(dst, scalar[:], point[:]); err != nil {
// The only error condition for x25519 when the inputs are 32 bytes long
// is if the output would have been the all-zero value.
for i := range dst {
dst[i] = 0
}
}
}
// ScalarBaseMult sets dst to the product scalar * base where base is the
@ -25,7 +33,12 @@ func ScalarMult(dst, scalar, point *[32]byte) {
// It is recommended to use the X25519 function with Basepoint instead, as
// copying into fixed size arrays can lead to unexpected bugs.
func ScalarBaseMult(dst, scalar *[32]byte) {
scalarBaseMult(dst, scalar)
curve := ecdh.X25519()
priv, err := curve.NewPrivateKey(scalar[:])
if err != nil {
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
}
copy(dst[:], priv.PublicKey().Bytes())
}
const (
@ -57,3 +70,21 @@ func X25519(scalar, point []byte) ([]byte, error) {
var dst [32]byte
return x25519(&dst, scalar, point)
}
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
curve := ecdh.X25519()
pub, err := curve.NewPublicKey(point)
if err != nil {
return nil, err
}
priv, err := curve.NewPrivateKey(scalar)
if err != nil {
return nil, err
}
out, err := priv.ECDH(pub)
if err != nil {
return nil, err
}
copy(dst[:], out)
return dst[:], nil
}

View file

@ -1,105 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.20
package curve25519
import (
"crypto/subtle"
"errors"
"strconv"
"golang.org/x/crypto/curve25519/internal/field"
)
func scalarMult(dst, scalar, point *[32]byte) {
var e [32]byte
copy(e[:], scalar[:])
e[0] &= 248
e[31] &= 127
e[31] |= 64
var x1, x2, z2, x3, z3, tmp0, tmp1 field.Element
x1.SetBytes(point[:])
x2.One()
x3.Set(&x1)
z3.One()
swap := 0
for pos := 254; pos >= 0; pos-- {
b := e[pos/8] >> uint(pos&7)
b &= 1
swap ^= int(b)
x2.Swap(&x3, swap)
z2.Swap(&z3, swap)
swap = int(b)
tmp0.Subtract(&x3, &z3)
tmp1.Subtract(&x2, &z2)
x2.Add(&x2, &z2)
z2.Add(&x3, &z3)
z3.Multiply(&tmp0, &x2)
z2.Multiply(&z2, &tmp1)
tmp0.Square(&tmp1)
tmp1.Square(&x2)
x3.Add(&z3, &z2)
z2.Subtract(&z3, &z2)
x2.Multiply(&tmp1, &tmp0)
tmp1.Subtract(&tmp1, &tmp0)
z2.Square(&z2)
z3.Mult32(&tmp1, 121666)
x3.Square(&x3)
tmp0.Add(&tmp0, &z3)
z3.Multiply(&x1, &z2)
z2.Multiply(&tmp1, &tmp0)
}
x2.Swap(&x3, swap)
z2.Swap(&z3, swap)
z2.Invert(&z2)
x2.Multiply(&x2, &z2)
copy(dst[:], x2.Bytes())
}
func scalarBaseMult(dst, scalar *[32]byte) {
checkBasepoint()
scalarMult(dst, scalar, &basePoint)
}
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
var in [32]byte
if l := len(scalar); l != 32 {
return nil, errors.New("bad scalar length: " + strconv.Itoa(l) + ", expected 32")
}
if l := len(point); l != 32 {
return nil, errors.New("bad point length: " + strconv.Itoa(l) + ", expected 32")
}
copy(in[:], scalar)
if &point[0] == &Basepoint[0] {
scalarBaseMult(dst, &in)
} else {
var base, zero [32]byte
copy(base[:], point)
scalarMult(dst, &in, &base)
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
return nil, errors.New("bad input point: low order point")
}
}
return dst[:], nil
}
func checkBasepoint() {
if subtle.ConstantTimeCompare(Basepoint, []byte{
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}) != 1 {
panic("curve25519: global Basepoint value was modified")
}
}

View file

@ -1,46 +0,0 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.20
package curve25519
import "crypto/ecdh"
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
curve := ecdh.X25519()
pub, err := curve.NewPublicKey(point)
if err != nil {
return nil, err
}
priv, err := curve.NewPrivateKey(scalar)
if err != nil {
return nil, err
}
out, err := priv.ECDH(pub)
if err != nil {
return nil, err
}
copy(dst[:], out)
return dst[:], nil
}
func scalarMult(dst, scalar, point *[32]byte) {
if _, err := x25519(dst, scalar[:], point[:]); err != nil {
// The only error condition for x25519 when the inputs are 32 bytes long
// is if the output would have been the all-zero value.
for i := range dst {
dst[i] = 0
}
}
}
func scalarBaseMult(dst, scalar *[32]byte) {
curve := ecdh.X25519()
priv, err := curve.NewPrivateKey(scalar[:])
if err != nil {
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
}
copy(dst[:], priv.PublicKey().Bytes())
}

View file

@ -1,7 +0,0 @@
This package is kept in sync with crypto/ed25519/internal/edwards25519/field in
the standard library.
If there are any changes in the standard library that need to be synced to this
package, run sync.sh. It will not overwrite any local changes made since the
previous sync, so it's ok to land changes in this package first, and then sync
to the standard library later.

View file

@ -1,416 +0,0 @@
// Copyright (c) 2017 The Go 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 field implements fast arithmetic modulo 2^255-19.
package field
import (
"crypto/subtle"
"encoding/binary"
"math/bits"
)
// Element represents an element of the field GF(2^255-19). Note that this
// is not a cryptographically secure group, and should only be used to interact
// with edwards25519.Point coordinates.
//
// This type works similarly to math/big.Int, and all arguments and receivers
// are allowed to alias.
//
// The zero value is a valid zero element.
type Element struct {
// An element t represents the integer
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
//
// Between operations, all limbs are expected to be lower than 2^52.
l0 uint64
l1 uint64
l2 uint64
l3 uint64
l4 uint64
}
const maskLow51Bits uint64 = (1 << 51) - 1
var feZero = &Element{0, 0, 0, 0, 0}
// Zero sets v = 0, and returns v.
func (v *Element) Zero() *Element {
*v = *feZero
return v
}
var feOne = &Element{1, 0, 0, 0, 0}
// One sets v = 1, and returns v.
func (v *Element) One() *Element {
*v = *feOne
return v
}
// reduce reduces v modulo 2^255 - 19 and returns it.
func (v *Element) reduce() *Element {
v.carryPropagate()
// After the light reduction we now have a field element representation
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
c := (v.l0 + 19) >> 51
c = (v.l1 + c) >> 51
c = (v.l2 + c) >> 51
c = (v.l3 + c) >> 51
c = (v.l4 + c) >> 51
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
// effectively applying the reduction identity to the carry.
v.l0 += 19 * c
v.l1 += v.l0 >> 51
v.l0 = v.l0 & maskLow51Bits
v.l2 += v.l1 >> 51
v.l1 = v.l1 & maskLow51Bits
v.l3 += v.l2 >> 51
v.l2 = v.l2 & maskLow51Bits
v.l4 += v.l3 >> 51
v.l3 = v.l3 & maskLow51Bits
// no additional carry
v.l4 = v.l4 & maskLow51Bits
return v
}
// Add sets v = a + b, and returns v.
func (v *Element) Add(a, b *Element) *Element {
v.l0 = a.l0 + b.l0
v.l1 = a.l1 + b.l1
v.l2 = a.l2 + b.l2
v.l3 = a.l3 + b.l3
v.l4 = a.l4 + b.l4
// Using the generic implementation here is actually faster than the
// assembly. Probably because the body of this function is so simple that
// the compiler can figure out better optimizations by inlining the carry
// propagation. TODO
return v.carryPropagateGeneric()
}
// Subtract sets v = a - b, and returns v.
func (v *Element) Subtract(a, b *Element) *Element {
// We first add 2 * p, to guarantee the subtraction won't underflow, and
// then subtract b (which can be up to 2^255 + 2^13 * 19).
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
return v.carryPropagate()
}
// Negate sets v = -a, and returns v.
func (v *Element) Negate(a *Element) *Element {
return v.Subtract(feZero, a)
}
// Invert sets v = 1/z mod p, and returns v.
//
// If z == 0, Invert returns v = 0.
func (v *Element) Invert(z *Element) *Element {
// Inversion is implemented as exponentiation with exponent p 2. It uses the
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
z2.Square(z) // 2
t.Square(&z2) // 4
t.Square(&t) // 8
z9.Multiply(&t, z) // 9
z11.Multiply(&z9, &z2) // 11
t.Square(&z11) // 22
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
t.Square(&z2_5_0) // 2^6 - 2^1
for i := 0; i < 4; i++ {
t.Square(&t) // 2^10 - 2^5
}
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
t.Square(&z2_10_0) // 2^11 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^20 - 2^10
}
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
t.Square(&z2_20_0) // 2^21 - 2^1
for i := 0; i < 19; i++ {
t.Square(&t) // 2^40 - 2^20
}
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
t.Square(&t) // 2^41 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^50 - 2^10
}
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
t.Square(&z2_50_0) // 2^51 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^100 - 2^50
}
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
t.Square(&z2_100_0) // 2^101 - 2^1
for i := 0; i < 99; i++ {
t.Square(&t) // 2^200 - 2^100
}
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
t.Square(&t) // 2^201 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^250 - 2^50
}
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
t.Square(&t) // 2^251 - 2^1
t.Square(&t) // 2^252 - 2^2
t.Square(&t) // 2^253 - 2^3
t.Square(&t) // 2^254 - 2^4
t.Square(&t) // 2^255 - 2^5
return v.Multiply(&t, &z11) // 2^255 - 21
}
// Set sets v = a, and returns v.
func (v *Element) Set(a *Element) *Element {
*v = *a
return v
}
// SetBytes sets v to x, which must be a 32-byte little-endian encoding.
//
// Consistent with RFC 7748, the most significant bit (the high bit of the
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
// are accepted. Note that this is laxer than specified by RFC 8032.
func (v *Element) SetBytes(x []byte) *Element {
if len(x) != 32 {
panic("edwards25519: invalid field element input size")
}
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
v.l0 = binary.LittleEndian.Uint64(x[0:8])
v.l0 &= maskLow51Bits
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
v.l1 &= maskLow51Bits
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
v.l2 &= maskLow51Bits
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
v.l3 &= maskLow51Bits
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
// Note: not bytes 25:33, shift 4, to avoid overread.
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
v.l4 &= maskLow51Bits
return v
}
// Bytes returns the canonical 32-byte little-endian encoding of v.
func (v *Element) Bytes() []byte {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap.
var out [32]byte
return v.bytes(&out)
}
func (v *Element) bytes(out *[32]byte) []byte {
t := *v
t.reduce()
var buf [8]byte
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
bitsOffset := i * 51
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
for i, bb := range buf {
off := bitsOffset/8 + i
if off >= len(out) {
break
}
out[off] |= bb
}
}
return out[:]
}
// Equal returns 1 if v and u are equal, and 0 otherwise.
func (v *Element) Equal(u *Element) int {
sa, sv := u.Bytes(), v.Bytes()
return subtle.ConstantTimeCompare(sa, sv)
}
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
// Select sets v to a if cond == 1, and to b if cond == 0.
func (v *Element) Select(a, b *Element, cond int) *Element {
m := mask64Bits(cond)
v.l0 = (m & a.l0) | (^m & b.l0)
v.l1 = (m & a.l1) | (^m & b.l1)
v.l2 = (m & a.l2) | (^m & b.l2)
v.l3 = (m & a.l3) | (^m & b.l3)
v.l4 = (m & a.l4) | (^m & b.l4)
return v
}
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
func (v *Element) Swap(u *Element, cond int) {
m := mask64Bits(cond)
t := m & (v.l0 ^ u.l0)
v.l0 ^= t
u.l0 ^= t
t = m & (v.l1 ^ u.l1)
v.l1 ^= t
u.l1 ^= t
t = m & (v.l2 ^ u.l2)
v.l2 ^= t
u.l2 ^= t
t = m & (v.l3 ^ u.l3)
v.l3 ^= t
u.l3 ^= t
t = m & (v.l4 ^ u.l4)
v.l4 ^= t
u.l4 ^= t
}
// IsNegative returns 1 if v is negative, and 0 otherwise.
func (v *Element) IsNegative() int {
return int(v.Bytes()[0] & 1)
}
// Absolute sets v to |u|, and returns v.
func (v *Element) Absolute(u *Element) *Element {
return v.Select(new(Element).Negate(u), u, u.IsNegative())
}
// Multiply sets v = x * y, and returns v.
func (v *Element) Multiply(x, y *Element) *Element {
feMul(v, x, y)
return v
}
// Square sets v = x * x, and returns v.
func (v *Element) Square(x *Element) *Element {
feSquare(v, x)
return v
}
// Mult32 sets v = x * y, and returns v.
func (v *Element) Mult32(x *Element, y uint32) *Element {
x0lo, x0hi := mul51(x.l0, y)
x1lo, x1hi := mul51(x.l1, y)
x2lo, x2hi := mul51(x.l2, y)
x3lo, x3hi := mul51(x.l3, y)
x4lo, x4hi := mul51(x.l4, y)
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
v.l1 = x1lo + x0hi
v.l2 = x2lo + x1hi
v.l3 = x3lo + x2hi
v.l4 = x4lo + x3hi
// The hi portions are going to be only 32 bits, plus any previous excess,
// so we can skip the carry propagation.
return v
}
// mul51 returns lo + hi * 2⁵¹ = a * b.
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
mh, ml := bits.Mul64(a, uint64(b))
lo = ml & maskLow51Bits
hi = (mh << 13) | (ml >> 51)
return
}
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
func (v *Element) Pow22523(x *Element) *Element {
var t0, t1, t2 Element
t0.Square(x) // x^2
t1.Square(&t0) // x^4
t1.Square(&t1) // x^8
t1.Multiply(x, &t1) // x^9
t0.Multiply(&t0, &t1) // x^11
t0.Square(&t0) // x^22
t0.Multiply(&t1, &t0) // x^31
t1.Square(&t0) // x^62
for i := 1; i < 5; i++ { // x^992
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
t1.Square(&t0) // 2^11 - 2
for i := 1; i < 10; i++ { // 2^20 - 2^10
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^20 - 1
t2.Square(&t1) // 2^21 - 2
for i := 1; i < 20; i++ { // 2^40 - 2^20
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^40 - 1
t1.Square(&t1) // 2^41 - 2
for i := 1; i < 10; i++ { // 2^50 - 2^10
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^50 - 1
t1.Square(&t0) // 2^51 - 2
for i := 1; i < 50; i++ { // 2^100 - 2^50
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^100 - 1
t2.Square(&t1) // 2^101 - 2
for i := 1; i < 100; i++ { // 2^200 - 2^100
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^200 - 1
t1.Square(&t1) // 2^201 - 2
for i := 1; i < 50; i++ { // 2^250 - 2^50
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^250 - 1
t0.Square(&t0) // 2^251 - 2
t0.Square(&t0) // 2^252 - 4
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
}
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
var sqrtM1 = &Element{1718705420411056, 234908883556509,
2233514472574048, 2117202627021982, 765476049583133}
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
//
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
// and returns r and 0.
func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) {
var a, b Element
// r = (u * v3) * (u * v7)^((p-5)/8)
v2 := a.Square(v)
uv3 := b.Multiply(u, b.Multiply(v2, v))
uv7 := a.Multiply(uv3, a.Square(v2))
r.Multiply(uv3, r.Pow22523(uv7))
check := a.Multiply(v, a.Square(r)) // check = v * r^2
uNeg := b.Negate(u)
correctSignSqrt := check.Equal(u)
flippedSignSqrt := check.Equal(uNeg)
flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1))
rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI)
r.Absolute(r) // Choose the nonnegative square root.
return r, correctSignSqrt | flippedSignSqrt
}

View file

@ -1,15 +0,0 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
//go:build amd64 && gc && !purego
package field
// feMul sets out = a * b. It works like feMulGeneric.
//
//go:noescape
func feMul(out *Element, a *Element, b *Element)
// feSquare sets out = a * a. It works like feSquareGeneric.
//
//go:noescape
func feSquare(out *Element, a *Element)

View file

@ -1,378 +0,0 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
//go:build amd64 && gc && !purego
#include "textflag.h"
// func feMul(out *Element, a *Element, b *Element)
TEXT ·feMul(SB), NOSPLIT, $0-24
MOVQ a+8(FP), CX
MOVQ b+16(FP), BX
// r0 = a0×b0
MOVQ (CX), AX
MULQ (BX)
MOVQ AX, DI
MOVQ DX, SI
// r0 += 19×a1×b4
MOVQ 8(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a2×b3
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a3×b2
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a4×b1
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 8(BX)
ADDQ AX, DI
ADCQ DX, SI
// r1 = a0×b1
MOVQ (CX), AX
MULQ 8(BX)
MOVQ AX, R9
MOVQ DX, R8
// r1 += a1×b0
MOVQ 8(CX), AX
MULQ (BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a2×b4
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a3×b3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a4×b2
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, R9
ADCQ DX, R8
// r2 = a0×b2
MOVQ (CX), AX
MULQ 16(BX)
MOVQ AX, R11
MOVQ DX, R10
// r2 += a1×b1
MOVQ 8(CX), AX
MULQ 8(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += a2×b0
MOVQ 16(CX), AX
MULQ (BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a3×b4
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a4×b3
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R11
ADCQ DX, R10
// r3 = a0×b3
MOVQ (CX), AX
MULQ 24(BX)
MOVQ AX, R13
MOVQ DX, R12
// r3 += a1×b2
MOVQ 8(CX), AX
MULQ 16(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a2×b1
MOVQ 16(CX), AX
MULQ 8(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a3×b0
MOVQ 24(CX), AX
MULQ (BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += 19×a4×b4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R13
ADCQ DX, R12
// r4 = a0×b4
MOVQ (CX), AX
MULQ 32(BX)
MOVQ AX, R15
MOVQ DX, R14
// r4 += a1×b3
MOVQ 8(CX), AX
MULQ 24(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a2×b2
MOVQ 16(CX), AX
MULQ 16(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a3×b1
MOVQ 24(CX), AX
MULQ 8(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a4×b0
MOVQ 32(CX), AX
MULQ (BX)
ADDQ AX, R15
ADCQ DX, R14
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, DI, SI
SHLQ $0x0d, R9, R8
SHLQ $0x0d, R11, R10
SHLQ $0x0d, R13, R12
SHLQ $0x0d, R15, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Second reduction chain (carryPropagate)
MOVQ DI, SI
SHRQ $0x33, SI
MOVQ R9, R8
SHRQ $0x33, R8
MOVQ R11, R10
SHRQ $0x33, R10
MOVQ R13, R12
SHRQ $0x33, R12
MOVQ R15, R14
SHRQ $0x33, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Store output
MOVQ out+0(FP), AX
MOVQ DI, (AX)
MOVQ R9, 8(AX)
MOVQ R11, 16(AX)
MOVQ R13, 24(AX)
MOVQ R15, 32(AX)
RET
// func feSquare(out *Element, a *Element)
TEXT ·feSquare(SB), NOSPLIT, $0-16
MOVQ a+8(FP), CX
// r0 = l0×l0
MOVQ (CX), AX
MULQ (CX)
MOVQ AX, SI
MOVQ DX, BX
// r0 += 38×l1×l4
MOVQ 8(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, SI
ADCQ DX, BX
// r0 += 38×l2×l3
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 24(CX)
ADDQ AX, SI
ADCQ DX, BX
// r1 = 2×l0×l1
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 8(CX)
MOVQ AX, R8
MOVQ DX, DI
// r1 += 38×l2×l4
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R8
ADCQ DX, DI
// r1 += 19×l3×l3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(CX)
ADDQ AX, R8
ADCQ DX, DI
// r2 = 2×l0×l2
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 16(CX)
MOVQ AX, R10
MOVQ DX, R9
// r2 += l1×l1
MOVQ 8(CX), AX
MULQ 8(CX)
ADDQ AX, R10
ADCQ DX, R9
// r2 += 38×l3×l4
MOVQ 24(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R10
ADCQ DX, R9
// r3 = 2×l0×l3
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 24(CX)
MOVQ AX, R12
MOVQ DX, R11
// r3 += 2×l1×l2
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 16(CX)
ADDQ AX, R12
ADCQ DX, R11
// r3 += 19×l4×l4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(CX)
ADDQ AX, R12
ADCQ DX, R11
// r4 = 2×l0×l4
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 32(CX)
MOVQ AX, R14
MOVQ DX, R13
// r4 += 2×l1×l3
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 24(CX)
ADDQ AX, R14
ADCQ DX, R13
// r4 += l2×l2
MOVQ 16(CX), AX
MULQ 16(CX)
ADDQ AX, R14
ADCQ DX, R13
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, SI, BX
SHLQ $0x0d, R8, DI
SHLQ $0x0d, R10, R9
SHLQ $0x0d, R12, R11
SHLQ $0x0d, R14, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Second reduction chain (carryPropagate)
MOVQ SI, BX
SHRQ $0x33, BX
MOVQ R8, DI
SHRQ $0x33, DI
MOVQ R10, R9
SHRQ $0x33, R9
MOVQ R12, R11
SHRQ $0x33, R11
MOVQ R14, R13
SHRQ $0x33, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Store output
MOVQ out+0(FP), AX
MOVQ SI, (AX)
MOVQ R8, 8(AX)
MOVQ R10, 16(AX)
MOVQ R12, 24(AX)
MOVQ R14, 32(AX)
RET

View file

@ -1,11 +0,0 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !amd64 || !gc || purego
package field
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
func feSquare(v, x *Element) { feSquareGeneric(v, x) }

View file

@ -1,15 +0,0 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
package field
//go:noescape
func carryPropagate(v *Element)
func (v *Element) carryPropagate() *Element {
carryPropagate(v)
return v
}

View file

@ -1,42 +0,0 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
#include "textflag.h"
// carryPropagate works exactly like carryPropagateGeneric and uses the
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
// avoids loading R0-R4 twice and uses LDP and STP.
//
// See https://golang.org/issues/43145 for the main compiler issue.
//
// func carryPropagate(v *Element)
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
MOVD v+0(FP), R20
LDP 0(R20), (R0, R1)
LDP 16(R20), (R2, R3)
MOVD 32(R20), R4
AND $0x7ffffffffffff, R0, R10
AND $0x7ffffffffffff, R1, R11
AND $0x7ffffffffffff, R2, R12
AND $0x7ffffffffffff, R3, R13
AND $0x7ffffffffffff, R4, R14
ADD R0>>51, R11, R11
ADD R1>>51, R12, R12
ADD R2>>51, R13, R13
ADD R3>>51, R14, R14
// R4>>51 * 19 + R10 -> R10
LSR $51, R4, R21
MOVD $19, R22
MADD R22, R10, R21, R10
STP (R10, R11), 0(R20)
STP (R12, R13), 16(R20)
MOVD R14, 32(R20)
RET

View file

@ -1,11 +0,0 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !arm64 || !gc || purego
package field
func (v *Element) carryPropagate() *Element {
return v.carryPropagateGeneric()
}

View file

@ -1,264 +0,0 @@
// Copyright (c) 2017 The Go 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 field
import "math/bits"
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
// bits.Mul64 and bits.Add64 intrinsics.
type uint128 struct {
lo, hi uint64
}
// mul64 returns a * b.
func mul64(a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
return uint128{lo, hi}
}
// addMul64 returns v + a * b.
func addMul64(v uint128, a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
lo, c := bits.Add64(lo, v.lo, 0)
hi, _ = bits.Add64(hi, v.hi, c)
return uint128{lo, hi}
}
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
func shiftRightBy51(a uint128) uint64 {
return (a.hi << (64 - 51)) | (a.lo >> 51)
}
func feMulGeneric(v, a, b *Element) {
a0 := a.l0
a1 := a.l1
a2 := a.l2
a3 := a.l3
a4 := a.l4
b0 := b.l0
b1 := b.l1
b2 := b.l2
b3 := b.l3
b4 := b.l4
// Limb multiplication works like pen-and-paper columnar multiplication, but
// with 51-bit limbs instead of digits.
//
// a4 a3 a2 a1 a0 x
// b4 b3 b2 b1 b0 =
// ------------------------
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a4b1 a3b1 a2b1 a1b1 a0b1 +
// a4b2 a3b2 a2b2 a1b2 a0b2 +
// a4b3 a3b3 a2b3 a1b3 a0b3 +
// a4b4 a3b4 a2b4 a1b4 a0b4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
//
// Reduction can be carried out simultaneously to multiplication. For
// example, we do not compute r5: whenever the result of a multiplication
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
//
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// Finally we add up the columns into wide, overlapping limbs.
a1_19 := a1 * 19
a2_19 := a2 * 19
a3_19 := a3 * 19
a4_19 := a4 * 19
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
r0 := mul64(a0, b0)
r0 = addMul64(r0, a1_19, b4)
r0 = addMul64(r0, a2_19, b3)
r0 = addMul64(r0, a3_19, b2)
r0 = addMul64(r0, a4_19, b1)
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
r1 := mul64(a0, b1)
r1 = addMul64(r1, a1, b0)
r1 = addMul64(r1, a2_19, b4)
r1 = addMul64(r1, a3_19, b3)
r1 = addMul64(r1, a4_19, b2)
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
r2 := mul64(a0, b2)
r2 = addMul64(r2, a1, b1)
r2 = addMul64(r2, a2, b0)
r2 = addMul64(r2, a3_19, b4)
r2 = addMul64(r2, a4_19, b3)
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
r3 := mul64(a0, b3)
r3 = addMul64(r3, a1, b2)
r3 = addMul64(r3, a2, b1)
r3 = addMul64(r3, a3, b0)
r3 = addMul64(r3, a4_19, b4)
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
r4 := mul64(a0, b4)
r4 = addMul64(r4, a1, b3)
r4 = addMul64(r4, a2, b2)
r4 = addMul64(r4, a3, b1)
r4 = addMul64(r4, a4, b0)
// After the multiplication, we need to reduce (carry) the five coefficients
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
// to respect the Element invariant.
//
// Overall, the reduction works the same as carryPropagate, except with
// wider inputs: we take the carry for each coefficient by shifting it right
// by 51, and add it to the limb above it. The top carry is multiplied by 19
// according to the reduction identity and added to the lowest limb.
//
// The largest coefficient (r0) will be at most 111 bits, which guarantees
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
//
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
// r0 < 2⁷ × 2⁵² × 2⁵²
// r0 < 2¹¹¹
//
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
// allows us to easily apply the reduction identity.
//
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
// r4 < 5 × 2⁵² × 2⁵²
// r4 < 2¹⁰⁷
//
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
// Now all coefficients fit into 64-bit registers but are still too large to
// be passed around as a Element. We therefore do one last carry chain,
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
func feSquareGeneric(v, a *Element) {
l0 := a.l0
l1 := a.l1
l2 := a.l2
l3 := a.l3
l4 := a.l4
// Squaring works precisely like multiplication above, but thanks to its
// symmetry we get to group a few terms together.
//
// l4 l3 l2 l1 l0 x
// l4 l3 l2 l1 l0 =
// ------------------------
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l4l1 l3l1 l2l1 l1l1 l0l1 +
// l4l2 l3l2 l2l2 l1l2 l0l2 +
// l4l3 l3l3 l2l3 l1l3 l0l3 +
// l4l4 l3l4 l2l4 l1l4 l0l4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
// only three Mul64 and four Add64, instead of five and eight.
l0_2 := l0 * 2
l1_2 := l1 * 2
l1_38 := l1 * 38
l2_38 := l2 * 38
l3_38 := l3 * 38
l3_19 := l3 * 19
l4_19 := l4 * 19
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
r0 := mul64(l0, l0)
r0 = addMul64(r0, l1_38, l4)
r0 = addMul64(r0, l2_38, l3)
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
r1 := mul64(l0_2, l1)
r1 = addMul64(r1, l2_38, l4)
r1 = addMul64(r1, l3_19, l3)
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
r2 := mul64(l0_2, l2)
r2 = addMul64(r2, l1, l1)
r2 = addMul64(r2, l3_38, l4)
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
r3 := mul64(l0_2, l3)
r3 = addMul64(r3, l1_2, l2)
r3 = addMul64(r3, l4_19, l4)
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
r4 := mul64(l0_2, l4)
r4 = addMul64(r4, l1_2, l3)
r4 = addMul64(r4, l2, l2)
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
// carryPropagateGeneric brings the limbs below 52 bits by applying the reduction
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. TODO inline
func (v *Element) carryPropagateGeneric() *Element {
c0 := v.l0 >> 51
c1 := v.l1 >> 51
c2 := v.l2 >> 51
c3 := v.l3 >> 51
c4 := v.l4 >> 51
v.l0 = v.l0&maskLow51Bits + c4*19
v.l1 = v.l1&maskLow51Bits + c0
v.l2 = v.l2&maskLow51Bits + c1
v.l3 = v.l3&maskLow51Bits + c2
v.l4 = v.l4&maskLow51Bits + c3
return v
}

View file

@ -1 +0,0 @@
b0c49ae9f59d233526f8934262c5bbbe14d4358d

View file

@ -1,19 +0,0 @@
#! /bin/bash
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
STD_PATH=src/crypto/ed25519/internal/edwards25519/field
LOCAL_PATH=curve25519/internal/field
LAST_SYNC_REF=$(cat $LOCAL_PATH/sync.checkpoint)
git fetch https://go.googlesource.com/go master
if git diff --quiet $LAST_SYNC_REF:$STD_PATH FETCH_HEAD:$STD_PATH; then
echo "No changes."
else
NEW_REF=$(git rev-parse FETCH_HEAD | tee $LOCAL_PATH/sync.checkpoint)
echo "Applying changes from $LAST_SYNC_REF to $NEW_REF..."
git diff $LAST_SYNC_REF:$STD_PATH FETCH_HEAD:$STD_PATH | \
git apply -3 --directory=$LOCAL_PATH
fi

View file

@ -11,9 +11,7 @@
// operations with the same key more efficient. This package refers to the RFC
// 8032 private key as the “seed”.
//
// Beginning with Go 1.13, the functionality of this package was moved to the
// standard library as crypto/ed25519. This package only acts as a compatibility
// wrapper.
// This package is a wrapper around the standard library crypto/ed25519 package.
package ed25519
import (

View file

@ -8,7 +8,7 @@
// HKDF is a cryptographic key derivation function (KDF) with the goal of
// expanding limited input keying material into one or more cryptographically
// strong secret keys.
package hkdf // import "golang.org/x/crypto/hkdf"
package hkdf
import (
"crypto/hmac"

View file

@ -16,7 +16,7 @@ Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
choose, you can pass the `New` functions from the different SHA packages to
pbkdf2.Key.
*/
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
package pbkdf2
import (
"crypto/hmac"

View file

@ -71,6 +71,10 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
for auth := AuthMethod(new(noneAuth)); auth != nil; {
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand, extensions)
if err != nil {
// On disconnect, return error immediately
if _, ok := err.(*disconnectMsg); ok {
return err
}
// We return the error later if there is no other method left to
// try.
ok = authFailure

View file

@ -20,4 +20,4 @@ References:
This package does not fall under the stability promise of the Go language itself,
so its API may be changed when pressing needs arise.
*/
package ssh // import "golang.org/x/crypto/ssh"
package ssh

4
vendor/golang.org/x/exp/LICENSE generated vendored
View file

@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

View file

@ -50,3 +50,8 @@ func (m *mremapMmapper) Mremap(oldData []byte, newLength int, flags int) (data [
func Mremap(oldData []byte, newLength int, flags int) (data []byte, err error) {
return mapper.Mremap(oldData, newLength, flags)
}
func MremapPtr(oldAddr unsafe.Pointer, oldSize uintptr, newAddr unsafe.Pointer, newSize uintptr, flags int) (ret unsafe.Pointer, err error) {
xaddr, err := mapper.mremap(uintptr(oldAddr), oldSize, newSize, flags, uintptr(newAddr))
return unsafe.Pointer(xaddr), err
}

View file

@ -542,6 +542,18 @@ func SysctlKinfoProcSlice(name string, args ...int) ([]KinfoProc, error) {
}
}
//sys pthread_chdir_np(path string) (err error)
func PthreadChdir(path string) (err error) {
return pthread_chdir_np(path)
}
//sys pthread_fchdir_np(fd int) (err error)
func PthreadFchdir(fd int) (err error) {
return pthread_fchdir_np(fd)
}
//sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error)
//sys shmat(id int, addr uintptr, flag int) (ret uintptr, err error)

View file

@ -154,6 +154,15 @@ func Munmap(b []byte) (err error) {
return mapper.Munmap(b)
}
func MmapPtr(fd int, offset int64, addr unsafe.Pointer, length uintptr, prot int, flags int) (ret unsafe.Pointer, err error) {
xaddr, err := mapper.mmap(uintptr(addr), length, prot, flags, fd, offset)
return unsafe.Pointer(xaddr), err
}
func MunmapPtr(addr unsafe.Pointer, length uintptr) (err error) {
return mapper.munmap(uintptr(addr), length)
}
func Read(fd int, p []byte) (n int, err error) {
n, err = read(fd, p)
if raceenabled {

View file

@ -760,6 +760,39 @@ var libc_sysctl_trampoline_addr uintptr
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func pthread_chdir_np(path string) (err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
_, _, e1 := syscall_syscall(libc_pthread_chdir_np_trampoline_addr, uintptr(unsafe.Pointer(_p0)), 0, 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
var libc_pthread_chdir_np_trampoline_addr uintptr
//go:cgo_import_dynamic libc_pthread_chdir_np pthread_chdir_np "/usr/lib/libSystem.B.dylib"
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func pthread_fchdir_np(fd int) (err error) {
_, _, e1 := syscall_syscall(libc_pthread_fchdir_np_trampoline_addr, uintptr(fd), 0, 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
var libc_pthread_fchdir_np_trampoline_addr uintptr
//go:cgo_import_dynamic libc_pthread_fchdir_np pthread_fchdir_np "/usr/lib/libSystem.B.dylib"
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) {
_, _, e1 := syscall_syscall6(libc_sendfile_trampoline_addr, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags))
if e1 != 0 {

View file

@ -228,6 +228,16 @@ TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0
GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8
DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB)
TEXT libc_pthread_chdir_np_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_pthread_chdir_np(SB)
GLOBL ·libc_pthread_chdir_np_trampoline_addr(SB), RODATA, $8
DATA ·libc_pthread_chdir_np_trampoline_addr(SB)/8, $libc_pthread_chdir_np_trampoline<>(SB)
TEXT libc_pthread_fchdir_np_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_pthread_fchdir_np(SB)
GLOBL ·libc_pthread_fchdir_np_trampoline_addr(SB), RODATA, $8
DATA ·libc_pthread_fchdir_np_trampoline_addr(SB)/8, $libc_pthread_fchdir_np_trampoline<>(SB)
TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_sendfile(SB)
GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8

View file

@ -760,6 +760,39 @@ var libc_sysctl_trampoline_addr uintptr
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func pthread_chdir_np(path string) (err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
_, _, e1 := syscall_syscall(libc_pthread_chdir_np_trampoline_addr, uintptr(unsafe.Pointer(_p0)), 0, 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
var libc_pthread_chdir_np_trampoline_addr uintptr
//go:cgo_import_dynamic libc_pthread_chdir_np pthread_chdir_np "/usr/lib/libSystem.B.dylib"
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func pthread_fchdir_np(fd int) (err error) {
_, _, e1 := syscall_syscall(libc_pthread_fchdir_np_trampoline_addr, uintptr(fd), 0, 0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
var libc_pthread_fchdir_np_trampoline_addr uintptr
//go:cgo_import_dynamic libc_pthread_fchdir_np pthread_fchdir_np "/usr/lib/libSystem.B.dylib"
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) {
_, _, e1 := syscall_syscall6(libc_sendfile_trampoline_addr, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags))
if e1 != 0 {

View file

@ -228,6 +228,16 @@ TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0
GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8
DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB)
TEXT libc_pthread_chdir_np_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_pthread_chdir_np(SB)
GLOBL ·libc_pthread_chdir_np_trampoline_addr(SB), RODATA, $8
DATA ·libc_pthread_chdir_np_trampoline_addr(SB)/8, $libc_pthread_chdir_np_trampoline<>(SB)
TEXT libc_pthread_fchdir_np_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_pthread_fchdir_np(SB)
GLOBL ·libc_pthread_fchdir_np_trampoline_addr(SB), RODATA, $8
DATA ·libc_pthread_fchdir_np_trampoline_addr(SB)/8, $libc_pthread_fchdir_np_trampoline<>(SB)
TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_sendfile(SB)
GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8

View file

@ -894,7 +894,7 @@ type ACL struct {
aclRevision byte
sbz1 byte
aclSize uint16
aceCount uint16
AceCount uint16
sbz2 uint16
}
@ -1087,6 +1087,27 @@ type EXPLICIT_ACCESS struct {
Trustee TRUSTEE
}
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
type ACE_HEADER struct {
AceType uint8
AceFlags uint8
AceSize uint16
}
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace
type ACCESS_ALLOWED_ACE struct {
Header ACE_HEADER
Mask ACCESS_MASK
SidStart uint32
}
const (
// Constants for AceType
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
ACCESS_ALLOWED_ACE_TYPE = 0
ACCESS_DENIED_ACE_TYPE = 1
)
// This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions.
type TrusteeValue uintptr
@ -1158,6 +1179,7 @@ type OBJECTS_AND_NAME struct {
//sys makeSelfRelativeSD(absoluteSD *SECURITY_DESCRIPTOR, selfRelativeSD *SECURITY_DESCRIPTOR, selfRelativeSDSize *uint32) (err error) = advapi32.MakeSelfRelativeSD
//sys setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCESS, oldACL *ACL, newACL **ACL) (ret error) = advapi32.SetEntriesInAclW
//sys GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) = advapi32.GetAce
// Control returns the security descriptor control bits.
func (sd *SECURITY_DESCRIPTOR) Control() (control SECURITY_DESCRIPTOR_CONTROL, revision uint32, err error) {

View file

@ -91,6 +91,7 @@ var (
procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW")
procEqualSid = modadvapi32.NewProc("EqualSid")
procFreeSid = modadvapi32.NewProc("FreeSid")
procGetAce = modadvapi32.NewProc("GetAce")
procGetLengthSid = modadvapi32.NewProc("GetLengthSid")
procGetNamedSecurityInfoW = modadvapi32.NewProc("GetNamedSecurityInfoW")
procGetSecurityDescriptorControl = modadvapi32.NewProc("GetSecurityDescriptorControl")
@ -1224,6 +1225,14 @@ func setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCE
return
}
func GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) {
r0, _, _ := syscall.Syscall(procGetAce.Addr(), 3, uintptr(unsafe.Pointer(acl)), uintptr(aceIndex), uintptr(unsafe.Pointer(pAce)))
if r0 == 0 {
ret = GetLastError()
}
return
}
func SetKernelObjectSecurity(handle Handle, securityInformation SECURITY_INFORMATION, securityDescriptor *SECURITY_DESCRIPTOR) (err error) {
r1, _, e1 := syscall.Syscall(procSetKernelObjectSecurity.Addr(), 3, uintptr(handle), uintptr(securityInformation), uintptr(unsafe.Pointer(securityDescriptor)))
if r1 == 0 {

View file

@ -1,4 +1,4 @@
.idea/
.vscode/
*.db
*.db*
*.log

View file

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.6.0
hooks:
- id: trailing-whitespace
exclude_types: [markdown]
@ -17,6 +17,7 @@ repos:
- "maunium.net/go/mautrix"
- "-w"
- id: go-vet-repo-mod
- id: go-mod-tidy
- repo: https://github.com/beeper/pre-commit-go
rev: v0.3.1

View file

@ -1,3 +1,69 @@
## v0.19.0 (2024-07-16)
* Renamed `master` branch to `main`.
* *(bridgev2)* Added more features.
* *(crypto)* Fixed bug with copying `m.relates_to` from wire content to
decrypted content.
* *(mediaproxy)* Added module for implementing simple media repos that proxy
requests elsewhere.
* *(client)* Changed `Members()` to automatically parse event content for all
returned events.
* *(bridge)* Added `/register` call if `/versions` fails with `M_FORBIDDEN`.
* *(crypto)* Fixed `DecryptMegolmEvent` sometimes calling database without
transaction by using the non-context version of `ResolveTrust`.
* *(crypto/attachment)* Implemented `io.Seeker` in `EncryptStream` to allow
using it in retriable HTTP requests.
* *(event)* Added helper method to add user ID to a `Mentions` object.
* *(event)* Fixed default power level for invites
(thanks to [@rudis] in [#250]).
* *(client)* Fixed incorrect warning log in `State()` when state store returns
no error (thanks to [@rudis] in [#249]).
* *(crypto/verificationhelper)* Fixed deadlock when ignoring unknown
cancellation events (thanks to [@rudis] in [#247]).
[@rudis]: https://github.com/rudis
[#250]: https://github.com/mautrix/go/pull/250
[#249]: https://github.com/mautrix/go/pull/249
[#247]: https://github.com/mautrix/go/pull/247
### beta.1 (2024-06-16)
* *(bridgev2)* Added experimental high-level bridge framework.
* *(hicli)* Added experimental high-level client framework.
* **Slightly breaking changes**
* *(crypto)* Added room ID and first known index parameters to
`SessionReceived` callback.
* *(crypto)* Changed `ImportRoomKeyFromBackup` to return the imported
session.
* *(client)* Added `error` parameter to `ResponseHook`.
* *(client)* Changed `Download` to return entire response instead of just an
`io.Reader`.
* *(crypto)* Changed initial olm device sharing to save keys before sharing to
ensure keys aren't accidentally regenerated in case the request fails.
* *(crypto)* Changed `EncryptMegolmEvent` and `ShareGroupSession` to return
more errors instead of only logging and ignoring them.
* *(crypto)* Added option to completely disable megolm ratchet tracking.
* The tracking is meant for bots and bridges which may want to delete old
keys, but for normal clients it's just unnecessary overhead.
* *(crypto)* Changed Megolm session storage methods in `Store` to not take
sender key as parameter.
* This causes a breaking change to the layout of the `MemoryStore` struct.
Using MemoryStore in production is not recommended.
* *(crypto)* Changed `DecryptMegolmEvent` to copy `m.relates_to` in the raw
content too instead of only in the parsed struct.
* *(crypto)* Exported function to parse megolm message index from raw
ciphertext bytes.
* *(crypto/sqlstore)* Fixed schema of `crypto_secrets` table to include
account ID.
* *(crypto/verificationhelper)* Fixed more bugs.
* *(client)* Added `UpdateRequestOnRetry` hook which is called immediately
before retrying a normal HTTP request.
* *(client)* Added support for MSC3916 media download endpoint.
* Support is automatically detected from spec versions. The `SpecVersions`
property can either be filled manually, or `Versions` can be called to
automatically populate the field with the response.
* *(event)* Added constants for known room versions.
## v0.18.1 (2024-04-16)
* *(format)* Added a `context.Context` field to HTMLParser's Context struct.

View file

@ -5,7 +5,7 @@ A Golang Matrix framework. Used by [gomuks](https://matrix.org/docs/projects/cli
[go-neb](https://github.com/matrix-org/go-neb), [mautrix-whatsapp](https://github.com/mautrix/whatsapp)
and others.
Matrix room: [`#maunium:maunium.net`](https://matrix.to/#/#maunium:maunium.net)
Matrix room: [`#go:maunium.net`](https://matrix.to/#/#go:maunium.net)
This project is based on [matrix-org/gomatrix](https://github.com/matrix-org/gomatrix).
The original project is licensed under [Apache 2.0](https://github.com/matrix-org/gomatrix/blob/master/LICENSE).
@ -14,6 +14,9 @@ In addition to the basic client API features the original project has, this fram
* Appservice support (Intent API like mautrix-python, room state storage, etc)
* End-to-end encryption support (incl. interactive SAS verification)
* High-level module for building puppeting bridges
* High-level module for building chat clients
* Wrapper functions for the Synapse admin API
* Structs for parsing event content
* Helpers for parsing and generating Matrix HTML
* Helpers for handling push rules

View file

@ -61,11 +61,14 @@ type Client struct {
StateStore StateStore
Crypto CryptoHelper
Verification VerificationHelper
SpecVersions *RespVersions
Log zerolog.Logger
RequestHook func(req *http.Request)
ResponseHook func(req *http.Request, resp *http.Response, duration time.Duration)
ResponseHook func(req *http.Request, resp *http.Response, err error, duration time.Duration)
UpdateRequestOnRetry func(req *http.Request, cause error) *http.Request
SyncPresence event.Presence
@ -74,6 +77,8 @@ type Client struct {
// Number of times that mautrix will retry any HTTP request
// if the request fails entirely or returns a HTTP gateway error (502-504)
DefaultHTTPRetries int
// Amount of time to wait between HTTP retries, defaults to 4 seconds
DefaultHTTPBackoff time.Duration
// Set to true to disable automatically sleeping on 429 errors.
IgnoreRateLimit bool
@ -197,14 +202,20 @@ func (cli *Client) SyncWithContext(ctx context.Context) error {
}
}
lastSuccessfulSync := time.Now().Add(-cli.StreamSyncMinAge - 1*time.Hour)
// Always do first sync with 0 timeout
isFailing := true
for {
streamResp := false
if cli.StreamSyncMinAge > 0 && time.Since(lastSuccessfulSync) > cli.StreamSyncMinAge {
cli.Log.Debug().Msg("Last sync is old, will stream next response")
streamResp = true
}
timeout := 30000
if isFailing {
timeout = 0
}
resSync, err := cli.FullSyncRequest(ctx, ReqSync{
Timeout: 30000,
Timeout: timeout,
Since: nextBatch,
FilterID: filterID,
FullState: false,
@ -212,6 +223,7 @@ func (cli *Client) SyncWithContext(ctx context.Context) error {
StreamResponse: streamResp,
})
if err != nil {
isFailing = true
if ctx.Err() != nil {
return ctx.Err()
}
@ -219,6 +231,9 @@ func (cli *Client) SyncWithContext(ctx context.Context) error {
if err2 != nil {
return err2
}
if duration <= 0 {
continue
}
select {
case <-ctx.Done():
return ctx.Err()
@ -226,6 +241,7 @@ func (cli *Client) SyncWithContext(ctx context.Context) error {
continue
}
}
isFailing = false
lastSuccessfulSync = time.Now()
// Check that the syncing state hasn't changed
@ -291,10 +307,10 @@ func (cli *Client) LogRequestDone(req *http.Request, resp *http.Response, err er
Str("method", req.Method).
Str("url", req.URL.String()).
Dur("duration", duration)
if resp != nil {
if cli.ResponseHook != nil {
cli.ResponseHook(req, resp, duration)
cli.ResponseHook(req, resp, err, duration)
}
if resp != nil {
mime := resp.Header.Get("Content-Type")
length := resp.ContentLength
if length == -1 && contentLength > 0 {
@ -319,7 +335,7 @@ func (cli *Client) LogRequestDone(req *http.Request, resp *http.Response, err er
}
}
func (cli *Client) MakeRequest(ctx context.Context, method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
func (cli *Client) MakeRequest(ctx context.Context, method string, httpURL string, reqBody any, resBody any) ([]byte, error) {
return cli.MakeFullRequest(ctx, FullRequest{Method: method, URL: httpURL, RequestJSON: reqBody, ResponseJSON: resBody})
}
@ -335,8 +351,10 @@ type FullRequest struct {
RequestLength int64
ResponseJSON interface{}
MaxAttempts int
BackoffDuration time.Duration
SensitiveContent bool
Handler ClientResponseHandler
DontReadResponse bool
Logger *zerolog.Logger
Client *http.Client
}
@ -401,26 +419,36 @@ func (params *FullRequest) compileRequest(ctx context.Context) (*http.Request, e
return req, nil
}
// MakeFullRequest makes a JSON HTTP request to the given URL.
// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
//
// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along
// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned
// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError.
func (cli *Client) MakeFullRequest(ctx context.Context, params FullRequest) ([]byte, error) {
data, _, err := cli.MakeFullRequestWithResp(ctx, params)
return data, err
}
func (cli *Client) MakeFullRequestWithResp(ctx context.Context, params FullRequest) ([]byte, *http.Response, error) {
if params.MaxAttempts == 0 {
params.MaxAttempts = 1 + cli.DefaultHTTPRetries
}
if params.BackoffDuration == 0 {
if cli.DefaultHTTPBackoff == 0 {
params.BackoffDuration = 4 * time.Second
} else {
params.BackoffDuration = cli.DefaultHTTPBackoff
}
}
if params.Logger == nil {
params.Logger = &cli.Log
}
req, err := params.compileRequest(ctx)
if err != nil {
return nil, err
return nil, nil, err
}
if params.Handler == nil {
if params.DontReadResponse {
params.Handler = noopHandleResponse
} else {
params.Handler = handleNormalResponse
}
}
req.Header.Set("User-Agent", cli.UserAgent)
if len(cli.AccessToken) > 0 {
req.Header.Set("Authorization", "Bearer "+cli.AccessToken)
@ -428,7 +456,7 @@ func (cli *Client) MakeFullRequest(ctx context.Context, params FullRequest) ([]b
if params.Client == nil {
params.Client = cli.Client
}
return cli.executeCompiledRequest(req, params.MaxAttempts-1, 4*time.Second, params.ResponseJSON, params.Handler, params.Client)
return cli.executeCompiledRequest(req, params.MaxAttempts-1, params.BackoffDuration, params.ResponseJSON, params.Handler, params.DontReadResponse, params.Client)
}
func (cli *Client) cliOrContextLog(ctx context.Context) *zerolog.Logger {
@ -439,28 +467,38 @@ func (cli *Client) cliOrContextLog(ctx context.Context) *zerolog.Logger {
return log
}
func (cli *Client) doRetry(req *http.Request, cause error, retries int, backoff time.Duration, responseJSON interface{}, handler ClientResponseHandler, client *http.Client) ([]byte, error) {
func (cli *Client) doRetry(req *http.Request, cause error, retries int, backoff time.Duration, responseJSON any, handler ClientResponseHandler, dontReadResponse bool, client *http.Client) ([]byte, *http.Response, error) {
log := zerolog.Ctx(req.Context())
if req.Body != nil {
if req.GetBody == nil {
log.Warn().Msg("Failed to get new body to retry request: GetBody is nil")
return nil, cause
}
var err error
if req.GetBody != nil {
req.Body, err = req.GetBody()
if err != nil {
log.Warn().Err(err).Msg("Failed to get new body to retry request")
return nil, cause
return nil, nil, cause
}
} else if bodySeeker, ok := req.Body.(io.ReadSeeker); ok {
_, err = bodySeeker.Seek(0, io.SeekStart)
if err != nil {
log.Warn().Err(err).Msg("Failed to seek to beginning of request body")
return nil, nil, cause
}
} else {
log.Warn().Msg("Failed to get new body to retry request: GetBody is nil and Body is not an io.ReadSeeker")
return nil, nil, cause
}
}
log.Warn().Err(cause).
Int("retry_in_seconds", int(backoff.Seconds())).
Msg("Request failed, retrying")
time.Sleep(backoff)
return cli.executeCompiledRequest(req, retries-1, backoff*2, responseJSON, handler, client)
if cli.UpdateRequestOnRetry != nil {
req = cli.UpdateRequestOnRetry(req, cause)
}
return cli.executeCompiledRequest(req, retries-1, backoff*2, responseJSON, handler, dontReadResponse, client)
}
func readRequestBody(req *http.Request, res *http.Response) ([]byte, error) {
func readResponseBody(req *http.Request, res *http.Response) ([]byte, error) {
contents, err := io.ReadAll(res.Body)
if err != nil {
return nil, HTTPError{
@ -502,8 +540,12 @@ func streamResponse(req *http.Request, res *http.Response, responseJSON interfac
}
}
func noopHandleResponse(req *http.Request, res *http.Response, responseJSON interface{}) ([]byte, error) {
return nil, nil
}
func handleNormalResponse(req *http.Request, res *http.Response, responseJSON interface{}) ([]byte, error) {
if contents, err := readRequestBody(req, res); err != nil {
if contents, err := readResponseBody(req, res); err != nil {
return nil, err
} else if responseJSON == nil {
return contents, nil
@ -522,7 +564,7 @@ func handleNormalResponse(req *http.Request, res *http.Response, responseJSON in
}
func ParseErrorResponse(req *http.Request, res *http.Response) ([]byte, error) {
contents, err := readRequestBody(req, res)
contents, err := readResponseBody(req, res)
if err != nil {
return contents, err
}
@ -539,17 +581,17 @@ func ParseErrorResponse(req *http.Request, res *http.Response) ([]byte, error) {
}
}
func (cli *Client) executeCompiledRequest(req *http.Request, retries int, backoff time.Duration, responseJSON interface{}, handler ClientResponseHandler, client *http.Client) ([]byte, error) {
func (cli *Client) executeCompiledRequest(req *http.Request, retries int, backoff time.Duration, responseJSON any, handler ClientResponseHandler, dontReadResponse bool, client *http.Client) ([]byte, *http.Response, error) {
cli.RequestStart(req)
startTime := time.Now()
res, err := client.Do(req)
duration := time.Now().Sub(startTime)
if res != nil {
if res != nil && !dontReadResponse {
defer res.Body.Close()
}
if err != nil {
if retries > 0 {
return cli.doRetry(req, err, retries, backoff, responseJSON, handler, client)
return cli.doRetry(req, err, retries, backoff, responseJSON, handler, dontReadResponse, client)
}
err = HTTPError{
Request: req,
@ -559,12 +601,12 @@ func (cli *Client) executeCompiledRequest(req *http.Request, retries int, backof
WrappedError: err,
}
cli.LogRequestDone(req, res, err, nil, 0, duration)
return nil, err
return nil, res, err
}
if retries > 0 && retryafter.Should(res.StatusCode, !cli.IgnoreRateLimit) {
backoff = retryafter.Parse(res.Header.Get("Retry-After"), backoff)
return cli.doRetry(req, fmt.Errorf("HTTP %d", res.StatusCode), retries, backoff, responseJSON, handler, client)
return cli.doRetry(req, fmt.Errorf("HTTP %d", res.StatusCode), retries, backoff, responseJSON, handler, dontReadResponse, client)
}
var body []byte
@ -575,7 +617,7 @@ func (cli *Client) executeCompiledRequest(req *http.Request, retries int, backof
body, err = handler(req, res, responseJSON)
cli.LogRequestDone(req, res, nil, err, len(body), duration)
}
return body, err
return body, res, err
}
// Whoami gets the user ID of the current user. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3accountwhoami
@ -611,6 +653,8 @@ type ReqSync struct {
FullState bool
SetPresence event.Presence
StreamResponse bool
BeeperStreaming bool
Client *http.Client
}
func (req *ReqSync) BuildQuery() map[string]string {
@ -629,6 +673,11 @@ func (req *ReqSync) BuildQuery() map[string]string {
if req.FullState {
query["full_state"] = "true"
}
if req.BeeperStreaming {
// TODO remove this
query["streaming"] = ""
query["com.beeper.streaming"] = "true"
}
return query
}
@ -639,6 +688,7 @@ func (cli *Client) FullSyncRequest(ctx context.Context, req ReqSync) (resp *Resp
Method: http.MethodGet,
URL: urlPath,
ResponseJSON: &resp,
Client: req.Client,
// We don't want automatic retries for SyncRequest, the Sync() wrapper handles those.
MaxAttempts: 1,
}
@ -830,6 +880,9 @@ func (cli *Client) LogoutAll(ctx context.Context) (resp *RespLogout, err error)
func (cli *Client) Versions(ctx context.Context) (resp *RespVersions, err error) {
urlPath := cli.BuildClientURL("versions")
_, err = cli.MakeRequest(ctx, http.MethodGet, urlPath, nil, &resp)
if resp != nil {
cli.SpecVersions = resp
}
return
}
@ -941,9 +994,9 @@ func (cli *Client) SetAvatarURL(ctx context.Context, url id.ContentURI) (err err
}
// BeeperUpdateProfile sets custom fields in the user's profile.
func (cli *Client) BeeperUpdateProfile(ctx context.Context, data map[string]any) (err error) {
func (cli *Client) BeeperUpdateProfile(ctx context.Context, data any) (err error) {
urlPath := cli.BuildClientURL("v3", "profile", cli.UserID)
_, err = cli.MakeRequest(ctx, http.MethodPatch, urlPath, &data, nil)
_, err = cli.MakeRequest(ctx, http.MethodPatch, urlPath, data, nil)
return
}
@ -1358,9 +1411,11 @@ func (cli *Client) State(ctx context.Context, roomID id.RoomID) (stateMap RoomSt
})
if err == nil && cli.StateStore != nil {
clearErr := cli.StateStore.ClearCachedMembers(ctx, roomID)
if clearErr != nil {
cli.cliOrContextLog(ctx).Warn().Err(clearErr).
Stringer("room_id", roomID).
Msg("Failed to clear cached member list after fetching state")
}
for _, evts := range stateMap {
for _, evt := range evts {
UpdateStateStore(ctx, cli.StateStore, evt)
@ -1372,7 +1427,12 @@ func (cli *Client) State(ctx context.Context, roomID id.RoomID) (stateMap RoomSt
// GetMediaConfig fetches the configuration of the content repository, such as upload limitations.
func (cli *Client) GetMediaConfig(ctx context.Context) (resp *RespMediaConfig, err error) {
u := cli.BuildURL(MediaURLPath{"v3", "config"})
var u string
if cli.SpecVersions.ContainsGreaterOrEqual(SpecV111) {
u = cli.BuildClientURL("v1", "media", "config")
} else {
u = cli.BuildURL(MediaURLPath{"v3", "config"})
}
_, err = cli.MakeRequest(ctx, http.MethodGet, u, nil, &resp)
return
}
@ -1394,31 +1454,31 @@ func (cli *Client) UploadLink(ctx context.Context, link string) (*RespMediaUploa
return cli.Upload(ctx, res.Body, res.Header.Get("Content-Type"), res.ContentLength)
}
// Deprecated: unauthenticated media is deprecated as of Matrix v1.11. Use [Download] or [DownloadBytes] instead.
func (cli *Client) GetDownloadURL(mxcURL id.ContentURI) string {
return cli.BuildURLWithQuery(MediaURLPath{"v3", "download", mxcURL.Homeserver, mxcURL.FileID}, map[string]string{"allow_redirect": "true"})
}
func (cli *Client) Download(ctx context.Context, mxcURL id.ContentURI) (io.ReadCloser, error) {
resp, err := cli.download(ctx, mxcURL)
if err != nil {
return nil, err
}
return resp.Body, nil
}
func (cli *Client) doMediaRetry(req *http.Request, cause error, retries int, backoff time.Duration) (*http.Response, error) {
log := zerolog.Ctx(req.Context())
if req.Body != nil {
if req.GetBody == nil {
log.Warn().Msg("Failed to get new body to retry request: GetBody is nil")
return nil, cause
}
var err error
if req.GetBody != nil {
req.Body, err = req.GetBody()
if err != nil {
log.Warn().Err(err).Msg("Failed to get new body to retry request")
return nil, cause
}
} else if bodySeeker, ok := req.Body.(io.ReadSeeker); ok {
_, err = bodySeeker.Seek(0, io.SeekStart)
if err != nil {
log.Warn().Err(err).Msg("Failed to seek to beginning of request body")
return nil, cause
}
} else {
log.Warn().Msg("Failed to get new body to retry request: GetBody is nil and Body is not an io.ReadSeeker")
return nil, cause
}
}
log.Warn().Err(cause).
Int("retry_in_seconds", int(backoff.Seconds())).
@ -1462,21 +1522,30 @@ func (cli *Client) doMediaRequest(req *http.Request, retries int, backoff time.D
return res, err
}
func (cli *Client) download(ctx context.Context, mxcURL id.ContentURI) (*http.Response, error) {
func (cli *Client) Download(ctx context.Context, mxcURL id.ContentURI) (*http.Response, error) {
ctxLog := zerolog.Ctx(ctx)
if ctxLog.GetLevel() == zerolog.Disabled || ctxLog == zerolog.DefaultContextLogger {
ctx = cli.Log.WithContext(ctx)
}
if cli.SpecVersions.ContainsGreaterOrEqual(SpecV111) {
_, resp, err := cli.MakeFullRequestWithResp(ctx, FullRequest{
Method: http.MethodGet,
URL: cli.BuildClientURL("v1", "media", "download", mxcURL.Homeserver, mxcURL.FileID),
DontReadResponse: true,
})
return resp, err
} else {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, cli.GetDownloadURL(mxcURL), nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", cli.UserAgent+" (media downloader)")
return cli.doMediaRequest(req, cli.DefaultHTTPRetries, 4*time.Second)
}
}
func (cli *Client) DownloadBytes(ctx context.Context, mxcURL id.ContentURI) ([]byte, error) {
resp, err := cli.download(ctx, mxcURL)
resp, err := cli.Download(ctx, mxcURL)
if err != nil {
return nil, err
}
@ -1488,13 +1557,8 @@ func (cli *Client) DownloadBytes(ctx context.Context, mxcURL id.ContentURI) ([]b
//
// See https://spec.matrix.org/v1.7/client-server-api/#post_matrixmediav1create
func (cli *Client) CreateMXC(ctx context.Context) (*RespCreateMXC, error) {
u, _ := url.Parse(cli.BuildURL(MediaURLPath{"v1", "create"}))
var m RespCreateMXC
_, err := cli.MakeFullRequest(ctx, FullRequest{
Method: http.MethodPost,
URL: u.String(),
ResponseJSON: &m,
})
_, err := cli.MakeRequest(ctx, http.MethodPost, cli.BuildURL(MediaURLPath{"v1", "create"}), nil, &m)
return &m, err
}
@ -1558,12 +1622,13 @@ type ReqUploadMedia struct {
UnstableUploadURL string
}
func (cli *Client) tryUploadMediaToURL(ctx context.Context, url, contentType string, content io.Reader) (*http.Response, error) {
func (cli *Client) tryUploadMediaToURL(ctx context.Context, url, contentType string, content io.Reader, contentLength int64) (*http.Response, error) {
cli.Log.Debug().Str("url", url).Msg("Uploading media to external URL")
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, content)
if err != nil {
return nil, err
}
req.ContentLength = contentLength
req.Header.Set("Content-Type", contentType)
req.Header.Set("User-Agent", cli.UserAgent+" (external media uploader)")
@ -1572,18 +1637,17 @@ func (cli *Client) tryUploadMediaToURL(ctx context.Context, url, contentType str
func (cli *Client) uploadMediaToURL(ctx context.Context, data ReqUploadMedia) (*RespMediaUpload, error) {
retries := cli.DefaultHTTPRetries
if data.ContentBytes == nil {
// Can't retry with a reader
reader := data.Content
if data.ContentBytes != nil {
data.ContentLength = int64(len(data.ContentBytes))
reader = bytes.NewReader(data.ContentBytes)
}
readerSeeker, canSeek := reader.(io.ReadSeeker)
if !canSeek {
retries = 0
}
for {
reader := data.Content
if reader == nil {
reader = bytes.NewReader(data.ContentBytes)
} else {
data.Content = nil
}
resp, err := cli.tryUploadMediaToURL(ctx, data.UnstableUploadURL, data.ContentType, reader)
resp, err := cli.tryUploadMediaToURL(ctx, data.UnstableUploadURL, data.ContentType, reader, data.ContentLength)
if err == nil {
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
// Everything is fine
@ -1599,6 +1663,10 @@ func (cli *Client) uploadMediaToURL(ctx context.Context, data ReqUploadMedia) (*
cli.Log.Warn().Str("url", data.UnstableUploadURL).Err(err).
Msg("Error uploading media to external URL, retrying")
retries--
_, err = readerSeeker.Seek(0, io.SeekStart)
if err != nil {
return nil, fmt.Errorf("failed to seek back to start of reader: %w", err)
}
}
query := map[string]string{}
@ -1609,11 +1677,7 @@ func (cli *Client) uploadMediaToURL(ctx context.Context, data ReqUploadMedia) (*
notifyURL := cli.BuildURLWithQuery(MediaURLPath{"unstable", "com.beeper.msc3870", "upload", data.MXC.Homeserver, data.MXC.FileID, "complete"}, query)
var m *RespMediaUpload
_, err := cli.MakeFullRequest(ctx, FullRequest{
Method: http.MethodPost,
URL: notifyURL,
ResponseJSON: m,
})
_, err := cli.MakeRequest(ctx, http.MethodPost, notifyURL, nil, &m)
if err != nil {
return nil, err
}
@ -1664,7 +1728,13 @@ func (cli *Client) UploadMedia(ctx context.Context, data ReqUploadMedia) (*RespM
//
// See https://spec.matrix.org/v1.2/client-server-api/#get_matrixmediav3preview_url
func (cli *Client) GetURLPreview(ctx context.Context, url string) (*RespPreviewURL, error) {
reqURL := cli.BuildURLWithQuery(MediaURLPath{"v3", "preview_url"}, map[string]string{
var urlPath PrefixableURLPath
if cli.SpecVersions.ContainsGreaterOrEqual(SpecV111) {
urlPath = ClientURLPath{"v1", "media", "preview_url"}
} else {
urlPath = MediaURLPath{"v3", "preview_url"}
}
reqURL := cli.BuildURLWithQuery(urlPath, map[string]string{
"url": url,
})
var output RespPreviewURL
@ -1720,6 +1790,11 @@ func (cli *Client) Members(ctx context.Context, roomID id.RoomID, req ...ReqMemb
}
u := cli.BuildURLWithQuery(ClientURLPath{"v3", "rooms", roomID, "members"}, query)
_, err = cli.MakeRequest(ctx, http.MethodGet, u, nil, &resp)
if err == nil {
for _, evt := range resp.Chunk {
_ = evt.Content.ParseRaw(evt.Type)
}
}
if err == nil && cli.StateStore != nil {
var clearMemberships []event.Membership
if extra.Membership != "" {
@ -1857,15 +1932,13 @@ func (cli *Client) SetReadMarkers(ctx context.Context, roomID id.RoomID, content
return
}
func (cli *Client) AddTag(ctx context.Context, roomID id.RoomID, tag string, order float64) error {
var tagData event.Tag
if order == order {
tagData.Order = json.Number(strconv.FormatFloat(order, 'e', -1, 64))
}
return cli.AddTagWithCustomData(ctx, roomID, tag, tagData)
func (cli *Client) AddTag(ctx context.Context, roomID id.RoomID, tag event.RoomTag, order float64) error {
return cli.AddTagWithCustomData(ctx, roomID, tag, &event.TagMetadata{
Order: json.Number(strconv.FormatFloat(order, 'e', -1, 64)),
})
}
func (cli *Client) AddTagWithCustomData(ctx context.Context, roomID id.RoomID, tag string, data interface{}) (err error) {
func (cli *Client) AddTagWithCustomData(ctx context.Context, roomID id.RoomID, tag event.RoomTag, data any) (err error) {
urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags", tag)
_, err = cli.MakeRequest(ctx, http.MethodPut, urlPath, data, nil)
return
@ -1876,13 +1949,13 @@ func (cli *Client) GetTags(ctx context.Context, roomID id.RoomID) (tags event.Ta
return
}
func (cli *Client) GetTagsWithCustomData(ctx context.Context, roomID id.RoomID, resp interface{}) (err error) {
func (cli *Client) GetTagsWithCustomData(ctx context.Context, roomID id.RoomID, resp any) (err error) {
urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags")
_, err = cli.MakeRequest(ctx, http.MethodGet, urlPath, nil, &resp)
return
}
func (cli *Client) RemoveTag(ctx context.Context, roomID id.RoomID, tag string) (err error) {
func (cli *Client) RemoveTag(ctx context.Context, roomID id.RoomID, tag event.RoomTag) (err error) {
urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags", tag)
_, err = cli.MakeRequest(ctx, http.MethodDelete, urlPath, nil, nil)
return

View file

@ -81,6 +81,5 @@ func (account *OlmAccount) getOneTimeKeys(userID id.UserID, deviceID id.DeviceID
key.IsSigned = true
oneTimeKeys[id.NewKeyID(id.KeyAlgorithmSignedCurve25519, keyID)] = key
}
account.Internal.MarkKeysAsPublished()
return oneTimeKeys
}

View file

@ -12,6 +12,7 @@ import (
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"hash"
"io"
@ -136,6 +137,27 @@ type encryptingReader struct {
isDecrypting bool
}
func (r *encryptingReader) Seek(offset int64, whence int) (int64, error) {
if r.closed {
return 0, ReaderClosed
}
if offset != 0 || whence != io.SeekStart {
return 0, fmt.Errorf("attachments.EncryptStream: only seeking to the beginning is supported")
}
seeker, ok := r.source.(io.ReadSeeker)
if !ok {
return 0, fmt.Errorf("attachments.EncryptStream: source reader (%T) is not an io.ReadSeeker", r.source)
}
n, err := seeker.Seek(offset, whence)
if err != nil {
return 0, err
}
block, _ := aes.NewCipher(r.file.decoded.key[:])
r.stream = cipher.NewCTR(block, r.file.decoded.iv[:])
r.hash.Reset()
return n, nil
}
func (r *encryptingReader) Read(dst []byte) (n int, err error) {
if r.closed {
return 0, ReaderClosed

View file

@ -142,6 +142,16 @@ func (mach *OlmMachine) PublishCrossSigningKeys(ctx context.Context, keys *Cross
return err
}
if err := mach.CryptoStore.PutSignature(ctx, userID, keys.MasterKey.PublicKey(), userID, mach.account.SigningKey(), masterSig); err != nil {
return fmt.Errorf("error storing signature of master key by device signing key in crypto store: %w", err)
}
if err := mach.CryptoStore.PutSignature(ctx, userID, keys.SelfSigningKey.PublicKey(), userID, keys.MasterKey.PublicKey(), selfSig); err != nil {
return fmt.Errorf("error storing signature of self-signing key by master key in crypto store: %w", err)
}
if err := mach.CryptoStore.PutSignature(ctx, userID, keys.UserSigningKey.PublicKey(), userID, keys.MasterKey.PublicKey(), userSig); err != nil {
return fmt.Errorf("error storing signature of user-signing key by master key in crypto store: %w", err)
}
mach.CrossSigningKeys = keys
mach.crossSigningPubkeys = keys.PublicKeys()

View file

@ -19,7 +19,7 @@ import (
)
var (
ErrCrossSigningKeysNotCached = errors.New("cross-signing private keys not in cache")
ErrCrossSigningPubkeysNotCached = errors.New("cross-signing public keys not in cache")
ErrUserSigningKeyNotCached = errors.New("user-signing private key not in cache")
ErrSelfSigningKeyNotCached = errors.New("self-signing private key not in cache")
ErrSignatureUploadFail = errors.New("server-side failure uploading signatures")
@ -69,15 +69,16 @@ func (mach *OlmMachine) SignUser(ctx context.Context, userID id.UserID, masterKe
// SignOwnMasterKey uses the current account for signing the current user's master key and uploads the signature.
func (mach *OlmMachine) SignOwnMasterKey(ctx context.Context) error {
if mach.CrossSigningKeys == nil {
return ErrCrossSigningKeysNotCached
crossSigningPubkeys := mach.GetOwnCrossSigningPublicKeys(ctx)
if crossSigningPubkeys == nil {
return ErrCrossSigningPubkeysNotCached
} else if mach.account == nil {
return ErrOlmAccountNotLoaded
}
userID := mach.Client.UserID
deviceID := mach.Client.DeviceID
masterKey := mach.CrossSigningKeys.MasterKey.PublicKey()
masterKey := crossSigningPubkeys.MasterKey
masterKeyObj := mautrix.ReqKeysSignatures{
UserID: userID,

View file

@ -10,6 +10,8 @@ package crypto
import (
"context"
"go.mau.fi/util/exzerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/signatures"
"maunium.net/go/mautrix/id"
@ -47,7 +49,7 @@ func (mach *OlmMachine) storeCrossSigningKeys(ctx context.Context, crossSigningK
}
for _, key := range userKeys.Keys {
log := log.With().Str("key", key.String()).Strs("usages", strishArray(userKeys.Usage)).Logger()
log := log.With().Str("key", key.String()).Array("usages", exzerolog.ArrayOfStrs(userKeys.Usage)).Logger()
for _, usage := range userKeys.Usage {
log.Debug().Str("usage", string(usage)).Msg("Storing cross-signing key")
if err = mach.CryptoStore.PutCrossSigningKey(ctx, userID, usage, key); err != nil {

View file

@ -14,6 +14,9 @@ import (
"strings"
"github.com/rs/zerolog"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"go.mau.fi/util/exgjson"
"maunium.net/go/mautrix/crypto/olm"
"maunium.net/go/mautrix/event"
@ -36,6 +39,11 @@ type megolmEvent struct {
Content event.Content `json:"content"`
}
var (
relatesToContentPath = exgjson.Path("m.relates_to")
relatesToTopLevelPath = exgjson.Path("content", "m.relates_to")
)
// DecryptMegolmEvent decrypts an m.room.encrypted event where the algorithm is m.megolm.v1.aes-sha2
func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event) (*event.Event, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
@ -90,14 +98,20 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
} else if device.SigningKey != sess.SigningKey || device.IdentityKey != sess.SenderKey {
return nil, DeviceKeyMismatch
} else {
trustLevel = mach.ResolveTrust(device)
trustLevel, err = mach.ResolveTrustContext(ctx, device)
if err != nil {
return nil, err
}
}
} else {
forwardedKeys = true
lastChainItem := sess.ForwardingChains[len(sess.ForwardingChains)-1]
device, _ = mach.CryptoStore.FindDeviceByKey(ctx, evt.Sender, id.IdentityKey(lastChainItem))
if device != nil {
trustLevel = mach.ResolveTrust(device)
trustLevel, err = mach.ResolveTrustContext(ctx, device)
if err != nil {
return nil, err
}
} else {
log.Debug().
Str("forward_last_sender_key", lastChainItem).
@ -107,6 +121,26 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
}
}
if content.RelatesTo != nil {
relation := gjson.GetBytes(evt.Content.VeryRaw, relatesToContentPath)
if relation.Exists() && !gjson.GetBytes(plaintext, relatesToTopLevelPath).IsObject() {
var raw []byte
if relation.Index > 0 {
raw = evt.Content.VeryRaw[relation.Index : relation.Index+len(relation.Raw)]
} else {
raw = []byte(relation.Raw)
}
updatedPlaintext, err := sjson.SetRawBytes(plaintext, relatesToTopLevelPath, raw)
if err != nil {
log.Warn().Msg("Failed to copy m.relates_to to decrypted payload")
} else if updatedPlaintext != nil {
plaintext = updatedPlaintext
}
} else if !relation.Exists() {
log.Warn().Msg("Failed to find m.relates_to in raw encrypted event even though it was present in parsed content")
}
}
megolmEvt := &megolmEvent{}
err = json.Unmarshal(plaintext, &megolmEvt)
if err != nil {
@ -124,19 +158,6 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
return nil, fmt.Errorf("failed to parse content of megolm payload event: %w", err)
}
}
if content.RelatesTo != nil {
relatable, ok := megolmEvt.Content.Parsed.(event.Relatable)
if ok {
if relatable.OptionalGetRelatesTo() == nil {
relatable.SetRelatesTo(content.RelatesTo)
} else {
log.Trace().Msg("Not overriding relation data as encrypted payload already has it")
}
}
if _, hasRelation := megolmEvt.Content.Raw["m.relates_to"]; !hasRelation {
megolmEvt.Content.Raw["m.relates_to"] = evt.Content.Raw["m.relates_to"]
}
}
log.Debug().Msg("Event decrypted successfully")
megolmEvt.Type.Class = evt.Type.Class
return &event.Event{
@ -170,7 +191,7 @@ const missedIndexCutoff = 10
func (mach *OlmMachine) checkUndecryptableMessageIndexDuplication(ctx context.Context, sess *InboundGroupSession, evt *event.Event, content *event.EncryptedEventContent) (uint, error) {
log := *zerolog.Ctx(ctx)
messageIndex, decodeErr := parseMessageIndex(content.MegolmCiphertext)
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)
@ -192,7 +213,7 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
mach.megolmDecryptLock.Lock()
defer mach.megolmDecryptLock.Unlock()
sess, err := mach.CryptoStore.GetGroupSession(ctx, encryptionRoomID, content.SenderKey, content.SessionID)
sess, err := mach.CryptoStore.GetGroupSession(ctx, encryptionRoomID, content.SessionID)
if err != nil {
return nil, nil, 0, fmt.Errorf("failed to get group session: %w", err)
} else if sess == nil {
@ -213,6 +234,11 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
return sess, nil, messageIndex, fmt.Errorf("%w %d", DuplicateMessageIndex, messageIndex)
}
// Normal clients don't care about tracking the ratchet state, so let them bypass the rest of the function
if mach.DisableRatchetTracking {
return sess, plaintext, messageIndex, nil
}
expectedMessageIndex := sess.RatchetSafety.NextIndex
didModify := false
switch {
@ -254,7 +280,7 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
Int("max_messages", sess.MaxMessages).
Logger()
if sess.MaxMessages > 0 && int(ratchetTargetIndex) >= sess.MaxMessages && len(sess.RatchetSafety.MissedIndices) == 0 && mach.DeleteFullyUsedKeysOnDecrypt {
err = mach.CryptoStore.RedactGroupSession(ctx, sess.RoomID, sess.SenderKey, sess.ID(), "maximum messages reached")
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
@ -265,14 +291,14 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
if err = sess.RatchetTo(ratchetTargetIndex); err != nil {
log.Err(err).Msg("Failed to ratchet session")
return sess, plaintext, messageIndex, RatchetError
} else if err = mach.CryptoStore.PutGroupSession(ctx, sess.RoomID, sess.SenderKey, sess.ID(), sess); err != nil {
} else if err = mach.CryptoStore.PutGroupSession(ctx, sess); err != nil {
log.Err(err).Msg("Failed to store ratcheted session")
return sess, plaintext, messageIndex, RatchetError
} else {
log.Info().Msg("Ratcheted session forward")
}
} else if didModify {
if err = mach.CryptoStore.PutGroupSession(ctx, sess.RoomID, sess.SenderKey, sess.ID(), sess); err != nil {
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
} else {

View file

@ -12,6 +12,7 @@ import (
"fmt"
"github.com/rs/zerolog"
"go.mau.fi/util/exzerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/signatures"
@ -93,6 +94,10 @@ func (mach *OlmMachine) storeDeviceSelfSignatures(ctx context.Context, userID id
}
}
// FetchKeys fetches the devices of a list of other users. If includeUntracked
// is set to false, then the users are filtered to to only include user IDs
// whose device lists have been stored with the PutDevices function on the
// [Store]. See the FilterTrackedUsers function on [Store] for details.
func (mach *OlmMachine) FetchKeys(ctx context.Context, users []id.UserID, includeUntracked bool) (data map[id.UserID]map[id.DeviceID]*id.Device, err error) {
req := &mautrix.ReqQueryKeys{
DeviceKeys: mautrix.DeviceKeysRequest{},
@ -111,7 +116,7 @@ func (mach *OlmMachine) FetchKeys(ctx context.Context, users []id.UserID, includ
for _, userID := range users {
req.DeviceKeys[userID] = mautrix.DeviceIDList{}
}
log.Debug().Strs("users", strishArray(users)).Msg("Querying keys for users")
log.Debug().Array("users", exzerolog.ArrayOfStrs(users)).Msg("Querying keys for users")
resp, err := mach.Client.QueryKeys(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to query keys: %w", err)
@ -177,7 +182,7 @@ func (mach *OlmMachine) FetchKeys(ctx context.Context, users []id.UserID, includ
log.Err(err).Msg("Failed to redact megolm sessions from deleted device")
} else {
log.Info().
Strs("session_ids", stringifyArray(sessionIDs)).
Array("session_ids", exzerolog.ArrayOfStrs(sessionIDs)).
Msg("Redacted megolm sessions from deleted device")
}
}

View file

@ -15,6 +15,7 @@ import (
"fmt"
"github.com/rs/zerolog"
"go.mau.fi/util/exzerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
@ -61,7 +62,7 @@ func IsShareError(err error) bool {
return err == SessionExpired || err == SessionNotShared || err == NoGroupSession
}
func parseMessageIndex(ciphertext []byte) (uint, error) {
func ParseMegolmMessageIndex(ciphertext []byte) (uint, error) {
decoded := make([]byte, base64.RawStdEncoding.DecodedLen(len(ciphertext)))
var err error
_, err = base64.RawStdEncoding.Decode(decoded, ciphertext)
@ -109,7 +110,7 @@ func (mach *OlmMachine) EncryptMegolmEvent(ctx context.Context, roomID id.RoomID
if err != nil {
return nil, err
}
idx, err := parseMessageIndex(ciphertext)
idx, err := ParseMegolmMessageIndex(ciphertext)
if err != nil {
log.Warn().Err(err).Msg("Failed to get megolm message index of encrypted event")
} else {
@ -136,19 +137,23 @@ func (mach *OlmMachine) EncryptMegolmEvent(ctx context.Context, roomID id.RoomID
return encrypted, nil
}
func (mach *OlmMachine) newOutboundGroupSession(ctx context.Context, roomID id.RoomID) *OutboundGroupSession {
func (mach *OlmMachine) newOutboundGroupSession(ctx context.Context, roomID id.RoomID) (*OutboundGroupSession, error) {
encryptionEvent, err := mach.StateStore.GetEncryptionEvent(ctx, roomID)
if err != nil {
mach.machOrContextLog(ctx).Err(err).
Stringer("room_id", roomID).
Msg("Failed to get encryption event in room")
return nil, fmt.Errorf("failed to get encryption event in room %s: %w", roomID, err)
}
session := NewOutboundGroupSession(roomID, encryptionEvent)
if !mach.DontStoreOutboundKeys {
signingKey, idKey := mach.account.Keys()
mach.createGroupSession(ctx, idKey, signingKey, roomID, session.ID(), session.Internal.Key(), session.MaxAge, session.MaxMessages, false)
err := mach.createGroupSession(ctx, idKey, signingKey, roomID, session.ID(), session.Internal.Key(), session.MaxAge, session.MaxMessages, false)
if err != nil {
return nil, err
}
return session
}
return session, err
}
type deviceSessionWrapper struct {
@ -156,14 +161,6 @@ type deviceSessionWrapper struct {
identity *id.Device
}
func strishArray[T ~string](arr []T) []string {
out := make([]string, len(arr))
for i, item := range arr {
out[i] = string(item)
}
return out
}
// ShareGroupSession shares a group session for a specific room with all the devices of the given user list.
//
// For devices with TrustStateBlacklisted, a m.room_key.withheld event with code=m.blacklisted is sent.
@ -183,27 +180,30 @@ func (mach *OlmMachine) ShareGroupSession(ctx context.Context, roomID id.RoomID,
Logger()
ctx = log.WithContext(ctx)
if session == nil || session.Expired() {
session = mach.newOutboundGroupSession(ctx, roomID)
if session, err = mach.newOutboundGroupSession(ctx, roomID); err != nil {
return err
}
}
log = log.With().Str("session_id", session.ID().String()).Logger()
ctx = log.WithContext(ctx)
log.Debug().Strs("users", strishArray(users)).Msg("Sharing group session for room")
log.Debug().Array("users", exzerolog.ArrayOfStrs(users)).Msg("Sharing group session for room")
withheldCount := 0
toDeviceWithheld := &mautrix.ReqSendToDevice{Messages: make(map[id.UserID]map[id.DeviceID]*event.Content)}
olmSessions := make(map[id.UserID]map[id.DeviceID]deviceSessionWrapper)
missingSessions := make(map[id.UserID]map[id.DeviceID]*id.Device)
missingUserSessions := make(map[id.DeviceID]*id.Device)
var fetchKeys []id.UserID
var fetchKeysForUsers []id.UserID
for _, userID := range users {
log := log.With().Str("target_user_id", userID.String()).Logger()
devices, err := mach.CryptoStore.GetDevices(ctx, userID)
if err != nil {
log.Error().Err(err).Msg("Failed to get devices of user")
log.Err(err).Msg("Failed to get devices of user")
return fmt.Errorf("failed to get devices of user %s: %w", userID, err)
} else if devices == nil {
log.Debug().Msg("GetDevices returned nil, will fetch keys and retry")
fetchKeys = append(fetchKeys, userID)
fetchKeysForUsers = append(fetchKeysForUsers, userID)
} else if len(devices) == 0 {
log.Trace().Msg("User has no devices, skipping")
} else {
@ -227,11 +227,13 @@ func (mach *OlmMachine) ShareGroupSession(ctx context.Context, roomID id.RoomID,
}
}
if len(fetchKeys) > 0 {
log.Debug().Strs("users", strishArray(fetchKeys)).Msg("Fetching missing keys")
if keys, err := mach.FetchKeys(ctx, fetchKeys, true); err != nil {
log.Err(err).Strs("users", strishArray(fetchKeys)).Msg("Failed to fetch missing keys")
} else if keys != nil {
if len(fetchKeysForUsers) > 0 {
log.Debug().Array("users", exzerolog.ArrayOfStrs(fetchKeysForUsers)).Msg("Fetching missing keys")
keys, err := mach.FetchKeys(ctx, fetchKeysForUsers, true)
if err != nil {
log.Err(err).Array("users", exzerolog.ArrayOfStrs(fetchKeysForUsers)).Msg("Failed to fetch missing keys")
return fmt.Errorf("failed to fetch missing keys: %w", err)
}
for userID, devices := range keys {
log.Debug().
Int("device_count", len(devices)).
@ -240,13 +242,13 @@ func (mach *OlmMachine) ShareGroupSession(ctx context.Context, roomID id.RoomID,
missingSessions[userID] = devices
}
}
}
if len(missingSessions) > 0 {
log.Debug().Msg("Creating missing olm sessions")
err = mach.createOutboundSessions(ctx, missingSessions)
if err != nil {
log.Error().Err(err).Msg("Failed to create missing olm sessions")
log.Err(err).Msg("Failed to create missing olm sessions")
return fmt.Errorf("failed to create missing olm sessions: %w", err)
}
}

View file

@ -1,7 +1,6 @@
package crypto
import (
"bytes"
"crypto/ed25519"
"encoding/base64"
"fmt"
@ -118,7 +117,7 @@ type Ed25519PrivateKey ed25519.PrivateKey
// Equal compares the private key to the given private key.
func (c Ed25519PrivateKey) Equal(x Ed25519PrivateKey) bool {
return bytes.Equal(c, x)
return ed25519.PrivateKey(c).Equal(ed25519.PrivateKey(x))
}
// PubKey returns the public key derived from the private key.
@ -137,7 +136,7 @@ type Ed25519PublicKey ed25519.PublicKey
// Equal compares the public key to the given public key.
func (c Ed25519PublicKey) Equal(x Ed25519PublicKey) bool {
return bytes.Equal(c, x)
return ed25519.PublicKey(c).Equal(ed25519.PublicKey(x))
}
// B64Encoded returns a base64 encoded string of the public key.

View file

@ -54,6 +54,9 @@ func (mach *OlmMachine) GetAndVerifyLatestKeyBackupVersion(ctx context.Context)
}
crossSigningPubkeys := mach.GetOwnCrossSigningPublicKeys(ctx)
if crossSigningPubkeys == nil {
return nil, ErrCrossSigningPubkeysNotCached
}
signatureVerified := false
for keyID := range userSignatures {
@ -114,7 +117,7 @@ func (mach *OlmMachine) GetAndStoreKeyBackup(ctx context.Context, version id.Key
continue
}
err = mach.ImportRoomKeyFromBackup(ctx, version, roomID, sessionID, sessionData)
_, err = mach.ImportRoomKeyFromBackup(ctx, version, roomID, sessionID, sessionData)
if err != nil {
log.Warn().Err(err).Msg("Failed to import room key from backup")
failedCount++
@ -132,23 +135,23 @@ func (mach *OlmMachine) GetAndStoreKeyBackup(ctx context.Context, version id.Key
return nil
}
func (mach *OlmMachine) ImportRoomKeyFromBackup(ctx context.Context, version id.KeyBackupVersion, roomID id.RoomID, sessionID id.SessionID, keyBackupData *backup.MegolmSessionData) error {
func (mach *OlmMachine) ImportRoomKeyFromBackup(ctx context.Context, version id.KeyBackupVersion, roomID id.RoomID, sessionID id.SessionID, keyBackupData *backup.MegolmSessionData) (*InboundGroupSession, error) {
log := zerolog.Ctx(ctx).With().
Str("room_id", roomID.String()).
Str("session_id", sessionID.String()).
Logger()
if keyBackupData.Algorithm != id.AlgorithmMegolmV1 {
return fmt.Errorf("ignoring room key in backup with weird algorithm %s", keyBackupData.Algorithm)
return nil, fmt.Errorf("ignoring room key in backup with weird algorithm %s", keyBackupData.Algorithm)
}
igsInternal, err := olm.InboundGroupSessionImport([]byte(keyBackupData.SessionKey))
if err != nil {
return fmt.Errorf("failed to import inbound group session: %w", err)
return nil, fmt.Errorf("failed to import inbound group session: %w", err)
} else if igsInternal.ID() != sessionID {
log.Warn().
Stringer("actual_session_id", igsInternal.ID()).
Msg("Mismatched session ID while creating inbound group session from key backup")
return fmt.Errorf("mismatched session ID while creating inbound group session from key backup")
return nil, fmt.Errorf("mismatched session ID while creating inbound group session from key backup")
}
var maxAge time.Duration
@ -160,7 +163,8 @@ func (mach *OlmMachine) ImportRoomKeyFromBackup(ctx context.Context, version id.
maxMessages = config.RotationPeriodMessages
}
if firstKnownIndex := igsInternal.FirstKnownIndex(); firstKnownIndex > 0 {
firstKnownIndex := igsInternal.FirstKnownIndex()
if firstKnownIndex > 0 {
log.Warn().Uint32("first_known_index", firstKnownIndex).Msg("Importing partial session")
}
@ -177,10 +181,10 @@ func (mach *OlmMachine) ImportRoomKeyFromBackup(ctx context.Context, version id.
MaxMessages: maxMessages,
KeyBackupVersion: version,
}
err = mach.CryptoStore.PutGroupSession(ctx, roomID, keyBackupData.SenderKey, sessionID, igs)
err = mach.CryptoStore.PutGroupSession(ctx, igs)
if err != nil {
return fmt.Errorf("failed to store new inbound group session: %w", err)
return nil, fmt.Errorf("failed to store new inbound group session: %w", err)
}
mach.markSessionReceived(ctx, sessionID)
return nil
mach.markSessionReceived(ctx, roomID, sessionID, firstKnownIndex)
return igs, nil
}

View file

@ -11,7 +11,6 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
@ -20,9 +19,9 @@ import (
"fmt"
"math"
"go.mau.fi/util/random"
"golang.org/x/crypto/pbkdf2"
"maunium.net/go/mautrix/crypto/olm"
"maunium.net/go/mautrix/id"
)
@ -66,25 +65,15 @@ func computeKey(passphrase string, salt []byte, rounds int) (encryptionKey, hash
}
func makeExportIV() []byte {
iv := make([]byte, 16)
_, err := rand.Read(iv)
if err != nil {
panic(olm.NotEnoughGoRandom)
}
iv := random.Bytes(16)
// Set bit 63 to zero
iv[7] &= 0b11111110
return iv
}
func makeExportKeys(passphrase string) (encryptionKey, hashKey, salt, iv []byte) {
salt = make([]byte, 16)
_, err := rand.Read(salt)
if err != nil {
panic(olm.NotEnoughGoRandom)
}
salt = random.Bytes(16)
encryptionKey, hashKey = computeKey(passphrase, salt, defaultPassphraseRounds)
iv = makeExportIV()
return
}
@ -117,13 +106,6 @@ func exportSessionsJSON(sessions []*InboundGroupSession) ([]byte, error) {
return json.Marshal(exportedSessions)
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
func formatKeyExportData(data []byte) []byte {
base64Data := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(base64Data, data)

View file

@ -113,16 +113,17 @@ func (mach *OlmMachine) importExportedRoomKey(ctx context.Context, session Expor
ReceivedAt: time.Now().UTC(),
}
existingIGS, _ := mach.CryptoStore.GetGroupSession(ctx, igs.RoomID, igs.SenderKey, igs.ID())
if existingIGS != nil && existingIGS.Internal.FirstKnownIndex() <= igs.Internal.FirstKnownIndex() {
existingIGS, _ := mach.CryptoStore.GetGroupSession(ctx, igs.RoomID, igs.ID())
firstKnownIndex := igs.Internal.FirstKnownIndex()
if existingIGS != nil && existingIGS.Internal.FirstKnownIndex() <= firstKnownIndex {
// We already have an equivalent or better session in the store, so don't override it.
return false, nil
}
err = mach.CryptoStore.PutGroupSession(ctx, igs.RoomID, igs.SenderKey, igs.ID(), igs)
err = mach.CryptoStore.PutGroupSession(ctx, igs)
if err != nil {
return false, fmt.Errorf("failed to store imported session: %w", err)
}
mach.markSessionReceived(ctx, igs.ID())
mach.markSessionReceived(ctx, session.RoomID, igs.ID(), firstKnownIndex)
return true, nil
}

View file

@ -168,7 +168,8 @@ func (mach *OlmMachine) importForwardedRoomKey(ctx context.Context, evt *Decrypt
if content.MaxMessages != 0 {
maxMessages = content.MaxMessages
}
if firstKnownIndex := igsInternal.FirstKnownIndex(); firstKnownIndex > 0 {
firstKnownIndex := igsInternal.FirstKnownIndex()
if firstKnownIndex > 0 {
log.Warn().Uint32("first_known_index", firstKnownIndex).Msg("Importing partial session")
}
igs := &InboundGroupSession{
@ -184,17 +185,17 @@ func (mach *OlmMachine) importForwardedRoomKey(ctx context.Context, evt *Decrypt
MaxMessages: maxMessages,
IsScheduled: content.IsScheduled,
}
existingIGS, _ := mach.CryptoStore.GetGroupSession(ctx, igs.RoomID, igs.SenderKey, igs.ID())
existingIGS, _ := mach.CryptoStore.GetGroupSession(ctx, igs.RoomID, igs.ID())
if existingIGS != nil && existingIGS.Internal.FirstKnownIndex() <= igs.Internal.FirstKnownIndex() {
// We already have an equivalent or better session in the store, so don't override it.
return false
}
err = mach.CryptoStore.PutGroupSession(ctx, content.RoomID, content.SenderKey, content.SessionID, igs)
err = mach.CryptoStore.PutGroupSession(ctx, igs)
if err != nil {
log.Error().Err(err).Msg("Failed to store new inbound group session")
return false
}
mach.markSessionReceived(ctx, content.SessionID)
mach.markSessionReceived(ctx, content.RoomID, content.SessionID, firstKnownIndex)
log.Debug().Msg("Received forwarded inbound group session")
return true
}
@ -308,7 +309,7 @@ func (mach *OlmMachine) HandleRoomKeyRequest(ctx context.Context, sender id.User
return
}
igs, err := mach.CryptoStore.GetGroupSession(ctx, content.Body.RoomID, content.Body.SenderKey, content.Body.SessionID)
igs, err := mach.CryptoStore.GetGroupSession(ctx, content.Body.RoomID, content.Body.SessionID)
if err != nil {
if errors.Is(err, ErrGroupSessionWithheld) {
log.Debug().Err(err).Msg("Requested group session not available")
@ -365,7 +366,7 @@ func (mach *OlmMachine) HandleBeeperRoomKeyAck(ctx context.Context, sender id.Us
Int("first_message_index", content.FirstMessageIndex).
Logger()
sess, err := mach.CryptoStore.GetGroupSession(ctx, content.RoomID, "", content.SessionID)
sess, err := mach.CryptoStore.GetGroupSession(ctx, content.RoomID, content.SessionID)
if err != nil {
if errors.Is(err, ErrGroupSessionWithheld) {
log.Debug().Err(err).Msg("Acked group session was already redacted")
@ -385,7 +386,7 @@ func (mach *OlmMachine) HandleBeeperRoomKeyAck(ctx context.Context, sender id.Us
isInbound := sess.SenderKey == mach.OwnIdentity().IdentityKey
if isInbound && mach.DeleteOutboundKeysOnAck && content.FirstMessageIndex == 0 {
log.Debug().Msg("Redacting inbound copy of outbound group session after ack")
err = mach.CryptoStore.RedactGroupSession(ctx, content.RoomID, sess.SenderKey, content.SessionID, "outbound session acked")
err = mach.CryptoStore.RedactGroupSession(ctx, content.RoomID, content.SessionID, "outbound session acked")
if err != nil {
log.Err(err).Msg("Failed to redact group session")
}

View file

@ -15,11 +15,12 @@ import (
"github.com/rs/zerolog"
"maunium.net/go/mautrix/crypto/ssss"
"maunium.net/go/mautrix/id"
"go.mau.fi/util/exzerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/ssss"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// OlmMachine is the main struct for handling Matrix end-to-end encryption.
@ -53,7 +54,7 @@ type OlmMachine struct {
keyWaitersLock sync.Mutex
// Optional callback which is called when we save a session to store
SessionReceived func(context.Context, id.SessionID)
SessionReceived func(context.Context, id.RoomID, id.SessionID, uint32)
devicesToUnwedge map[id.IdentityKey]bool
devicesToUnwedgeLock sync.Mutex
@ -78,6 +79,7 @@ type OlmMachine struct {
RatchetKeysOnDecrypt bool
DeleteFullyUsedKeysOnDecrypt bool
DeleteKeysOnDeviceDelete bool
DisableRatchetTracking bool
DisableDeviceChangeKeyRotation bool
@ -505,25 +507,24 @@ func (mach *OlmMachine) SendEncryptedToDevice(ctx context.Context, device *id.De
return err
}
func (mach *OlmMachine) createGroupSession(ctx context.Context, senderKey id.SenderKey, signingKey id.Ed25519, roomID id.RoomID, sessionID id.SessionID, sessionKey string, maxAge time.Duration, maxMessages int, isScheduled bool) {
func (mach *OlmMachine) createGroupSession(ctx context.Context, senderKey id.SenderKey, signingKey id.Ed25519, roomID id.RoomID, sessionID id.SessionID, sessionKey string, maxAge time.Duration, maxMessages int, isScheduled bool) error {
log := zerolog.Ctx(ctx)
igs, err := NewInboundGroupSession(senderKey, signingKey, roomID, sessionKey, maxAge, maxMessages, isScheduled)
if err != nil {
log.Error().Err(err).Msg("Failed to create inbound group session")
return
return fmt.Errorf("failed to create inbound group session: %w", err)
} else if igs.ID() != sessionID {
log.Warn().
Str("expected_session_id", sessionID.String()).
Str("actual_session_id", igs.ID().String()).
Msg("Mismatched session ID while creating inbound group session")
return
return fmt.Errorf("mismatched session ID while creating inbound group session")
}
err = mach.CryptoStore.PutGroupSession(ctx, roomID, senderKey, sessionID, igs)
err = mach.CryptoStore.PutGroupSession(ctx, igs)
if err != nil {
log.Error().Err(err).Str("session_id", sessionID.String()).Msg("Failed to store new inbound group session")
return
log.Err(err).Str("session_id", sessionID.String()).Msg("Failed to store new inbound group session")
return fmt.Errorf("failed to store new inbound group session: %w", err)
}
mach.markSessionReceived(ctx, sessionID)
mach.markSessionReceived(ctx, roomID, sessionID, igs.Internal.FirstKnownIndex())
log.Debug().
Str("session_id", sessionID.String()).
Str("sender_key", senderKey.String()).
@ -531,11 +532,12 @@ func (mach *OlmMachine) createGroupSession(ctx context.Context, senderKey id.Sen
Int("max_messages", maxMessages).
Bool("is_scheduled", isScheduled).
Msg("Received inbound group session")
return nil
}
func (mach *OlmMachine) markSessionReceived(ctx context.Context, id id.SessionID) {
func (mach *OlmMachine) markSessionReceived(ctx context.Context, roomID id.RoomID, id id.SessionID, firstKnownIndex uint32) {
if mach.SessionReceived != nil {
mach.SessionReceived(ctx, id)
mach.SessionReceived(ctx, roomID, id, firstKnownIndex)
}
mach.keyWaitersLock.Lock()
@ -557,7 +559,7 @@ func (mach *OlmMachine) WaitForSession(ctx context.Context, roomID id.RoomID, se
}
mach.keyWaitersLock.Unlock()
// Handle race conditions where a session appears between the failed decryption and WaitForSession call.
sess, err := mach.CryptoStore.GetGroupSession(ctx, roomID, senderKey, sessionID)
sess, err := mach.CryptoStore.GetGroupSession(ctx, roomID, sessionID)
if sess != nil || errors.Is(err, ErrGroupSessionWithheld) {
return true
}
@ -565,7 +567,7 @@ func (mach *OlmMachine) WaitForSession(ctx context.Context, roomID id.RoomID, se
case <-ch:
return true
case <-time.After(timeout):
sess, err = mach.CryptoStore.GetGroupSession(ctx, roomID, senderKey, sessionID)
sess, err = mach.CryptoStore.GetGroupSession(ctx, roomID, sessionID)
// Check if the session somehow appeared in the store without telling us
// We accept withheld sessions as received, as then the decryption attempt will show the error.
return sess != nil || errors.Is(err, ErrGroupSessionWithheld)
@ -574,14 +576,6 @@ func (mach *OlmMachine) WaitForSession(ctx context.Context, roomID id.RoomID, se
}
}
func stringifyArray[T ~string](arr []T) []string {
strs := make([]string, len(arr))
for i, v := range arr {
strs[i] = string(v)
}
return strs
}
func (mach *OlmMachine) receiveRoomKey(ctx context.Context, evt *DecryptedOlmEvent, content *event.RoomKeyEventContent) {
log := zerolog.Ctx(ctx).With().
Str("algorithm", string(content.Algorithm)).
@ -622,11 +616,14 @@ func (mach *OlmMachine) receiveRoomKey(ctx context.Context, evt *DecryptedOlmEve
log.Err(err).Msg("Failed to redact previous megolm sessions")
} else {
log.Info().
Strs("session_ids", stringifyArray(sessionIDs)).
Array("session_ids", exzerolog.ArrayOfStrs(sessionIDs)).
Msg("Redacted previous megolm sessions")
}
}
mach.createGroupSession(ctx, evt.SenderKey, evt.Keys.Ed25519, content.RoomID, content.SessionID, content.SessionKey, maxAge, maxMessages, content.IsScheduled)
err = mach.createGroupSession(ctx, evt.SenderKey, evt.Keys.Ed25519, content.RoomID, content.SessionID, content.SessionKey, maxAge, maxMessages, content.IsScheduled)
if err != nil {
log.Err(err).Msg("Failed to create inbound group session")
}
}
func (mach *OlmMachine) HandleRoomKeyWithheld(ctx context.Context, content *event.RoomKeyWithheldEventContent) {
@ -634,6 +631,7 @@ func (mach *OlmMachine) HandleRoomKeyWithheld(ctx context.Context, content *even
zerolog.Ctx(ctx).Debug().Interface("content", content).Msg("Non-megolm room key withheld event")
return
}
// TODO log if there's a conflict? (currently ignored)
err := mach.CryptoStore.PutWithheldGroupSession(ctx, *content)
if err != nil {
zerolog.Ctx(ctx).Error().Err(err).Msg("Failed to save room key withheld event")
@ -681,6 +679,11 @@ func (mach *OlmMachine) ShareKeys(ctx context.Context, currentOTKCount int) erro
log.Debug().Msg("No one-time keys nor device keys got when trying to share keys")
return nil
}
// Save the keys before sending the upload request in case there is a
// network failure.
if err := mach.saveAccount(ctx); err != nil {
return err
}
req := &mautrix.ReqUploadKeys{
DeviceKeys: deviceKeys,
OneTimeKeys: oneTimeKeys,
@ -691,6 +694,7 @@ func (mach *OlmMachine) ShareKeys(ctx context.Context, currentOTKCount int) erro
return err
}
mach.lastOTKUpload = time.Now()
mach.account.Internal.MarkKeysAsPublished()
mach.account.Shared = true
return mach.saveAccount(ctx)
}
@ -702,7 +706,7 @@ func (mach *OlmMachine) ExpiredKeyDeleteLoop(ctx context.Context) {
if err != nil {
log.Err(err).Msg("Failed to redact expired megolm sessions")
} else if len(sessionIDs) > 0 {
log.Info().Strs("session_ids", stringifyArray(sessionIDs)).Msg("Redacted expired megolm sessions")
log.Info().Array("session_ids", exzerolog.ArrayOfStrs(sessionIDs)).Msg("Redacted expired megolm sessions")
} else {
log.Debug().Msg("Didn't find any expired megolm sessions")
}

View file

@ -7,6 +7,7 @@ package olm
import "C"
import (
"bytes"
"encoding/base64"
"unsafe"
@ -190,12 +191,6 @@ func (s *InboundGroupSession) UnmarshalJSON(data []byte) error {
return s.Unpickle(data[1:len(data)-1], pickleKey)
}
func clone(original []byte) []byte {
clone := make([]byte, len(original))
copy(clone, original)
return clone
}
// decryptMaxPlaintextLen returns the maximum number of bytes of plain-text a
// given message could decode to. The actual size could be different due to
// padding. Returns error on failure. If the message base64 couldn't be
@ -208,7 +203,7 @@ func (s *InboundGroupSession) decryptMaxPlaintextLen(message []byte) (uint, erro
return 0, EmptyInput
}
// olm_group_decrypt_max_plaintext_length destroys the input, so we have to clone it
message = clone(message)
message = bytes.Clone(message)
r := C.olm_group_decrypt_max_plaintext_length(
(*C.OlmInboundGroupSession)(s.int),
(*C.uint8_t)(&message[0]),

View file

@ -5,12 +5,6 @@ package olm
// #cgo LDFLAGS: -lolm -lstdc++
// #include <olm/olm.h>
import "C"
import (
"maunium.net/go/mautrix/id"
)
// Signatures is the data structure used to sign JSON objects.
type Signatures map[id.UserID]map[id.DeviceKeyID]string
// Version returns the version number of the olm library.
func Version() (major, minor, patch uint8) {

View file

@ -2,13 +2,6 @@
package olm
import (
"maunium.net/go/mautrix/id"
)
// Signatures is the data structure used to sign JSON objects.
type Signatures map[id.UserID]map[id.DeviceKeyID]string
// Version returns the version number of the olm library.
func Version() (major, minor, patch uint8) {
return 3, 2, 15

View file

@ -8,23 +8,23 @@ package pkcs7
import "bytes"
// Pad implements PKCS#7 padding as defined in [RFC2315]. It pads the plaintext
// to the given blockSize in the range [1, 255]. This is normally used in
// AES-CBC encryption.
// Pad implements PKCS#7 padding as defined in [RFC2315]. It pads the data to
// the given blockSize in the range [1, 255]. This is normally used in AES-CBC
// encryption.
//
// [RFC2315]: https://www.ietf.org/rfc/rfc2315.txt
func Pad(plaintext []byte, blockSize int) []byte {
padding := blockSize - len(plaintext)%blockSize
return append(plaintext, bytes.Repeat([]byte{byte(padding)}, padding)...)
func Pad(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
return append(data, bytes.Repeat([]byte{byte(padding)}, padding)...)
}
// Unpad implements PKCS#7 unpadding as defined in [RFC2315]. It unpads the
// plaintext by reading the padding amount from the last byte of the plaintext.
// This is normally used in AES-CBC decryption.
// data by reading the padding amount from the last byte of the data. This is
// normally used in AES-CBC decryption.
//
// [RFC2315]: https://www.ietf.org/rfc/rfc2315.txt
func Unpad(plaintext []byte) []byte {
length := len(plaintext)
unpadding := int(plaintext[length-1])
return plaintext[:length-unpadding]
func Unpad(data []byte) []byte {
length := len(data)
unpadding := int(data[length-1])
return data[:length-unpadding]
}

View file

@ -196,11 +196,14 @@ func NewOutboundGroupSession(roomID id.RoomID, encryptionContent *event.Encrypti
RoomID: roomID,
}
if encryptionContent != nil {
// Clamp rotation period to prevent unreasonable values
// Similar to https://github.com/matrix-org/matrix-rust-sdk/blob/matrix-sdk-crypto-0.7.1/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs#L415-L441
if encryptionContent.RotationPeriodMillis != 0 {
ogs.MaxAge = time.Duration(encryptionContent.RotationPeriodMillis) * time.Millisecond
ogs.MaxAge = min(max(ogs.MaxAge, 1*time.Hour), 365*24*time.Hour)
}
if encryptionContent.RotationPeriodMessages != 0 {
ogs.MaxMessages = encryptionContent.RotationPeriodMessages
ogs.MaxMessages = min(max(encryptionContent.RotationPeriodMessages, 1), 10000)
}
}
return ogs

View file

@ -77,8 +77,8 @@ func (store *SQLCryptoStore) PutNextBatch(ctx context.Context, nextBatch string)
// GetNextBatch retrieves the next sync batch token for the current account.
func (store *SQLCryptoStore) GetNextBatch(ctx context.Context) (string, error) {
if store.SyncToken == "" {
err := store.DB.Conn(ctx).
QueryRowContext(ctx, "SELECT sync_token FROM crypto_account WHERE account_id=$1", store.AccountID).
err := store.DB.
QueryRow(ctx, "SELECT sync_token FROM crypto_account WHERE account_id=$1", store.AccountID).
Scan(&store.SyncToken)
if !errors.Is(err, sql.ErrNoRows) {
return "", err
@ -261,13 +261,6 @@ func (store *SQLCryptoStore) UpdateSession(ctx context.Context, _ id.SenderKey,
return err
}
func intishPtr[T int | int64](i T) *T {
if i == 0 {
return nil
}
return &i
}
func datePtr(t time.Time) *time.Time {
if t.IsZero() {
return nil
@ -276,13 +269,25 @@ func datePtr(t time.Time) *time.Time {
}
// PutGroupSession stores an inbound Megolm group session for a room, sender and session.
func (store *SQLCryptoStore) PutGroupSession(ctx context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, session *InboundGroupSession) error {
func (store *SQLCryptoStore) PutGroupSession(ctx context.Context, session *InboundGroupSession) error {
sessionBytes := session.Internal.Pickle(store.PickleKey)
forwardingChains := strings.Join(session.ForwardingChains, ",")
ratchetSafety, err := json.Marshal(&session.RatchetSafety)
if err != nil {
return fmt.Errorf("failed to marshal ratchet safety info: %w", err)
}
zerolog.Ctx(ctx).Debug().
Stringer("session_id", session.ID()).
Str("account_id", store.AccountID).
Stringer("sender_key", session.SenderKey).
Stringer("signing_key", session.SigningKey).
Stringer("room_id", session.RoomID).
Time("received_at", session.ReceivedAt).
Int64("max_age", session.MaxAge).
Int("max_messages", session.MaxMessages).
Bool("is_scheduled", session.IsScheduled).
Stringer("key_backup_version", session.KeyBackupVersion).
Msg("Upserting megolm inbound group session")
_, err = store.DB.Exec(ctx, `
INSERT INTO crypto_megolm_inbound_session (
session_id, sender_key, signing_key, room_id, session, forwarding_chains,
@ -295,16 +300,16 @@ func (store *SQLCryptoStore) PutGroupSession(ctx context.Context, roomID id.Room
max_age=excluded.max_age, max_messages=excluded.max_messages, is_scheduled=excluded.is_scheduled,
key_backup_version=excluded.key_backup_version
`,
sessionID, senderKey, session.SigningKey, roomID, sessionBytes, forwardingChains,
ratchetSafety, datePtr(session.ReceivedAt), intishPtr(session.MaxAge), intishPtr(session.MaxMessages),
session.ID(), session.SenderKey, session.SigningKey, session.RoomID, sessionBytes, forwardingChains,
ratchetSafety, datePtr(session.ReceivedAt), dbutil.NumPtr(session.MaxAge), dbutil.NumPtr(session.MaxMessages),
session.IsScheduled, session.KeyBackupVersion, store.AccountID,
)
return err
}
// GetGroupSession retrieves an inbound Megolm group session for a room, sender and session.
func (store *SQLCryptoStore) GetGroupSession(ctx context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID) (*InboundGroupSession, error) {
var senderKeyDB, signingKey, forwardingChains, withheldCode, withheldReason sql.NullString
func (store *SQLCryptoStore) GetGroupSession(ctx context.Context, roomID id.RoomID, sessionID id.SessionID) (*InboundGroupSession, error) {
var senderKey, signingKey, forwardingChains, withheldCode, withheldReason sql.NullString
var sessionBytes, ratchetSafetyBytes []byte
var receivedAt sql.NullTime
var maxAge, maxMessages sql.NullInt64
@ -313,9 +318,9 @@ func (store *SQLCryptoStore) GetGroupSession(ctx context.Context, roomID id.Room
err := store.DB.QueryRow(ctx, `
SELECT sender_key, signing_key, session, forwarding_chains, withheld_code, withheld_reason, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version
FROM crypto_megolm_inbound_session
WHERE room_id=$1 AND (sender_key=$2 OR $2 = '') AND session_id=$3 AND account_id=$4`,
roomID, senderKey, sessionID, store.AccountID,
).Scan(&senderKeyDB, &signingKey, &sessionBytes, &forwardingChains, &withheldCode, &withheldReason, &ratchetSafetyBytes, &receivedAt, &maxAge, &maxMessages, &isScheduled, &version)
WHERE room_id=$1 AND session_id=$2 AND account_id=$3`,
roomID, sessionID, store.AccountID,
).Scan(&senderKey, &signingKey, &sessionBytes, &forwardingChains, &withheldCode, &withheldReason, &ratchetSafetyBytes, &receivedAt, &maxAge, &maxMessages, &isScheduled, &version)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
} else if err != nil {
@ -325,19 +330,19 @@ func (store *SQLCryptoStore) GetGroupSession(ctx context.Context, roomID id.Room
RoomID: roomID,
Algorithm: id.AlgorithmMegolmV1,
SessionID: sessionID,
SenderKey: senderKey,
SenderKey: id.Curve25519(senderKey.String),
Code: event.RoomKeyWithheldCode(withheldCode.String),
Reason: withheldReason.String,
}
}
igs, chains, rs, err := store.postScanInboundGroupSession(sessionBytes, ratchetSafetyBytes, forwardingChains.String)
if senderKey == "" {
senderKey = id.Curve25519(senderKeyDB.String)
if err != nil {
return nil, err
}
return &InboundGroupSession{
Internal: *igs,
SigningKey: id.Ed25519(signingKey.String),
SenderKey: senderKey,
SenderKey: id.Curve25519(senderKey.String),
RoomID: roomID,
ForwardingChains: chains,
RatchetSafety: rs,
@ -349,7 +354,7 @@ func (store *SQLCryptoStore) GetGroupSession(ctx context.Context, roomID id.Room
}, nil
}
func (store *SQLCryptoStore) RedactGroupSession(ctx context.Context, _ id.RoomID, _ id.SenderKey, sessionID id.SessionID, reason string) error {
func (store *SQLCryptoStore) RedactGroupSession(ctx context.Context, _ id.RoomID, sessionID id.SessionID, reason string) error {
_, err := store.DB.Exec(ctx, `
UPDATE crypto_megolm_inbound_session
SET withheld_code=$1, withheld_reason=$2, session=NULL, forwarding_chains=NULL
@ -420,18 +425,21 @@ func (store *SQLCryptoStore) RedactOutdatedGroupSessions(ctx context.Context) ([
}
func (store *SQLCryptoStore) PutWithheldGroupSession(ctx context.Context, content event.RoomKeyWithheldEventContent) error {
_, err := store.DB.Exec(ctx, "INSERT INTO crypto_megolm_inbound_session (session_id, sender_key, room_id, withheld_code, withheld_reason, received_at, account_id) VALUES ($1, $2, $3, $4, $5, $6, $7)",
content.SessionID, content.SenderKey, content.RoomID, content.Code, content.Reason, time.Now().UTC(), store.AccountID)
_, err := store.DB.Exec(ctx, `
INSERT INTO crypto_megolm_inbound_session (session_id, sender_key, room_id, withheld_code, withheld_reason, received_at, account_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (session_id, account_id) DO NOTHING
`, content.SessionID, content.SenderKey, content.RoomID, content.Code, content.Reason, time.Now().UTC(), store.AccountID)
return err
}
func (store *SQLCryptoStore) GetWithheldGroupSession(ctx context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID) (*event.RoomKeyWithheldEventContent, error) {
var code, reason sql.NullString
func (store *SQLCryptoStore) GetWithheldGroupSession(ctx context.Context, roomID id.RoomID, sessionID id.SessionID) (*event.RoomKeyWithheldEventContent, error) {
var senderKey, code, reason sql.NullString
err := store.DB.QueryRow(ctx, `
SELECT withheld_code, withheld_reason FROM crypto_megolm_inbound_session
WHERE room_id=$1 AND sender_key=$2 AND session_id=$3 AND account_id=$4`,
roomID, senderKey, sessionID, store.AccountID,
).Scan(&code, &reason)
SELECT withheld_code, withheld_reason, sender_key FROM crypto_megolm_inbound_session
WHERE room_id=$1 AND session_id=$2 AND account_id=$3`,
roomID, sessionID, store.AccountID,
).Scan(&code, &reason, &senderKey)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
} else if err != nil || !code.Valid {
@ -441,7 +449,7 @@ func (store *SQLCryptoStore) GetWithheldGroupSession(ctx context.Context, roomID
RoomID: roomID,
Algorithm: id.AlgorithmMegolmV1,
SessionID: sessionID,
SenderKey: senderKey,
SenderKey: id.Curve25519(senderKey.String),
Code: event.RoomKeyWithheldCode(code.String),
Reason: reason.String,
}, nil
@ -479,6 +487,9 @@ func (store *SQLCryptoStore) scanInboundGroupSession(rows dbutil.Scannable) (*In
return nil, err
}
igs, chains, rs, err := store.postScanInboundGroupSession(sessionBytes, ratchetSafetyBytes, forwardingChains.String)
if err != nil {
return nil, err
}
return &InboundGroupSession{
Internal: *igs,
SigningKey: id.Ed25519(signingKey.String),
@ -744,6 +755,17 @@ func (store *SQLCryptoStore) PutDevices(ctx context.Context, userID id.UserID, d
})
}
func userIDsToParams(users []id.UserID) (placeholders string, params []any) {
queryString := make([]string, len(users))
params = make([]any, len(users))
for i, user := range users {
queryString[i] = fmt.Sprintf("$%d", i+1)
params[i] = user
}
placeholders = strings.Join(queryString, ",")
return
}
// FilterTrackedUsers finds all the user IDs out of the given ones for which the database contains identity information.
func (store *SQLCryptoStore) FilterTrackedUsers(ctx context.Context, users []id.UserID) ([]id.UserID, error) {
var rows dbutil.Rows
@ -751,13 +773,8 @@ func (store *SQLCryptoStore) FilterTrackedUsers(ctx context.Context, users []id.
if store.DB.Dialect == dbutil.Postgres && PostgresArrayWrapper != nil {
rows, err = store.DB.Query(ctx, "SELECT user_id FROM crypto_tracked_user WHERE user_id = ANY($1)", PostgresArrayWrapper(users))
} else {
queryString := make([]string, len(users))
params := make([]interface{}, len(users))
for i, user := range users {
queryString[i] = fmt.Sprintf("?%d", i+1)
params[i] = user
}
rows, err = store.DB.Query(ctx, "SELECT user_id FROM crypto_tracked_user WHERE user_id IN ("+strings.Join(queryString, ",")+")", params...)
placeholders, params := userIDsToParams(users)
rows, err = store.DB.Query(ctx, "SELECT user_id FROM crypto_tracked_user WHERE user_id IN ("+placeholders+")", params...)
}
if err != nil {
return users, err
@ -766,18 +783,14 @@ func (store *SQLCryptoStore) FilterTrackedUsers(ctx context.Context, users []id.
}
// MarkTrackedUsersOutdated flags that the device list for given users are outdated.
func (store *SQLCryptoStore) MarkTrackedUsersOutdated(ctx context.Context, users []id.UserID) error {
return store.DB.DoTxn(ctx, nil, func(ctx context.Context) error {
// TODO refactor to use a single query
for _, userID := range users {
_, err := store.DB.Exec(ctx, "UPDATE crypto_tracked_user SET devices_outdated = true WHERE user_id = $1", userID)
if err != nil {
return fmt.Errorf("failed to update user in the tracked users list: %w", err)
func (store *SQLCryptoStore) MarkTrackedUsersOutdated(ctx context.Context, users []id.UserID) (err error) {
if store.DB.Dialect == dbutil.Postgres && PostgresArrayWrapper != nil {
_, err = store.DB.Exec(ctx, "UPDATE crypto_tracked_user SET devices_outdated = true WHERE user_id = ANY($1)", PostgresArrayWrapper(users))
} else {
placeholders, params := userIDsToParams(users)
_, err = store.DB.Exec(ctx, "UPDATE crypto_tracked_user SET devices_outdated = true WHERE user_id IN ("+placeholders+")", params...)
}
}
return nil
})
return
}
// GetOutdatedTrackerUsers gets all tracked users whose devices need to be updated.
@ -876,15 +889,15 @@ func (store *SQLCryptoStore) PutSecret(ctx context.Context, name id.Secret, valu
return err
}
_, err = store.DB.Exec(ctx, `
INSERT INTO crypto_secrets (name, secret) VALUES ($1, $2)
ON CONFLICT (name) DO UPDATE SET secret=excluded.secret
`, name, bytes)
INSERT INTO crypto_secrets (account_id, name, secret) VALUES ($1, $2, $3)
ON CONFLICT (account_id, name) DO UPDATE SET secret=excluded.secret
`, store.AccountID, name, bytes)
return err
}
func (store *SQLCryptoStore) GetSecret(ctx context.Context, name id.Secret) (value string, err error) {
var bytes []byte
err = store.DB.QueryRow(ctx, `SELECT secret FROM crypto_secrets WHERE name=$1`, name).Scan(&bytes)
err = store.DB.QueryRow(ctx, `SELECT secret FROM crypto_secrets WHERE account_id=$1 AND name=$2`, store.AccountID, name).Scan(&bytes)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
} else if err != nil {
@ -895,6 +908,6 @@ func (store *SQLCryptoStore) GetSecret(ctx context.Context, name id.Secret) (val
}
func (store *SQLCryptoStore) DeleteSecret(ctx context.Context, name id.Secret) (err error) {
_, err = store.DB.Exec(ctx, "DELETE FROM crypto_secrets WHERE name=$1", name)
_, err = store.DB.Exec(ctx, "DELETE FROM crypto_secrets WHERE account_id=$1 AND name=$2", store.AccountID, name)
return
}

View file

@ -1,4 +1,4 @@
-- v0 -> v14 (compatible with v9+): Latest revision
-- v0 -> v15: Latest revision
CREATE TABLE IF NOT EXISTS crypto_account (
account_id TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
@ -105,6 +105,9 @@ CREATE TABLE IF NOT EXISTS crypto_cross_signing_signatures (
);
CREATE TABLE IF NOT EXISTS crypto_secrets (
name TEXT PRIMARY KEY NOT NULL,
secret bytea NOT NULL
account_id TEXT NOT NULL,
name TEXT NOT NULL,
secret bytea NOT NULL,
PRIMARY KEY (account_id, name)
);

View file

@ -0,0 +1,21 @@
-- v15: Fix crypto_secrets table
CREATE TABLE crypto_secrets_new (
account_id TEXT NOT NULL,
name TEXT NOT NULL,
secret bytea NOT NULL,
PRIMARY KEY (account_id, name)
);
INSERT INTO crypto_secrets_new (account_id, name, secret)
SELECT '', name, secret
FROM crypto_secrets;
DROP TABLE crypto_secrets;
ALTER TABLE crypto_secrets_new RENAME TO crypto_secrets;
-- only: sqlite
UPDATE crypto_secrets SET account_id=(SELECT account_id FROM crypto_account ORDER BY rowid DESC LIMIT 1);
-- only: postgres
UPDATE crypto_secrets SET account_id=(SELECT account_id FROM crypto_account LIMIT 1);

View file

@ -22,7 +22,7 @@ const VersionTableName = "crypto_version"
var fs embed.FS
func init() {
Table.Register(-1, 3, 0, "Unsupported version", false, func(ctx context.Context, database *dbutil.Database) error {
Table.Register(-1, 3, 0, "Unsupported version", dbutil.TxnModeOff, func(ctx context.Context, database *dbutil.Database) error {
return fmt.Errorf("upgrading from versions 1 and 2 of the crypto store is no longer supported in mautrix-go v0.12+")
})
Table.RegisterFS(fs)

View file

@ -7,11 +7,12 @@
package ssss
import (
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
"go.mau.fi/util/random"
"maunium.net/go/mautrix/crypto/utils"
)
@ -33,10 +34,7 @@ func NewKey(passphrase string) (*Key, error) {
if len(passphrase) > 0 {
// There's a passphrase. We need to generate a salt for it, set the metadata
// and then compute the key using the passphrase and the metadata.
saltBytes := make([]byte, 24)
if _, err := rand.Read(saltBytes); err != nil {
return nil, fmt.Errorf("failed to get random bytes for salt: %w", err)
}
saltBytes := random.Bytes(24)
keyData.Passphrase = &PassphraseMetadata{
Algorithm: PassphraseAlgorithmPBKDF2,
Iterations: 500000,
@ -50,24 +48,15 @@ func NewKey(passphrase string) (*Key, error) {
}
} else {
// No passphrase, just generate a random key
ssssKey = make([]byte, 32)
if _, err := rand.Read(ssssKey); err != nil {
return nil, fmt.Errorf("failed to get random bytes for key: %w", err)
}
ssssKey = random.Bytes(32)
}
// Generate a random ID for the key. It's what identifies the key in account data.
keyIDBytes := make([]byte, 24)
if _, err := rand.Read(keyIDBytes); err != nil {
return nil, fmt.Errorf("failed to get random bytes for key ID: %w", err)
}
keyIDBytes := random.Bytes(24)
// We store a certain hash in the key metadata so that clients can check if the user entered the correct key.
var ivBytes [utils.AESCTRIVLength]byte
if _, err := rand.Read(ivBytes[:]); err != nil {
return nil, fmt.Errorf("failed to get random bytes for IV: %w", err)
}
keyData.IV = base64.RawStdEncoding.EncodeToString(ivBytes[:])
ivBytes := random.Bytes(utils.AESCTRIVLength)
keyData.IV = base64.RawStdEncoding.EncodeToString(ivBytes)
keyData.MAC = keyData.calculateHash(ssssKey)
return &Key{

View file

@ -13,6 +13,7 @@ import (
"sync"
"go.mau.fi/util/dbutil"
"golang.org/x/exp/maps"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
@ -50,13 +51,13 @@ type Store interface {
// PutGroupSession inserts an inbound Megolm session into the store. If an earlier withhold event has been inserted
// with PutWithheldGroupSession, this call should replace that. However, PutWithheldGroupSession must not replace
// sessions inserted with this call.
PutGroupSession(context.Context, id.RoomID, id.SenderKey, id.SessionID, *InboundGroupSession) error
PutGroupSession(context.Context, *InboundGroupSession) error
// GetGroupSession gets an inbound Megolm session from the store. If the group session has been withheld
// (i.e. a room key withheld event has been saved with PutWithheldGroupSession), this should return the
// ErrGroupSessionWithheld error. The caller may use GetWithheldGroupSession to find more details.
GetGroupSession(context.Context, id.RoomID, id.SenderKey, id.SessionID) (*InboundGroupSession, error)
GetGroupSession(context.Context, id.RoomID, id.SessionID) (*InboundGroupSession, error)
// RedactGroupSession removes the session data for the given inbound Megolm session from the store.
RedactGroupSession(context.Context, id.RoomID, id.SenderKey, id.SessionID, string) error
RedactGroupSession(context.Context, id.RoomID, id.SessionID, string) error
// RedactGroupSessions removes the session data for all inbound Megolm sessions from a specific device and/or in a specific room.
RedactGroupSessions(context.Context, id.RoomID, id.SenderKey, string) ([]id.SessionID, error)
// RedactExpiredGroupSessions removes the session data for all inbound Megolm sessions that have expired.
@ -66,7 +67,7 @@ type Store interface {
// PutWithheldGroupSession tells the store that a specific Megolm session was withheld.
PutWithheldGroupSession(context.Context, event.RoomKeyWithheldEventContent) error
// GetWithheldGroupSession gets the event content that was previously inserted with PutWithheldGroupSession.
GetWithheldGroupSession(context.Context, id.RoomID, id.SenderKey, id.SessionID) (*event.RoomKeyWithheldEventContent, error)
GetWithheldGroupSession(context.Context, id.RoomID, id.SessionID) (*event.RoomKeyWithheldEventContent, error)
// GetGroupSessionsForRoom gets all the inbound Megolm sessions for a specific room. This is used for creating key
// export files. Unlike GetGroupSession, this should not return any errors about withheld keys.
@ -160,8 +161,8 @@ type MemoryStore struct {
Account *OlmAccount
Sessions map[id.SenderKey]OlmSessionList
GroupSessions map[id.RoomID]map[id.SenderKey]map[id.SessionID]*InboundGroupSession
WithheldGroupSessions map[id.RoomID]map[id.SenderKey]map[id.SessionID]*event.RoomKeyWithheldEventContent
GroupSessions map[id.RoomID]map[id.SessionID]*InboundGroupSession
WithheldGroupSessions map[id.RoomID]map[id.SessionID]*event.RoomKeyWithheldEventContent
OutGroupSessions map[id.RoomID]*OutboundGroupSession
SharedGroupSessions map[id.UserID]map[id.IdentityKey]map[id.SessionID]struct{}
MessageIndices map[messageIndexKey]messageIndexValue
@ -182,8 +183,8 @@ func NewMemoryStore(saveCallback func() error) *MemoryStore {
save: saveCallback,
Sessions: make(map[id.SenderKey]OlmSessionList),
GroupSessions: make(map[id.RoomID]map[id.SenderKey]map[id.SessionID]*InboundGroupSession),
WithheldGroupSessions: make(map[id.RoomID]map[id.SenderKey]map[id.SessionID]*event.RoomKeyWithheldEventContent),
GroupSessions: make(map[id.RoomID]map[id.SessionID]*InboundGroupSession),
WithheldGroupSessions: make(map[id.RoomID]map[id.SessionID]*event.RoomKeyWithheldEventContent),
OutGroupSessions: make(map[id.RoomID]*OutboundGroupSession),
SharedGroupSessions: make(map[id.UserID]map[id.IdentityKey]map[id.SessionID]struct{}),
MessageIndices: make(map[messageIndexKey]messageIndexValue),
@ -197,9 +198,8 @@ func NewMemoryStore(saveCallback func() error) *MemoryStore {
func (gs *MemoryStore) Flush(_ context.Context) error {
gs.lock.Lock()
err := gs.save()
gs.lock.Unlock()
return err
defer gs.lock.Unlock()
return gs.save()
}
func (gs *MemoryStore) GetAccount(_ context.Context) (*OlmAccount, error) {
@ -208,31 +208,29 @@ func (gs *MemoryStore) GetAccount(_ context.Context) (*OlmAccount, error) {
func (gs *MemoryStore) PutAccount(_ context.Context, account *OlmAccount) error {
gs.lock.Lock()
defer gs.lock.Unlock()
gs.Account = account
err := gs.save()
gs.lock.Unlock()
return err
return gs.save()
}
func (gs *MemoryStore) GetSessions(_ context.Context, senderKey id.SenderKey) (OlmSessionList, error) {
gs.lock.Lock()
defer gs.lock.Unlock()
sessions, ok := gs.Sessions[senderKey]
if !ok {
sessions = []*OlmSession{}
gs.Sessions[senderKey] = sessions
}
gs.lock.Unlock()
return sessions, nil
}
func (gs *MemoryStore) AddSession(_ context.Context, senderKey id.SenderKey, session *OlmSession) error {
gs.lock.Lock()
sessions, _ := gs.Sessions[senderKey]
defer gs.lock.Unlock()
sessions := gs.Sessions[senderKey]
gs.Sessions[senderKey] = append(sessions, session)
sort.Sort(gs.Sessions[senderKey])
err := gs.save()
gs.lock.Unlock()
return err
return gs.save()
}
func (gs *MemoryStore) UpdateSession(_ context.Context, _ id.SenderKey, _ *OlmSession) error {
@ -242,102 +240,86 @@ func (gs *MemoryStore) UpdateSession(_ context.Context, _ id.SenderKey, _ *OlmSe
func (gs *MemoryStore) HasSession(_ context.Context, senderKey id.SenderKey) bool {
gs.lock.RLock()
defer gs.lock.RUnlock()
sessions, ok := gs.Sessions[senderKey]
gs.lock.RUnlock()
return ok && len(sessions) > 0 && !sessions[0].Expired()
}
func (gs *MemoryStore) GetLatestSession(_ context.Context, senderKey id.SenderKey) (*OlmSession, error) {
gs.lock.RLock()
defer gs.lock.RUnlock()
sessions, ok := gs.Sessions[senderKey]
gs.lock.RUnlock()
if !ok || len(sessions) == 0 {
return nil, nil
}
return sessions[0], nil
}
func (gs *MemoryStore) getGroupSessions(roomID id.RoomID, senderKey id.SenderKey) map[id.SessionID]*InboundGroupSession {
func (gs *MemoryStore) getGroupSessions(roomID id.RoomID) map[id.SessionID]*InboundGroupSession {
room, ok := gs.GroupSessions[roomID]
if !ok {
room = make(map[id.SenderKey]map[id.SessionID]*InboundGroupSession)
room = make(map[id.SessionID]*InboundGroupSession)
gs.GroupSessions[roomID] = room
}
sender, ok := room[senderKey]
if !ok {
sender = make(map[id.SessionID]*InboundGroupSession)
room[senderKey] = sender
}
return sender
return room
}
func (gs *MemoryStore) PutGroupSession(_ context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, igs *InboundGroupSession) error {
func (gs *MemoryStore) PutGroupSession(_ context.Context, igs *InboundGroupSession) error {
gs.lock.Lock()
gs.getGroupSessions(roomID, senderKey)[sessionID] = igs
err := gs.save()
gs.lock.Unlock()
return err
defer gs.lock.Unlock()
gs.getGroupSessions(igs.RoomID)[igs.ID()] = igs
return gs.save()
}
func (gs *MemoryStore) GetGroupSession(_ context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID) (*InboundGroupSession, error) {
func (gs *MemoryStore) GetGroupSession(_ context.Context, roomID id.RoomID, sessionID id.SessionID) (*InboundGroupSession, error) {
gs.lock.Lock()
session, ok := gs.getGroupSessions(roomID, senderKey)[sessionID]
defer gs.lock.Unlock()
session, ok := gs.getGroupSessions(roomID)[sessionID]
if !ok {
withheld, ok := gs.getWithheldGroupSessions(roomID, senderKey)[sessionID]
gs.lock.Unlock()
withheld, ok := gs.getWithheldGroupSessions(roomID)[sessionID]
if ok {
return nil, fmt.Errorf("%w (%s)", ErrGroupSessionWithheld, withheld.Code)
}
return nil, nil
}
gs.lock.Unlock()
return session, nil
}
func (gs *MemoryStore) RedactGroupSession(_ context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, reason string) error {
func (gs *MemoryStore) RedactGroupSession(_ context.Context, roomID id.RoomID, sessionID id.SessionID, reason string) error {
gs.lock.Lock()
delete(gs.getGroupSessions(roomID, senderKey), sessionID)
err := gs.save()
gs.lock.Unlock()
return err
defer gs.lock.Unlock()
delete(gs.getGroupSessions(roomID), sessionID)
return gs.save()
}
func (gs *MemoryStore) RedactGroupSessions(_ context.Context, roomID id.RoomID, senderKey id.SenderKey, reason string) ([]id.SessionID, error) {
gs.lock.Lock()
defer gs.lock.Unlock()
var sessionIDs []id.SessionID
if roomID != "" && senderKey != "" {
sessions := gs.getGroupSessions(roomID, senderKey)
for sessionID := range sessions {
sessions := gs.getGroupSessions(roomID)
for sessionID, session := range sessions {
if session.SenderKey == senderKey {
sessionIDs = append(sessionIDs, sessionID)
delete(sessions, sessionID)
}
}
} else if senderKey != "" {
for _, room := range gs.GroupSessions {
sessions, ok := room[senderKey]
if ok {
for sessionID := range sessions {
for sessionID, session := range room {
if session.SenderKey == senderKey {
sessionIDs = append(sessionIDs, sessionID)
delete(room, sessionID)
}
delete(room, senderKey)
}
}
} else if roomID != "" {
room, ok := gs.GroupSessions[roomID]
if ok {
for senderKey := range room {
sessions := room[senderKey]
for sessionID := range sessions {
sessionIDs = append(sessionIDs, sessionID)
}
}
sessionIDs = maps.Keys(gs.GroupSessions[roomID])
delete(gs.GroupSessions, roomID)
}
} else {
return nil, fmt.Errorf("room ID or sender key must be provided for redacting sessions")
}
err := gs.save()
gs.lock.Unlock()
return sessionIDs, err
return sessionIDs, gs.save()
}
func (gs *MemoryStore) RedactExpiredGroupSessions(_ context.Context) ([]id.SessionID, error) {
@ -348,32 +330,26 @@ func (gs *MemoryStore) RedactOutdatedGroupSessions(_ context.Context) ([]id.Sess
return nil, fmt.Errorf("not implemented")
}
func (gs *MemoryStore) getWithheldGroupSessions(roomID id.RoomID, senderKey id.SenderKey) map[id.SessionID]*event.RoomKeyWithheldEventContent {
func (gs *MemoryStore) getWithheldGroupSessions(roomID id.RoomID) map[id.SessionID]*event.RoomKeyWithheldEventContent {
room, ok := gs.WithheldGroupSessions[roomID]
if !ok {
room = make(map[id.SenderKey]map[id.SessionID]*event.RoomKeyWithheldEventContent)
room = make(map[id.SessionID]*event.RoomKeyWithheldEventContent)
gs.WithheldGroupSessions[roomID] = room
}
sender, ok := room[senderKey]
if !ok {
sender = make(map[id.SessionID]*event.RoomKeyWithheldEventContent)
room[senderKey] = sender
}
return sender
return room
}
func (gs *MemoryStore) PutWithheldGroupSession(_ context.Context, content event.RoomKeyWithheldEventContent) error {
gs.lock.Lock()
gs.getWithheldGroupSessions(content.RoomID, content.SenderKey)[content.SessionID] = &content
err := gs.save()
gs.lock.Unlock()
return err
defer gs.lock.Unlock()
gs.getWithheldGroupSessions(content.RoomID)[content.SessionID] = &content
return gs.save()
}
func (gs *MemoryStore) GetWithheldGroupSession(_ context.Context, roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID) (*event.RoomKeyWithheldEventContent, error) {
func (gs *MemoryStore) GetWithheldGroupSession(_ context.Context, roomID id.RoomID, sessionID id.SessionID) (*event.RoomKeyWithheldEventContent, error) {
gs.lock.Lock()
session, ok := gs.getWithheldGroupSessions(roomID, senderKey)[sessionID]
gs.lock.Unlock()
defer gs.lock.Unlock()
session, ok := gs.getWithheldGroupSessions(roomID)[sessionID]
if !ok {
return nil, nil
}
@ -387,51 +363,38 @@ func (gs *MemoryStore) GetGroupSessionsForRoom(_ context.Context, roomID id.Room
if !ok {
return nil
}
var result []*InboundGroupSession
for _, sessions := range room {
for _, session := range sessions {
result = append(result, session)
}
}
return dbutil.NewSliceIter[*InboundGroupSession](result)
return dbutil.NewSliceIter(maps.Values(room))
}
func (gs *MemoryStore) GetAllGroupSessions(_ context.Context) dbutil.RowIter[*InboundGroupSession] {
gs.lock.Lock()
defer gs.lock.Unlock()
var result []*InboundGroupSession
for _, room := range gs.GroupSessions {
for _, sessions := range room {
for _, session := range sessions {
result = append(result, session)
result = append(result, maps.Values(room)...)
}
}
}
gs.lock.Unlock()
return dbutil.NewSliceIter[*InboundGroupSession](result)
return dbutil.NewSliceIter(result)
}
func (gs *MemoryStore) GetGroupSessionsWithoutKeyBackupVersion(_ context.Context, version id.KeyBackupVersion) dbutil.RowIter[*InboundGroupSession] {
gs.lock.Lock()
defer gs.lock.Unlock()
var result []*InboundGroupSession
for _, room := range gs.GroupSessions {
for _, sessions := range room {
for _, session := range sessions {
for _, session := range room {
if session.KeyBackupVersion != version {
result = append(result, session)
}
}
}
}
gs.lock.Unlock()
return dbutil.NewSliceIter[*InboundGroupSession](result)
return dbutil.NewSliceIter(result)
}
func (gs *MemoryStore) AddOutboundGroupSession(_ context.Context, session *OutboundGroupSession) error {
gs.lock.Lock()
defer gs.lock.Unlock()
gs.OutGroupSessions[session.RoomID] = session
err := gs.save()
gs.lock.Unlock()
return err
return gs.save()
}
func (gs *MemoryStore) UpdateOutboundGroupSession(_ context.Context, _ *OutboundGroupSession) error {
@ -441,8 +404,8 @@ func (gs *MemoryStore) UpdateOutboundGroupSession(_ context.Context, _ *Outbound
func (gs *MemoryStore) GetOutboundGroupSession(_ context.Context, roomID id.RoomID) (*OutboundGroupSession, error) {
gs.lock.RLock()
defer gs.lock.RUnlock()
session, ok := gs.OutGroupSessions[roomID]
gs.lock.RUnlock()
if !ok {
return nil, nil
}
@ -451,18 +414,18 @@ func (gs *MemoryStore) GetOutboundGroupSession(_ context.Context, roomID id.Room
func (gs *MemoryStore) RemoveOutboundGroupSession(_ context.Context, roomID id.RoomID) error {
gs.lock.Lock()
defer gs.lock.Unlock()
session, ok := gs.OutGroupSessions[roomID]
if !ok || session == nil {
gs.lock.Unlock()
return nil
}
delete(gs.OutGroupSessions, roomID)
gs.lock.Unlock()
return nil
}
func (gs *MemoryStore) MarkOutboundGroupSessionShared(_ context.Context, userID id.UserID, identityKey id.IdentityKey, sessionID id.SessionID) error {
gs.lock.Lock()
defer gs.lock.Unlock()
if _, ok := gs.SharedGroupSessions[userID]; !ok {
gs.SharedGroupSessions[userID] = make(map[id.IdentityKey]map[id.SessionID]struct{})
@ -475,7 +438,6 @@ func (gs *MemoryStore) MarkOutboundGroupSessionShared(_ context.Context, userID
identities[identityKey][sessionID] = struct{}{}
gs.lock.Unlock()
return nil
}
@ -521,11 +483,11 @@ func (gs *MemoryStore) ValidateMessageIndex(_ context.Context, senderKey id.Send
func (gs *MemoryStore) GetDevices(_ context.Context, userID id.UserID) (map[id.DeviceID]*id.Device, error) {
gs.lock.RLock()
defer gs.lock.RUnlock()
devices, ok := gs.Devices[userID]
if !ok {
devices = nil
}
gs.lock.RUnlock()
return devices, nil
}
@ -560,30 +522,30 @@ func (gs *MemoryStore) FindDeviceByKey(_ context.Context, userID id.UserID, iden
func (gs *MemoryStore) PutDevice(_ context.Context, userID id.UserID, device *id.Device) error {
gs.lock.Lock()
defer gs.lock.Unlock()
devices, ok := gs.Devices[userID]
if !ok {
devices = make(map[id.DeviceID]*id.Device)
gs.Devices[userID] = devices
}
devices[device.DeviceID] = device
err := gs.save()
gs.lock.Unlock()
return err
return gs.save()
}
func (gs *MemoryStore) PutDevices(_ context.Context, userID id.UserID, devices map[id.DeviceID]*id.Device) error {
gs.lock.Lock()
defer gs.lock.Unlock()
gs.Devices[userID] = devices
err := gs.save()
if err == nil {
delete(gs.OutdatedUsers, userID)
}
gs.lock.Unlock()
return err
}
func (gs *MemoryStore) FilterTrackedUsers(_ context.Context, users []id.UserID) ([]id.UserID, error) {
gs.lock.RLock()
defer gs.lock.RUnlock()
var ptr int
for _, userID := range users {
_, ok := gs.Devices[userID]
@ -592,33 +554,33 @@ func (gs *MemoryStore) FilterTrackedUsers(_ context.Context, users []id.UserID)
ptr++
}
}
gs.lock.RUnlock()
return users[:ptr], nil
}
func (gs *MemoryStore) MarkTrackedUsersOutdated(_ context.Context, users []id.UserID) error {
gs.lock.Lock()
defer gs.lock.Unlock()
for _, userID := range users {
if _, ok := gs.Devices[userID]; ok {
gs.OutdatedUsers[userID] = struct{}{}
}
}
gs.lock.Unlock()
return nil
}
func (gs *MemoryStore) GetOutdatedTrackedUsers(_ context.Context) ([]id.UserID, error) {
gs.lock.RLock()
defer gs.lock.RUnlock()
users := make([]id.UserID, 0, len(gs.OutdatedUsers))
for userID := range gs.OutdatedUsers {
users = append(users, userID)
}
gs.lock.RUnlock()
return users, nil
}
func (gs *MemoryStore) PutCrossSigningKey(_ context.Context, userID id.UserID, usage id.CrossSigningUsage, key id.Ed25519) error {
gs.lock.RLock()
defer gs.lock.RUnlock()
userKeys, ok := gs.CrossSigningKeys[userID]
if !ok {
userKeys = make(map[id.CrossSigningUsage]id.CrossSigningKey)
@ -635,7 +597,6 @@ func (gs *MemoryStore) PutCrossSigningKey(_ context.Context, userID id.UserID, u
}
}
err := gs.save()
gs.lock.RUnlock()
return err
}
@ -651,6 +612,7 @@ func (gs *MemoryStore) GetCrossSigningKeys(_ context.Context, userID id.UserID)
func (gs *MemoryStore) PutSignature(_ context.Context, signedUserID id.UserID, signedKey id.Ed25519, signerUserID id.UserID, signerKey id.Ed25519, signature string) error {
gs.lock.RLock()
defer gs.lock.RUnlock()
signedUserSigs, ok := gs.KeySignatures[signedUserID]
if !ok {
signedUserSigs = make(map[id.Ed25519]map[id.UserID]map[id.Ed25519]string)
@ -667,9 +629,7 @@ func (gs *MemoryStore) PutSignature(_ context.Context, signedUserID id.UserID, s
signaturesForKey[signerUserID] = signedByUser
}
signedByUser[signerKey] = signature
err := gs.save()
gs.lock.RUnlock()
return err
return gs.save()
}
func (gs *MemoryStore) GetSignaturesForKeyBy(_ context.Context, userID id.UserID, key id.Ed25519, signerID id.UserID) (map[id.Ed25519]string, error) {
@ -700,8 +660,9 @@ func (gs *MemoryStore) IsKeySignedBy(ctx context.Context, userID id.UserID, key
}
func (gs *MemoryStore) DropSignaturesByKey(_ context.Context, userID id.UserID, key id.Ed25519) (int64, error) {
var count int64
gs.lock.RLock()
defer gs.lock.RUnlock()
var count int64
for _, userSigs := range gs.KeySignatures {
for _, keySigs := range userSigs {
if signedBySigner, ok := keySigs[userID]; ok {
@ -712,27 +673,25 @@ func (gs *MemoryStore) DropSignaturesByKey(_ context.Context, userID id.UserID,
}
}
}
gs.lock.RUnlock()
return count, nil
}
func (gs *MemoryStore) PutSecret(_ context.Context, name id.Secret, value string) error {
gs.lock.Lock()
defer gs.lock.Unlock()
gs.Secrets[name] = value
gs.lock.Unlock()
return nil
}
func (gs *MemoryStore) GetSecret(_ context.Context, name id.Secret) (value string, _ error) {
func (gs *MemoryStore) GetSecret(_ context.Context, name id.Secret) (string, error) {
gs.lock.RLock()
value = gs.Secrets[name]
gs.lock.RUnlock()
return
defer gs.lock.RUnlock()
return gs.Secrets[name], nil
}
func (gs *MemoryStore) DeleteSecret(_ context.Context, name id.Secret) error {
gs.lock.Lock()
defer gs.lock.Unlock()
delete(gs.Secrets, name)
gs.lock.Unlock()
return nil
}

View file

@ -8,6 +8,7 @@ package event
import (
"encoding/json"
"strings"
"maunium.net/go/mautrix/id"
)
@ -18,10 +19,47 @@ type TagEventContent struct {
Tags Tags `json:"tags"`
}
type Tags map[string]Tag
type Tags map[RoomTag]TagMetadata
type Tag struct {
type RoomTag string
const (
RoomTagFavourite RoomTag = "m.favourite"
RoomTagLowPriority RoomTag = "m.lowpriority"
RoomTagServerNotice RoomTag = "m.server_notice"
)
func (rt RoomTag) IsUserDefined() bool {
return strings.HasPrefix(string(rt), "u.")
}
func (rt RoomTag) String() string {
return string(rt)
}
func (rt RoomTag) Name() string {
if rt.IsUserDefined() {
return string(rt[2:])
}
switch rt {
case RoomTagFavourite:
return "Favourite"
case RoomTagLowPriority:
return "Low priority"
case RoomTagServerNotice:
return "Server notice"
default:
return ""
}
}
// Deprecated: type alias
type Tag = TagMetadata
type TagMetadata struct {
Order json.Number `json:"order,omitempty"`
MauDoublePuppetSource string `json:"fi.mau.double_puppet_source,omitempty"`
}
// DirectChatsEventContent represents the content of a m.direct account data event.
@ -43,3 +81,7 @@ type IgnoredUserListEventContent struct {
type IgnoredUser struct {
// This is an empty object
}
type MarkedUnreadEventContent struct {
Unread bool `json:"unread"`
}

8
vendor/maunium.net/go/mautrix/event/audio.go generated vendored Normal file
View file

@ -0,0 +1,8 @@
package event
type MSC1767Audio struct {
Duration int `json:"duration,omitempty"`
Waveform []int `json:"waveform,omitempty"`
}
type MSC3245Voice struct{}

View file

@ -32,11 +32,14 @@ const (
)
type BeeperMessageStatusEventContent struct {
Network string `json:"network"`
Network string `json:"network,omitempty"`
RelatesTo RelatesTo `json:"m.relates_to"`
Status MessageStatus `json:"status"`
Reason MessageStatusReason `json:"reason,omitempty"`
// Deprecated: clients were showing this to users even though they aren't supposed to.
// Use InternalError for error messages that should be included in bug reports, but not shown in the UI.
Error string `json:"error,omitempty"`
InternalError string `json:"internal_error,omitempty"`
Message string `json:"message,omitempty"`
LastRetry id.EventID `json:"last_retry,omitempty"`
@ -85,3 +88,12 @@ type BeeperLinkPreview struct {
MatchedURL string `json:"matched_url,omitempty"`
ImageEncryption *EncryptedFileInfo `json:"beeper:image:encryption,omitempty"`
}
type BeeperProfileExtra struct {
RemoteID string `json:"com.beeper.bridge.remote_id,omitempty"`
Identifiers []string `json:"com.beeper.bridge.identifiers,omitempty"`
Service string `json:"com.beeper.bridge.service,omitempty"`
Network string `json:"com.beeper.bridge.network,omitempty"`
IsBridgeBot bool `json:"com.beeper.bridge.is_bridge_bot,omitempty"`
IsNetworkBot bool `json:"com.beeper.bridge.is_network_bot,omitempty"`
}

View file

@ -40,6 +40,8 @@ var TypeMap = map[Type]reflect.Type{
StateSpaceChild: reflect.TypeOf(SpaceChildEventContent{}),
StateInsertionMarker: reflect.TypeOf(InsertionMarkerContent{}),
StateElementFunctionalMembers: reflect.TypeOf(ElementFunctionalMembersContent{}),
EventMessage: reflect.TypeOf(MessageEventContent{}),
EventSticker: reflect.TypeOf(MessageEventContent{}),
EventEncrypted: reflect.TypeOf(EncryptedEventContent{}),
@ -52,6 +54,7 @@ var TypeMap = map[Type]reflect.Type{
AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}),
AccountDataFullyRead: reflect.TypeOf(FullyReadEventContent{}),
AccountDataIgnoredUserList: reflect.TypeOf(IgnoredUserListEventContent{}),
AccountDataMarkedUnread: reflect.TypeOf(MarkedUnreadEventContent{}),
EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}),
EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}),
@ -211,6 +214,7 @@ func init() {
gob.Register(&BridgeEventContent{})
gob.Register(&SpaceChildEventContent{})
gob.Register(&SpaceParentEventContent{})
gob.Register(&ElementFunctionalMembersContent{})
gob.Register(&RoomNameEventContent{})
gob.Register(&RoomAvatarEventContent{})
gob.Register(&TopicEventContent{})
@ -352,6 +356,13 @@ func (content *Content) AsSpaceParent() *SpaceParentEventContent {
}
return casted
}
func (content *Content) AsElementFunctionalMembers() *ElementFunctionalMembersContent {
casted, ok := content.Parsed.(*ElementFunctionalMembersContent)
if !ok {
return &ElementFunctionalMembersContent{}
}
return casted
}
func (content *Content) AsMessage() *MessageEventContent {
casted, ok := content.Parsed.(*MessageEventContent)
if !ok {
@ -408,6 +419,13 @@ func (content *Content) AsIgnoredUserList() *IgnoredUserListEventContent {
}
return casted
}
func (content *Content) AsMarkedUnread() *MarkedUnreadEventContent {
casted, ok := content.Parsed.(*MarkedUnreadEventContent)
if !ok {
return &MarkedUnreadEventContent{}
}
return casted
}
func (content *Content) AsTyping() *TypingEventContent {
casted, ok := content.Parsed.(*TypingEventContent)
if !ok {

View file

@ -8,6 +8,7 @@ package event
import (
"encoding/json"
"slices"
"strconv"
"strings"
@ -21,6 +22,24 @@ import (
// https://spec.matrix.org/v1.2/client-server-api/#mroommessage-msgtypes
type MessageType string
func (mt MessageType) IsText() bool {
switch mt {
case MsgText, MsgNotice, MsgEmote:
return true
default:
return false
}
}
func (mt MessageType) IsMedia() bool {
switch mt {
case MsgImage, MsgVideo, MsgAudio, MsgFile, MessageType(EventSticker.Type):
return true
default:
return false
}
}
// Msgtypes
const (
MsgText MessageType = "m.text"
@ -118,6 +137,9 @@ type MessageEventContent struct {
BeeperGalleryCaptionHTML string `json:"com.beeper.gallery.caption_html,omitempty"`
BeeperLinkPreviews []*BeeperLinkPreview `json:"com.beeper.linkpreviews,omitempty"`
MSC1767Audio *MSC1767Audio `json:"org.matrix.msc1767.audio,omitempty"`
MSC3245Voice *MSC3245Voice `json:"org.matrix.msc3245.voice,omitempty"`
}
func (content *MessageEventContent) GetRelatesTo() *RelatesTo {
@ -191,6 +213,12 @@ type Mentions struct {
Room bool `json:"room,omitempty"`
}
func (m *Mentions) Add(userID id.UserID) {
if userID != "" && !slices.Contains(m.UserIDs, userID) {
m.UserIDs = append(m.UserIDs, userID)
}
}
type EncryptedFileInfo struct {
attachment.EncryptedFile
URL id.ContentURIString `json:"url"`

View file

@ -9,6 +9,9 @@ package event
import (
"sync"
"go.mau.fi/util/ptr"
"golang.org/x/exp/maps"
"maunium.net/go/mautrix/id"
)
@ -33,42 +36,23 @@ type PowerLevelsEventContent struct {
RedactPtr *int `json:"redact,omitempty"`
}
func copyPtr(ptr *int) *int {
if ptr == nil {
return nil
}
val := *ptr
return &val
}
func copyMap[Key comparable](m map[Key]int) map[Key]int {
if m == nil {
return nil
}
copied := make(map[Key]int, len(m))
for k, v := range m {
copied[k] = v
}
return copied
}
func (pl *PowerLevelsEventContent) Clone() *PowerLevelsEventContent {
if pl == nil {
return nil
}
return &PowerLevelsEventContent{
Users: copyMap(pl.Users),
Users: maps.Clone(pl.Users),
UsersDefault: pl.UsersDefault,
Events: copyMap(pl.Events),
Events: maps.Clone(pl.Events),
EventsDefault: pl.EventsDefault,
StateDefaultPtr: copyPtr(pl.StateDefaultPtr),
StateDefaultPtr: ptr.Clone(pl.StateDefaultPtr),
Notifications: pl.Notifications.Clone(),
InvitePtr: copyPtr(pl.InvitePtr),
KickPtr: copyPtr(pl.KickPtr),
BanPtr: copyPtr(pl.BanPtr),
RedactPtr: copyPtr(pl.RedactPtr),
InvitePtr: ptr.Clone(pl.InvitePtr),
KickPtr: ptr.Clone(pl.KickPtr),
BanPtr: ptr.Clone(pl.BanPtr),
RedactPtr: ptr.Clone(pl.RedactPtr),
}
}
@ -81,7 +65,7 @@ func (npl *NotificationPowerLevels) Clone() *NotificationPowerLevels {
return nil
}
return &NotificationPowerLevels{
RoomPtr: copyPtr(npl.RoomPtr),
RoomPtr: ptr.Clone(npl.RoomPtr),
}
}
@ -96,7 +80,7 @@ func (pl *PowerLevelsEventContent) Invite() int {
if pl.InvitePtr != nil {
return *pl.InvitePtr
}
return 50
return 0
}
func (pl *PowerLevelsEventContent) Kick() int {
@ -143,6 +127,9 @@ func (pl *PowerLevelsEventContent) SetUserLevel(userID id.UserID, level int) {
if level == pl.UsersDefault {
delete(pl.Users, userID)
} else {
if pl.Users == nil {
pl.Users = make(map[id.UserID]int)
}
pl.Users[userID] = level
}
}
@ -175,6 +162,9 @@ func (pl *PowerLevelsEventContent) SetEventLevel(eventType Type, level int) {
if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
delete(pl.Events, eventType.String())
} else {
if pl.Events == nil {
pl.Events = make(map[string]int)
}
pl.Events[eventType.String()] = level
}
}

View file

@ -26,7 +26,7 @@ type RoomNameEventContent struct {
// RoomAvatarEventContent represents the content of a m.room.avatar state event.
// https://spec.matrix.org/v1.2/client-server-api/#mroomavatar
type RoomAvatarEventContent struct {
URL id.ContentURI `json:"url"`
URL id.ContentURIString `json:"url"`
Info *FileInfo `json:"info,omitempty"`
}
@ -56,13 +56,29 @@ type Predecessor struct {
EventID id.EventID `json:"event_id"`
}
type RoomVersion string
const (
RoomV1 RoomVersion = "1"
RoomV2 RoomVersion = "2"
RoomV3 RoomVersion = "3"
RoomV4 RoomVersion = "4"
RoomV5 RoomVersion = "5"
RoomV6 RoomVersion = "6"
RoomV7 RoomVersion = "7"
RoomV8 RoomVersion = "8"
RoomV9 RoomVersion = "9"
RoomV10 RoomVersion = "10"
RoomV11 RoomVersion = "11"
)
// CreateEventContent represents the content of a m.room.create state event.
// https://spec.matrix.org/v1.2/client-server-api/#mroomcreate
type CreateEventContent struct {
Type RoomType `json:"type,omitempty"`
Creator id.UserID `json:"creator,omitempty"`
Federate bool `json:"m.federate,omitempty"`
RoomVersion string `json:"room_version,omitempty"`
RoomVersion RoomVersion `json:"room_version,omitempty"`
Predecessor *Predecessor `json:"predecessor,omitempty"`
}
@ -75,6 +91,7 @@ const (
JoinRuleKnock JoinRule = "knock"
JoinRuleInvite JoinRule = "invite"
JoinRuleRestricted JoinRule = "restricted"
JoinRuleKnockRestricted JoinRule = "knock_restricted"
JoinRulePrivate JoinRule = "private"
)
@ -149,6 +166,8 @@ type BridgeEventContent struct {
Protocol BridgeInfoSection `json:"protocol"`
Network *BridgeInfoSection `json:"network,omitempty"`
Channel BridgeInfoSection `json:"channel"`
BeeperRoomType string `json:"com.beeper.room_type,omitempty"`
}
type SpaceChildEventContent struct {
@ -175,3 +194,7 @@ type InsertionMarkerContent struct {
InsertionID id.EventID `json:"org.matrix.msc2716.marker.insertion"`
Timestamp int64 `json:"com.beeper.timestamp,omitempty"`
}
type ElementFunctionalMembersContent struct {
ServiceMembers []id.UserID `json:"service_members"`
}

View file

@ -112,11 +112,12 @@ func (et *Type) GuessClass() TypeClass {
StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateServerACL.Type, StateTopic.Type,
StatePinnedEvents.Type, StateTombstone.Type, StateEncryption.Type, StateBridge.Type, StateHalfShotBridge.Type,
StateSpaceParent.Type, StateSpaceChild.Type, StatePolicyRoom.Type, StatePolicyServer.Type, StatePolicyUser.Type,
StateInsertionMarker.Type:
StateInsertionMarker.Type, StateElementFunctionalMembers.Type:
return StateEventType
case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type:
return EphemeralEventType
case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type,
AccountDataFullyRead.Type, AccountDataIgnoredUserList.Type, AccountDataMarkedUnread.Type,
AccountDataSecretStorageKey.Type, AccountDataSecretStorageDefaultKey.Type,
AccountDataCrossSigningMaster.Type, AccountDataCrossSigningSelf.Type, AccountDataCrossSigningUser.Type,
AccountDataFullyRead.Type, AccountDataMegolmBackupKey.Type:
@ -193,6 +194,8 @@ var (
// Deprecated: MSC2716 has been abandoned
StateInsertionMarker = Type{"org.matrix.msc2716.marker", StateEventType}
StateElementFunctionalMembers = Type{"io.element.functional_members", StateEventType}
)
// Message events
@ -238,13 +241,14 @@ var (
AccountDataRoomTags = Type{"m.tag", AccountDataEventType}
AccountDataFullyRead = Type{"m.fully_read", AccountDataEventType}
AccountDataIgnoredUserList = Type{"m.ignored_user_list", AccountDataEventType}
AccountDataMarkedUnread = Type{"m.marked_unread", AccountDataEventType}
AccountDataSecretStorageDefaultKey = Type{"m.secret_storage.default_key", AccountDataEventType}
AccountDataSecretStorageKey = Type{"m.secret_storage.key", AccountDataEventType}
AccountDataCrossSigningMaster = Type{string(id.SecretXSMaster), AccountDataEventType}
AccountDataCrossSigningUser = Type{string(id.SecretXSUserSigning), AccountDataEventType}
AccountDataCrossSigningSelf = Type{string(id.SecretXSSelfSigning), AccountDataEventType}
AccountDataMegolmBackupKey = Type{"m.megolm_backup.v1", AccountDataEventType}
AccountDataMegolmBackupKey = Type{string(id.SecretMegolmBackupV1), AccountDataEventType}
)
// Device-to-device events

View file

@ -220,6 +220,10 @@ const (
VerificationCancelCodeAccepted VerificationCancelCode = "m.accepted"
VerificationCancelCodeSASMismatch VerificationCancelCode = "m.mismatched_sas"
VerificationCancelCodeCommitmentMismatch VerificationCancelCode = "m.mismatched_commitment"
// Non-spec codes
VerificationCancelCodeInternalError VerificationCancelCode = "com.beeper.internal_error"
VerificationCancelCodeMasterKeyNotTrusted VerificationCancelCode = "com.beeper.master_key_not_trusted" // the master key is not trusted by this device, but the QR code that was scanned was from a device that doesn't trust the master key
)
// VerificationCancelEventContent represents the content of an

View file

@ -119,7 +119,9 @@ type ReqCreateRoom struct {
PowerLevelOverride *event.PowerLevelsEventContent `json:"power_level_content_override,omitempty"`
MeowRoomID id.RoomID `json:"fi.mau.room_id,omitempty"`
BeeperInitialMembers []id.UserID `json:"com.beeper.initial_members,omitempty"`
BeeperAutoJoinInvites bool `json:"com.beeper.auto_join_invites,omitempty"`
BeeperLocalRoomID id.RoomID `json:"com.beeper.local_room_id,omitempty"`
}
// ReqRedact is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidredacteventidtxnid

View file

@ -8,7 +8,7 @@ import (
)
func init() {
UpgradeTable.Register(-1, 5, 0, "Mark rooms that need crypto state event resynced", true, func(ctx context.Context, db *dbutil.Database) error {
UpgradeTable.Register(-1, 5, 0, "Mark rooms that need crypto state event resynced", dbutil.TxnModeOn, func(ctx context.Context, db *dbutil.Database) error {
portalExists, err := db.TableExists(ctx, "portal")
if err != nil {
return fmt.Errorf("failed to check if portal table exists")

View file

@ -37,10 +37,18 @@ type StateStore interface {
GetRoomJoinedOrInvitedMembers(ctx context.Context, roomID id.RoomID) ([]id.UserID, error)
}
type StateStoreUpdater interface {
UpdateState(ctx context.Context, evt *event.Event)
}
func UpdateStateStore(ctx context.Context, store StateStore, evt *event.Event) {
if store == nil || evt == nil || evt.StateKey == nil {
return
}
if directUpdater, ok := store.(StateStoreUpdater); ok {
directUpdater.UpdateState(ctx, evt)
return
}
// We only care about events without a state key (power levels, encryption) or member events with state key
if evt.Type != event.StateMember && evt.GetStateKey() != "" {
return
@ -269,3 +277,14 @@ func (store *MemoryStateStore) IsEncrypted(ctx context.Context, roomID id.RoomID
cfg, err := store.GetEncryptionEvent(ctx, roomID)
return cfg != nil && cfg.Algorithm == id.AlgorithmMegolmV1, err
}
func (store *MemoryStateStore) FindSharedRooms(ctx context.Context, userID id.UserID) (rooms []id.RoomID, err error) {
store.membersLock.RLock()
defer store.membersLock.RUnlock()
for roomID, members := range store.Members {
if _, ok := members[userID]; ok {
rooms = append(rooms, roomID)
}
}
return rooms, nil
}

View file

@ -7,7 +7,7 @@ import (
"strings"
)
const Version = "v0.18.1"
const Version = "v0.19.0"
var GoModVersion = ""
var Commit = ""

View file

@ -19,6 +19,9 @@ type RespVersions struct {
}
func (versions *RespVersions) ContainsFunc(match func(found SpecVersion) bool) bool {
if versions == nil {
return false
}
for _, found := range versions.Versions {
if match(found) {
return true
@ -40,6 +43,9 @@ func (versions *RespVersions) ContainsGreaterOrEqual(version SpecVersion) bool {
}
func (versions *RespVersions) GetLatest() (latest SpecVersion) {
if versions == nil {
return
}
for _, ver := range versions.Versions {
if ver.GreaterThan(latest) {
latest = ver
@ -54,6 +60,7 @@ type UnstableFeature struct {
}
var (
FeatureAsyncUploads = UnstableFeature{UnstableFlag: "fi.mau.msc2246.stable", SpecVersion: SpecV17}
FeatureAppservicePing = UnstableFeature{UnstableFlag: "fi.mau.msc2659.stable", SpecVersion: SpecV17}
BeeperFeatureHungry = UnstableFeature{UnstableFlag: "com.beeper.hungry"}
@ -64,6 +71,9 @@ var (
)
func (versions *RespVersions) Supports(feature UnstableFeature) bool {
if versions == nil {
return false
}
return versions.UnstableFeatures[feature.UnstableFlag] ||
(!feature.SpecVersion.IsEmpty() && versions.ContainsGreaterOrEqual(feature.SpecVersion))
}
@ -95,6 +105,8 @@ var (
SpecV17 = MustParseSpecVersion("v1.7")
SpecV18 = MustParseSpecVersion("v1.8")
SpecV19 = MustParseSpecVersion("v1.9")
SpecV110 = MustParseSpecVersion("v1.10")
SpecV111 = MustParseSpecVersion("v1.11")
)
func (svf SpecVersionFormat) String() string {

565
vendor/modernc.org/libc/Makefile generated vendored
View file

@ -9,9 +9,6 @@ SHELL=/bin/bash -o pipefail
DIR = /tmp/libc
TAR = musl-7ada6dde6f9dc6a2836c3d92c2f762d35fd229e0.tar.gz
URL = https://git.musl-libc.org/cgit/musl/snapshot/$(TAR)
UCRT_386 = libc_windows_386.go
UCRT_AMD64 = libc_windows_amd64.go
UCRT_ARM64 = libc_windows_arm64.go
all: editor
golint 2>&1
@ -36,7 +33,7 @@ download:
edit:
@touch log
@if [ -f "Session.vim" ]; then novim -S & else novim -p Makefile all_windows_test.go generator.go libc.go libc_windows*.go & fi
@if [ -f "Session.vim" ]; then novim -S & else novim -p Makefile go.mod builder.json & fi
editor:
gofmt -l -s -w *.go 2>&1 | tee log-editor
@ -44,566 +41,6 @@ editor:
go install -v 2>&1 | tee -a log-editor
go build -o /dev/null generator*.go
ucrt:
make ucrt_amd64 ucrt_arm64 ucrt_386
go build -v ./... 2>&1 | tee -a log-generate
GOOS=darwin go build -v ./... 2>&1 | tee -a log-generate
git status
ucrt_amd64:
echo -n > log-generate
( ccgo -v4 \
--cpp=$(shell which x86_64-w64-mingw32-gcc) \
--goos=windows \
--goarch=amd64 \
--package-name libc \
--prefix-external=X \
--prefix-field=F \
--prefix-static-internal=_ \
--prefix-static-none=_ \
--prefix-tagged-struct=T \
--prefix-tagged-union=T \
--prefix-typename=T \
--winapi-test panic \
--winapi=ctype.h \
--winapi=float.h \
--winapi=io.h \
--winapi=libucrt.c \
--winapi=locale.h \
--winapi=malloc.h \
--winapi=math.h \
--winapi=process.h \
--winapi=types.h \
--winapi=stat.h \
--winapi=stdio.h \
--winapi=stdlib.h \
--winapi=string.h \
--winapi=time.h \
--winapi=timeb.h \
--winapi=wchar.h \
--winapi=winbase.h \
-build-lines=" " \
-eval-all-macros \
-hide __acrt_iob_func \
-hide __create_locale \
-hide __free_locale \
-hide __get_current_locale \
-hide __iob_func \
-hide __lock_fhandle \
-hide __sep__ \
-hide __updatetlocinfo \
-hide __updatetmbcinfo \
-hide _beginthread \
-hide _beginthreadex \
-hide _endthreadex \
-hide _errno \
-hide _filbuf \
-hide _flsbuf \
-hide _get_amblksiz \
-hide _get_osplatform \
-hide _get_osver \
-hide _get_output_format \
-hide _get_sbh_threshold \
-hide _get_winmajor \
-hide _get_winminor \
-hide _get_winver \
-hide _heapadd \
-hide _heapset \
-hide _heapused \
-hide _matherr \
-hide _onexit \
-hide _set_amblksiz \
-hide _set_malloc_crt_max_wait \
-hide _set_output_format \
-hide _set_sbh_threshold \
-hide _strcmpi \
-hide _strnset_l \
-hide _strset_l \
-hide _unlock_fhandle \
-hide _wcsncpy_l \
-hide _wctime \
-hide _wctime_s \
-hide _wgetdcwd_nolock \
-hide _wgetenv \
-hide _wputenv \
-hide access \
-hide at_quick_exit \
-hide atexit \
-hide chdir \
-hide exit \
-hide lldiv \
-hide qsort \
-hide chmod \
-hide chsize \
-hide close \
-hide creat \
-hide cwait \
-hide dup \
-hide dup2 \
-hide eof \
-hide execv \
-hide execve \
-hide execvp \
-hide execvpe \
-hide fcloseall \
-hide fdopen \
-hide fgetchar \
-hide fgetpos64 \
-hide filelength \
-hide fileno \
-hide flushall \
-hide fopen64 \
-hide fpreset \
-hide fputchar \
-hide fsetpos64 \
-hide ftime \
-hide fwide \
-hide getcwd \
-hide getpid \
-hide getw \
-hide isatty \
-hide itoa \
-hide lltoa \
-hide lltow \
-hide locking \
-hide lseek \
-hide lseek64 \
-hide ltoa \
-hide memccpy \
-hide memicmp \
-hide mempcpy \
-hide mkdir \
-hide mkstemp \
-hide mktemp \
-hide onexit \
-hide putenv \
-hide putw \
-hide read \
-hide rmdir \
-hide rmtmp \
-hide setmode \
-hide spawnv \
-hide spawnve \
-hide spawnvp \
-hide spawnvpe \
-hide strcasecmp \
-hide strcmpi \
-hide strdup \
-hide stricmp \
-hide strlwr \
-hide strlwr_l \
-hide strncasecmp \
-hide strnicmp \
-hide strnset \
-hide strrev \
-hide strset \
-hide strtok_r \
-hide strupr \
-hide swab \
-hide tell \
-hide tempnam \
-hide tzset \
-hide ulltoa \
-hide ulltow \
-hide ultoa \
-hide umask \
-hide unlink \
-hide wcsdup \
-hide wcsicmp \
-hide wcsicoll \
-hide wcslwr \
-hide wcsnicmp \
-hide wcsnset \
-hide wcsrev \
-hide wcsset \
-hide wcsupr \
-hide wmemchr \
-hide wmemcmp \
-hide wmemcpy \
-hide wmemmove \
-hide wmempcpy \
-hide wmemset \
-hide write \
-hide wtoll \
-ignore-link-errors \
-import syscall \
-keep-strings \
-o $(UCRT_AMD64) \
libucrt.c \
|| true ) 2>&1 | tee -a log-generate
sed -i '/"modernc.org\/libc"/d' $(UCRT_AMD64)
sed -i 's/\<libc\>\.//g' $(UCRT_AMD64)
GOOS=windows GOARCH=amd64 go build -v ./... 2>&1 | tee -a log-generate
ucrt_arm64:
echo -n > log-generate
( ccgo -v4 \
--cpp=$(shell which x86_64-w64-mingw32-gcc) \
--goos=windows \
--goarch=amd64 \
--package-name libc \
--prefix-external=X \
--prefix-field=F \
--prefix-static-internal=_ \
--prefix-static-none=_ \
--prefix-tagged-struct=T \
--prefix-tagged-union=T \
--prefix-typename=T \
--winapi-test panic \
--winapi=ctype.h \
--winapi=float.h \
--winapi=io.h \
--winapi=libucrt.c \
--winapi=locale.h \
--winapi=malloc.h \
--winapi=math.h \
--winapi=process.h \
--winapi=types.h \
--winapi=stat.h \
--winapi=stdio.h \
--winapi=stdlib.h \
--winapi=string.h \
--winapi=time.h \
--winapi=timeb.h \
--winapi=wchar.h \
--winapi=winbase.h \
-build-lines=" " \
-eval-all-macros \
-hide __acrt_iob_func \
-hide _errno \
-hide _wgetenv \
-hide _wputenv \
-hide exit \
-hide lldiv \
-hide qsort \
-hide __sep__ \
-hide __create_locale \
-hide __free_locale \
-hide __get_current_locale \
-hide __iob_func \
-hide __lock_fhandle \
-hide __updatetlocinfo \
-hide __updatetmbcinfo \
-hide _beginthread \
-hide _beginthreadex \
-hide _endthreadex \
-hide _filbuf \
-hide _flsbuf \
-hide _get_amblksiz \
-hide _get_osplatform \
-hide _get_osver \
-hide _get_output_format \
-hide _get_sbh_threshold \
-hide _get_winmajor \
-hide _get_winminor \
-hide _get_winver \
-hide _heapadd \
-hide _heapset \
-hide _heapused \
-hide _matherr \
-hide _onexit \
-hide _set_amblksiz \
-hide _set_malloc_crt_max_wait \
-hide _set_output_format \
-hide _set_sbh_threshold \
-hide _strcmpi \
-hide _strnset_l \
-hide _strset_l \
-hide _unlock_fhandle \
-hide _wcsncpy_l \
-hide _wctime \
-hide _wctime_s \
-hide _wgetdcwd_nolock \
-hide access \
-hide at_quick_exit \
-hide atexit \
-hide chdir \
-hide chmod \
-hide chsize \
-hide close \
-hide creat \
-hide cwait \
-hide dup \
-hide dup2 \
-hide eof \
-hide execv \
-hide execve \
-hide execvp \
-hide execvpe \
-hide fcloseall \
-hide fdopen \
-hide fgetchar \
-hide fgetpos64 \
-hide filelength \
-hide fileno \
-hide flushall \
-hide fopen64 \
-hide fpreset \
-hide fputchar \
-hide fsetpos64 \
-hide ftime \
-hide fwide \
-hide getcwd \
-hide getpid \
-hide getw \
-hide isatty \
-hide itoa \
-hide lltoa \
-hide lltow \
-hide locking \
-hide lseek \
-hide lseek64 \
-hide ltoa \
-hide memccpy \
-hide memicmp \
-hide mempcpy \
-hide mkdir \
-hide mkstemp \
-hide mktemp \
-hide onexit \
-hide putenv \
-hide putw \
-hide read \
-hide rmdir \
-hide rmtmp \
-hide setmode \
-hide spawnv \
-hide spawnve \
-hide spawnvp \
-hide spawnvpe \
-hide strcasecmp \
-hide strcmpi \
-hide strdup \
-hide stricmp \
-hide strlwr \
-hide strlwr_l \
-hide strncasecmp \
-hide strnicmp \
-hide strnset \
-hide strrev \
-hide strset \
-hide strtok_r \
-hide strupr \
-hide swab \
-hide tell \
-hide tempnam \
-hide tzset \
-hide ulltoa \
-hide ulltow \
-hide ultoa \
-hide umask \
-hide unlink \
-hide wcsdup \
-hide wcsicmp \
-hide wcsicoll \
-hide wcslwr \
-hide wcsnicmp \
-hide wcsnset \
-hide wcsrev \
-hide wcsset \
-hide wcsupr \
-hide wmemchr \
-hide wmemcmp \
-hide wmemcpy \
-hide wmemmove \
-hide wmempcpy \
-hide wmemset \
-hide write \
-hide wtoll \
-ignore-link-errors \
-import syscall \
-keep-strings \
-o $(UCRT_ARM64) \
libucrt.c \
|| true ) 2>&1 | tee -a log-generate
sed -i '/"modernc.org\/libc"/d' $(UCRT_ARM64)
sed -i 's/\<libc\>\.//g' $(UCRT_ARM64)
GOOS=windows GOARCH=arm64 go build -v ./... 2>&1 | tee -a log-generate
ucrt_386:
echo -n > log-generate
( ccgo -v4 \
--cpp=$(shell which i686-w64-mingw32-gcc) \
--goos=windows \
--goarch=386 \
--package-name libc \
--prefix-external=X \
--prefix-field=F \
--prefix-static-internal=_ \
--prefix-static-none=_ \
--prefix-tagged-struct=T \
--prefix-tagged-union=T \
--prefix-typename=T \
--winapi-test panic \
--winapi=ctype.h \
--winapi=float.h \
--winapi=io.h \
--winapi=libucrt.c \
--winapi=locale.h \
--winapi=malloc.h \
--winapi=math.h \
--winapi=process.h \
--winapi=types.h \
--winapi=stat.h \
--winapi=stdio.h \
--winapi=stdlib.h \
--winapi=string.h \
--winapi=time.h \
--winapi=timeb.h \
--winapi=wchar.h \
--winapi=winbase.h \
-build-lines=" " \
-eval-all-macros \
-hide __acrt_iob_func \
-hide _errno \
-hide _wgetenv \
-hide _wputenv \
-hide exit \
-hide lldiv \
-hide qsort \
-hide __sep__ \
-hide __create_locale \
-hide __free_locale \
-hide __get_current_locale \
-hide __lock_fhandle \
-hide __updatetlocinfo \
-hide __updatetmbcinfo \
-hide _beginthread \
-hide _beginthreadex \
-hide _endthreadex \
-hide _filbuf \
-hide _flsbuf \
-hide _get_amblksiz \
-hide _get_osplatform \
-hide _get_osver \
-hide _get_output_format \
-hide _get_sbh_threshold \
-hide _get_winmajor \
-hide _get_winminor \
-hide _get_winver \
-hide _heapadd \
-hide _heapset \
-hide _heapused \
-hide _matherr \
-hide _onexit \
-hide _set_amblksiz \
-hide _set_malloc_crt_max_wait \
-hide _set_output_format \
-hide _set_sbh_threshold \
-hide _strcmpi \
-hide _strnset_l \
-hide _strset_l \
-hide _unlock_fhandle \
-hide _wcsncpy_l \
-hide _wctime \
-hide _wctime_s \
-hide _wgetdcwd_nolock \
-hide access \
-hide at_quick_exit \
-hide atexit \
-hide chdir \
-hide chmod \
-hide chsize \
-hide close \
-hide creat \
-hide cwait \
-hide dup \
-hide dup2 \
-hide eof \
-hide execv \
-hide execve \
-hide execvp \
-hide execvpe \
-hide fcloseall \
-hide fdopen \
-hide fgetchar \
-hide fgetpos64 \
-hide filelength \
-hide fileno \
-hide flushall \
-hide fopen64 \
-hide fpreset \
-hide fputchar \
-hide fsetpos64 \
-hide ftime \
-hide fwide \
-hide getcwd \
-hide getpid \
-hide getw \
-hide isatty \
-hide itoa \
-hide lltoa \
-hide lltow \
-hide locking \
-hide lseek \
-hide lseek64 \
-hide ltoa \
-hide memccpy \
-hide memicmp \
-hide mempcpy \
-hide mkdir \
-hide mkstemp \
-hide mktemp \
-hide onexit \
-hide putenv \
-hide putw \
-hide read \
-hide rmdir \
-hide rmtmp \
-hide setmode \
-hide spawnv \
-hide spawnve \
-hide spawnvp \
-hide spawnvpe \
-hide strcasecmp \
-hide strcmpi \
-hide strdup \
-hide stricmp \
-hide strlwr \
-hide strlwr_l \
-hide strncasecmp \
-hide strnicmp \
-hide strnset \
-hide strrev \
-hide strset \
-hide strtok_r \
-hide strupr \
-hide swab \
-hide tell \
-hide tempnam \
-hide tzset \
-hide ulltoa \
-hide ulltow \
-hide ultoa \
-hide umask \
-hide unlink \
-hide wcsdup \
-hide wcsicmp \
-hide wcsicoll \
-hide wcslwr \
-hide wcsnicmp \
-hide wcsnset \
-hide wcsrev \
-hide wcsset \
-hide wcsupr \
-hide wmemchr \
-hide wmemcmp \
-hide wmemcpy \
-hide wmemmove \
-hide wmempcpy \
-hide wmemset \
-hide write \
-hide wtoll \
-ignore-link-errors \
-import syscall \
-keep-strings \
-o $(UCRT_386) \
libucrt.c \
|| true ) 2>&1 | tee -a log-generate
sed -i '/"modernc.org\/libc"/d' $(UCRT_386)
sed -i 's/\<libc\>\.//g' $(UCRT_386)
GOOS=windows GOARCH=386 go build -v ./... 2>&1 | tee -a log-generate
generate: download
mkdir -p $(DIR) || true
rm -rf $(DIR)/*

95
vendor/modernc.org/libc/all.go generated vendored
View file

@ -1,95 +0,0 @@
// Copyright 202 The Libc 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 libc // import "modernc.org/libc"
import (
"fmt"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"unsafe"
)
const (
heapAlign = 16
heapGuard = 16
)
const FALSE = 0
const TRUE = 1
var (
pid = fmt.Sprintf("[%v %v] ", os.Getpid(), filepath.Base(os.Args[0]))
)
func origin(skip int) string {
pc, fn, fl, _ := runtime.Caller(skip)
f := runtime.FuncForPC(pc)
var fns string
if f != nil {
fns = f.Name()
if x := strings.LastIndex(fns, "."); x > 0 {
fns = fns[x+1:]
}
}
return fmt.Sprintf("%s:%d:%s", filepath.Base(fn), fl, fns)
}
func trc(s string, args ...interface{}) string { //TODO-
switch {
case s == "":
s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
default:
s = fmt.Sprintf(s, args...)
}
r := fmt.Sprintf("%s: TRC %s (%v:)", origin(2), s, origin(3))
fmt.Fprintf(os.Stdout, "%s %s\n", pid, r)
os.Stdout.Sync()
return r
}
func todo(s string, args ...interface{}) string { //TODO-
switch {
case s == "":
s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
default:
s = fmt.Sprintf(s, args...)
}
r := fmt.Sprintf("%s %s: TODOTODO %s (%v:)", pid, origin(2), s, origin(3)) //TODOOK
if dmesgs {
dmesg("%s", r)
}
fmt.Fprintf(os.Stdout, "%s\n", r)
fmt.Fprintf(os.Stdout, "%s\n", debug.Stack()) //TODO-
os.Stdout.Sync()
os.Exit(1)
panic("unrechable")
}
type sorter struct {
len int
base uintptr
sz uintptr
f func(*TLS, uintptr, uintptr) int32
t *TLS
}
func (s *sorter) Len() int { return s.len }
func (s *sorter) Less(i, j int) bool {
return s.f(s.t, s.base+uintptr(i)*s.sz, s.base+uintptr(j)*s.sz) < 0
}
func (s *sorter) Swap(i, j int) {
p := s.base + uintptr(i)*s.sz
q := s.base + uintptr(j)*s.sz
for i := 0; i < int(s.sz); i++ {
*(*byte)(unsafe.Pointer(p)), *(*byte)(unsafe.Pointer(q)) = *(*byte)(unsafe.Pointer(q)), *(*byte)(unsafe.Pointer(p))
p++
q++
}
}

View file

@ -1,6 +1,6 @@
{
"autogen": "linux/(amd64|arm64|loong64)",
"autoupdate": "",
"autoupdate": "linux/amd64",
"autotag": "darwin/(amd64|arm64)|freebsd/(amd64|arm64)|linux/(386|amd64|arm|arm64|loong64|ppc64le|riscv64|s390x)|openbsd/(386|amd64|arm64)|windows/(amd64|arm64|386)",
"download": [
{"re": "linux/(amd64|arm64|loong64)", "files": ["https://git.musl-libc.org/cgit/musl/snapshot/musl-7ada6dde6f9dc6a2836c3d92c2f762d35fd229e0.tar.gz"]}

View file

@ -143,16 +143,14 @@ const WINT_MAX = 4294967295
const WINT_MIN = 0
const _ABILP64 = 3
const _Complex_I = 0
const _LOONGARCH_ARCH = "loongarch64"
const _LOONGARCH_ARCH_LOONGARCH64 = 1
const _LOONGARCH_ARCH = "la64v1.0"
const _LOONGARCH_FPSET = 32
const _LOONGARCH_SIM = 3
const _LOONGARCH_SPFPSET = 32
const _LOONGARCH_SZINT = 32
const _LOONGARCH_SZLONG = 64
const _LOONGARCH_SZPTR = 64
const _LOONGARCH_TUNE = "loongarch64"
const _LOONGARCH_TUNE_LOONGARCH64 = 1
const _LOONGARCH_TUNE = "generic"
const _LP64 = 1
const _XOPEN_SOURCE = 700
const __ACCUM_EPSILON__ = 0
@ -322,9 +320,8 @@ const __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 = 1
const __GCC_IEC_559 = 2
const __GCC_IEC_559_COMPLEX = 2
const __GNUC_EXECUTION_CHARSET_NAME = "UTF-8"
const __GNUC_MINOR__ = 0
const __GNUC_PATCHLEVEL__ = 1
const __GNUC_RH_RELEASE__ = 0
const __GNUC_MINOR__ = 1
const __GNUC_PATCHLEVEL__ = 0
const __GNUC_STDC_INLINE__ = 1
const __GNUC_WIDE_EXECUTION_CHARSET_NAME = "UTF-32LE"
const __GNUC__ = 14
@ -404,8 +401,6 @@ const __LONG_LONG_WIDTH__ = 64
const __LONG_MAX = 9223372036854775807
const __LONG_MAX__ = 9223372036854775807
const __LONG_WIDTH__ = 64
const __LOONGARCH_ARCH_LOONGARCH64 = 1
const __LOONGARCH_TUNE_LOONGARCH64 = 1
const __LP64__ = 1
const __NO_INLINE__ = 1
const __ORDER_BIG_ENDIAN__ = 4321
@ -537,7 +532,7 @@ const __UTA_FBIT__ = 64
const __UTA_IBIT__ = 64
const __UTQ_FBIT__ = 128
const __UTQ_IBIT__ = 0
const __VERSION__ = "14.0.1 20240411 (Red Hat 14.0.1-0)"
const __VERSION__ = "14.1.0 20240507 (Red Hat 14.1.0-1)"
const __WCHAR_MAX__ = 2147483647
const __WCHAR_MIN__ = -2147483648
const __WCHAR_TYPE__ = 0
@ -557,13 +552,18 @@ const __linux = 1
const __linux__ = 1
const __loongarch64 = 1
const __loongarch__ = 1
const __loongarch_arch = "loongarch64"
const __loongarch_arch = "la64v1.0"
const __loongarch_double_float = 1
const __loongarch_frlen = 64
const __loongarch_grlen = 64
const __loongarch_hard_float = 1
const __loongarch_lp64 = 1
const __loongarch_tune = "loongarch64"
const __loongarch_simd = 1
const __loongarch_simd_width = 128
const __loongarch_sx = 1
const __loongarch_tune = "generic"
const __loongarch_version_major = 1
const __loongarch_version_minor = 0
const __restrict = 0
const __restrict_arr = 0
const __unix = 1

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