Move remaining utilities

This commit is contained in:
Tulir Asokan 2023-08-05 19:24:45 +03:00
commit fd1cdd8c94
35 changed files with 18 additions and 1687 deletions

View file

@ -21,8 +21,10 @@ import (
"github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"go.mau.fi/util/configupgrade"
"go.mau.fi/util/dbutil"
_ "go.mau.fi/util/dbutil/litestream"
"go.mau.fi/util/exzerolog"
"gopkg.in/yaml.v3"
flag "maunium.net/go/mauflag"
"maunium.net/go/maulogger/v2"
@ -35,8 +37,6 @@ import (
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/sqlstatestore"
"maunium.net/go/mautrix/util"
"maunium.net/go/mautrix/util/configupgrade"
)
var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String()
@ -498,7 +498,7 @@ func (br *Bridge) init() {
}
defaultCtxLog := br.ZLog.With().Bool("default_context_log", true).Caller().Logger()
zerolog.TimeFieldFormat = time.RFC3339Nano
zerolog.CallerMarshalFunc = util.CallerWithFunctionName
zerolog.CallerMarshalFunc = exzerolog.CallerWithFunctionName
zerolog.DefaultContextLogger = &defaultCtxLog
br.Log = maulogadapt.ZeroAsMau(br.ZLog)

View file

@ -14,6 +14,7 @@ import (
"strings"
"github.com/rs/zerolog"
up "go.mau.fi/util/configupgrade"
"go.mau.fi/util/dbutil"
"go.mau.fi/util/random"
"go.mau.fi/zeroconfig"
@ -21,7 +22,6 @@ import (
"maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/id"
up "maunium.net/go/mautrix/util/configupgrade"
)
type HomeserverSoftware string

View file

@ -17,11 +17,11 @@ import (
"time"
"github.com/tidwall/sjson"
"go.mau.fi/util/jsontime"
"golang.org/x/exp/maps"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/util/jsontime"
)
type BridgeStateEvent string

View file

@ -14,10 +14,11 @@ import (
"net/http"
"time"
"go.mau.fi/util/jsontime"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/util/jsontime"
)
type MessageCheckpointStep string

View file

@ -7,8 +7,9 @@ import (
"sync"
"time"
"go.mau.fi/util/jsontime"
"maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/util/jsontime"
)
const defaultReconnectBackoff = 2 * time.Second

View file

@ -11,10 +11,10 @@ import (
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"go.mau.fi/util/exgjson"
"maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/util"
)
// Utility stores the necessary state to perform hash and signature
@ -115,7 +115,7 @@ func (u *Utility) VerifySignatureJSON(obj interface{}, userID id.UserID, keyName
return false, err
}
}
sig := gjson.GetBytes(objJSON, util.GJSONPath("signatures", string(userID), fmt.Sprintf("ed25519:%s", keyName)))
sig := gjson.GetBytes(objJSON, exgjson.Path("signatures", string(userID), fmt.Sprintf("ed25519:%s", keyName)))
if !sig.Exists() || sig.Type != gjson.String {
return false, SignatureNotFound
}

View file

@ -16,10 +16,9 @@ import (
"math/rand"
"strings"
"go.mau.fi/util/base58"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/pbkdf2"
"maunium.net/go/mautrix/util/base58"
)
const (

2
go.mod
View file

@ -12,7 +12,7 @@ require (
github.com/tidwall/gjson v1.14.4
github.com/tidwall/sjson v1.2.5
github.com/yuin/goldmark v1.5.4
go.mau.fi/util v0.0.0-20230805154123-3981666a86f1
go.mau.fi/util v0.0.0-20230805161919-cf42c11d39c3
go.mau.fi/zeroconfig v0.1.2
golang.org/x/crypto v0.11.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1

4
go.sum
View file

@ -35,8 +35,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mau.fi/util v0.0.0-20230805154123-3981666a86f1 h1:oZGQm7OArpQDISW7f6qu64nw+GMXiiXEhONy7Ab6RKI=
go.mau.fi/util v0.0.0-20230805154123-3981666a86f1/go.mod h1:tNxQ2KpD+QhP2MlMfJvFSGSJfDjg4OhIwP7bIK43X/I=
go.mau.fi/util v0.0.0-20230805161919-cf42c11d39c3 h1:r3Hrayw0CfmkrDhse7SbClYzq0e7/5P3iKpyV+gW16w=
go.mau.fi/util v0.0.0-20230805161919-cf42c11d39c3/go.mod h1:tNxQ2KpD+QhP2MlMfJvFSGSJfDjg4OhIwP7bIK43X/I=
go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=

View file

@ -8,11 +8,11 @@ import (
"strings"
"github.com/tidwall/gjson"
"go.mau.fi/util/jsontime"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/util"
"maunium.net/go/mautrix/util/jsontime"
)
// RespWhoami is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3accountwhoami

View file

@ -11,9 +11,10 @@ import (
"fmt"
"net/http"
"go.mau.fi/util/jsontime"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/util/jsontime"
)
// ReqResetPassword is the request content for Client.ResetPassword.

View file

@ -1,9 +0,0 @@
base58
==========
This is a copy of <https://github.com/btcsuite/btcd/tree/master/btcutil/base58>.
## License
Package base58 is licensed under the [copyfree](http://copyfree.org) ISC
License.

View file

@ -1,49 +0,0 @@
// Copyright (c) 2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// AUTOGENERATED by genalphabet.go; do not edit.
package base58
const (
// alphabet is the modified base58 alphabet used by Bitcoin.
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
alphabetIdx0 = '1'
)
var b58 = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 0, 1, 2, 3, 4, 5, 6,
7, 8, 255, 255, 255, 255, 255, 255,
255, 9, 10, 11, 12, 13, 14, 15,
16, 255, 17, 18, 19, 20, 21, 255,
22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 255, 255, 255, 255, 255,
255, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 255, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
}

View file

@ -1,138 +0,0 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58
import (
"math/big"
)
//go:generate go run genalphabet.go
var bigRadix = [...]*big.Int{
big.NewInt(0),
big.NewInt(58),
big.NewInt(58 * 58),
big.NewInt(58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
bigRadix10,
}
var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10
// Decode decodes a modified base58 string to a byte slice.
func Decode(b string) []byte {
answer := big.NewInt(0)
scratch := new(big.Int)
// Calculating with big.Int is slow for each iteration.
// x += b58[b[i]] * j
// j *= 58
//
// Instead we can try to do as much calculations on int64.
// We can represent a 10 digit base58 number using an int64.
//
// Hence we'll try to convert 10, base58 digits at a time.
// The rough idea is to calculate `t`, such that:
//
// t := b58[b[i+9]] * 58^9 ... + b58[b[i+1]] * 58^1 + b58[b[i]] * 58^0
// x *= 58^10
// x += t
//
// Of course, in addition, we'll need to handle boundary condition when `b` is not multiple of 58^10.
// In that case we'll use the bigRadix[n] lookup for the appropriate power.
for t := b; len(t) > 0; {
n := len(t)
if n > 10 {
n = 10
}
total := uint64(0)
for _, v := range t[:n] {
tmp := b58[v]
if tmp == 255 {
return []byte("")
}
total = total*58 + uint64(tmp)
}
answer.Mul(answer, bigRadix[n])
scratch.SetUint64(total)
answer.Add(answer, scratch)
t = t[n:]
}
tmpval := answer.Bytes()
var numZeros int
for numZeros = 0; numZeros < len(b); numZeros++ {
if b[numZeros] != alphabetIdx0 {
break
}
}
flen := numZeros + len(tmpval)
val := make([]byte, flen)
copy(val[numZeros:], tmpval)
return val
}
// Encode encodes a byte slice to a modified base58 string.
func Encode(b []byte) string {
x := new(big.Int)
x.SetBytes(b)
// maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58)
maxlen := int(float64(len(b))*1.365658237309761) + 1
answer := make([]byte, 0, maxlen)
mod := new(big.Int)
for x.Sign() > 0 {
// Calculating with big.Int is slow for each iteration.
// x, mod = x / 58, x % 58
//
// Instead we can try to do as much calculations on int64.
// x, mod = x / 58^10, x % 58^10
//
// Which will give us mod, which is 10 digit base58 number.
// We'll loop that 10 times to convert to the answer.
x.DivMod(x, bigRadix10, mod)
if x.Sign() == 0 {
// When x = 0, we need to ensure we don't add any extra zeros.
m := mod.Int64()
for m > 0 {
answer = append(answer, alphabet[m%58])
m /= 58
}
} else {
m := mod.Int64()
for i := 0; i < 10; i++ {
answer = append(answer, alphabet[m%58])
m /= 58
}
}
}
// leading zero bytes
for _, i := range b {
if i != 0 {
break
}
answer = append(answer, alphabetIdx0)
}
// reverse
alen := len(answer)
for i := 0; i < alen/2; i++ {
answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
}
return string(answer)
}

View file

@ -1,101 +0,0 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58_test
import (
"bytes"
"encoding/hex"
"testing"
"maunium.net/go/mautrix/util/base58"
)
var stringTests = []struct {
in string
out string
}{
{"", ""},
{" ", "Z"},
{"-", "n"},
{"0", "q"},
{"1", "r"},
{"-1", "4SU"},
{"11", "4k8"},
{"abc", "ZiCa"},
{"1234598760", "3mJr7AoUXx2Wqd"},
{"abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"},
{"00000000000000000000000000000000000000000000000000000000000000", "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y"},
}
var invalidStringTests = []struct {
in string
out string
}{
{"0", ""},
{"O", ""},
{"I", ""},
{"l", ""},
{"3mJr0", ""},
{"O3yxU", ""},
{"3sNI", ""},
{"4kl8", ""},
{"0OIl", ""},
{"!@#$%^&*()-_=+~`", ""},
}
var hexTests = []struct {
in string
out string
}{
{"", ""},
{"61", "2g"},
{"626262", "a3gV"},
{"636363", "aPEr"},
{"73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"},
{"00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"},
{"516b6fcd0f", "ABnLTmg"},
{"bf4f89001e670274dd", "3SEo3LWLoPntC"},
{"572e4794", "3EFU7m"},
{"ecac89cad93923c02321", "EJDM8drfXA6uyA"},
{"10c8511e", "Rt5zm"},
{"00000000000000000000", "1111111111"},
{"000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"},
{"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"},
}
func TestBase58(t *testing.T) {
// Encode tests
for x, test := range stringTests {
tmp := []byte(test.in)
if res := base58.Encode(tmp); res != test.out {
t.Errorf("Encode test #%d failed: got: %s want: %s",
x, res, test.out)
continue
}
}
// Decode tests
for x, test := range hexTests {
b, err := hex.DecodeString(test.in)
if err != nil {
t.Errorf("hex.DecodeString failed failed #%d: got: %s", x, test.in)
continue
}
if res := base58.Decode(test.out); !bytes.Equal(res, b) {
t.Errorf("Decode test #%d failed: got: %q want: %q",
x, res, test.in)
continue
}
}
// Decode with invalid input
for x, test := range invalidStringTests {
if res := base58.Decode(test.in); string(res) != test.out {
t.Errorf("Decode invalidString test #%d failed: got: %q want: %q",
x, res, test.out)
continue
}
}
}

View file

@ -1,47 +0,0 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58_test
import (
"bytes"
"testing"
"maunium.net/go/mautrix/util/base58"
)
var (
raw5k = bytes.Repeat([]byte{0xff}, 5000)
raw100k = bytes.Repeat([]byte{0xff}, 100*1000)
encoded5k = base58.Encode(raw5k)
encoded100k = base58.Encode(raw100k)
)
func BenchmarkBase58Encode_5K(b *testing.B) {
b.SetBytes(int64(len(raw5k)))
for i := 0; i < b.N; i++ {
base58.Encode(raw5k)
}
}
func BenchmarkBase58Encode_100K(b *testing.B) {
b.SetBytes(int64(len(raw100k)))
for i := 0; i < b.N; i++ {
base58.Encode(raw100k)
}
}
func BenchmarkBase58Decode_5K(b *testing.B) {
b.SetBytes(int64(len(encoded5k)))
for i := 0; i < b.N; i++ {
base58.Decode(encoded5k)
}
}
func BenchmarkBase58Decode_100K(b *testing.B) {
b.SetBytes(int64(len(encoded100k)))
for i := 0; i < b.N; i++ {
base58.Decode(encoded100k)
}
}

View file

@ -1,52 +0,0 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58
import (
"crypto/sha256"
"errors"
)
// ErrChecksum indicates that the checksum of a check-encoded string does not verify against
// the checksum.
var ErrChecksum = errors.New("checksum error")
// ErrInvalidFormat indicates that the check-encoded string has an invalid format.
var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")
// checksum: first four bytes of sha256^2
func checksum(input []byte) (cksum [4]byte) {
h := sha256.Sum256(input)
h2 := sha256.Sum256(h[:])
copy(cksum[:], h2[:4])
return
}
// CheckEncode prepends a version byte and appends a four byte checksum.
func CheckEncode(input []byte, version byte) string {
b := make([]byte, 0, 1+len(input)+4)
b = append(b, version)
b = append(b, input...)
cksum := checksum(b)
b = append(b, cksum[:]...)
return Encode(b)
}
// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
func CheckDecode(input string) (result []byte, version byte, err error) {
decoded := Decode(input)
if len(decoded) < 5 {
return nil, 0, ErrInvalidFormat
}
version = decoded[0]
var cksum [4]byte
copy(cksum[:], decoded[len(decoded)-4:])
if checksum(decoded[:len(decoded)-4]) != cksum {
return nil, 0, ErrChecksum
}
payload := decoded[1 : len(decoded)-4]
result = append(result, payload...)
return
}

View file

@ -1,69 +0,0 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58_test
import (
"testing"
"maunium.net/go/mautrix/util/base58"
)
var checkEncodingStringTests = []struct {
version byte
in string
out string
}{
{20, "", "3MNQE1X"},
{20, " ", "B2Kr6dBE"},
{20, "-", "B3jv1Aft"},
{20, "0", "B482yuaX"},
{20, "1", "B4CmeGAC"},
{20, "-1", "mM7eUf6kB"},
{20, "11", "mP7BMTDVH"},
{20, "abc", "4QiVtDjUdeq"},
{20, "1234598760", "ZmNb8uQn5zvnUohNCEPP"},
{20, "abcdefghijklmnopqrstuvwxyz", "K2RYDcKfupxwXdWhSAxQPCeiULntKm63UXyx5MvEH2"},
{20, "00000000000000000000000000000000000000000000000000000000000000", "bi1EWXwJay2udZVxLJozuTb8Meg4W9c6xnmJaRDjg6pri5MBAxb9XwrpQXbtnqEoRV5U2pixnFfwyXC8tRAVC8XxnjK"},
}
func TestBase58Check(t *testing.T) {
for x, test := range checkEncodingStringTests {
// test encoding
if res := base58.CheckEncode([]byte(test.in), test.version); res != test.out {
t.Errorf("CheckEncode test #%d failed: got %s, want: %s", x, res, test.out)
}
// test decoding
res, version, err := base58.CheckDecode(test.out)
switch {
case err != nil:
t.Errorf("CheckDecode test #%d failed with err: %v", x, err)
case version != test.version:
t.Errorf("CheckDecode test #%d failed: got version: %d want: %d", x, version, test.version)
case string(res) != test.in:
t.Errorf("CheckDecode test #%d failed: got: %s want: %s", x, res, test.in)
}
}
// test the two decoding failure cases
// case 1: checksum error
_, _, err := base58.CheckDecode("3MNQE1Y")
if err != base58.ErrChecksum {
t.Error("Checkdecode test failed, expected ErrChecksum")
}
// case 2: invalid formats (string lengths below 5 mean the version byte and/or the checksum
// bytes are missing).
testString := ""
for len := 0; len < 4; len++ {
testString += "x"
_, _, err = base58.CheckDecode(testString)
if err != base58.ErrInvalidFormat {
t.Error("Checkdecode test failed, expected ErrInvalidFormat")
}
}
}

View file

@ -1,29 +0,0 @@
// Copyright (c) 2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package base58 provides an API for working with modified base58 and Base58Check
encodings.
# Modified Base58 Encoding
Standard base58 encoding is similar to standard base64 encoding except, as the
name implies, it uses a 58 character alphabet which results in an alphanumeric
string and allows some characters which are problematic for humans to be
excluded. Due to this, there can be various base58 alphabets.
The modified base58 alphabet used by Bitcoin, and hence this package, omits the
0, O, I, and l characters that look the same in many fonts and are therefore
hard to humans to distinguish.
# Base58Check Encoding Scheme
The Base58Check encoding scheme is primarily used for Bitcoin addresses at the
time of this writing, however it can be used to generically encode arbitrary
byte arrays into human-readable strings along with a version byte that can be
used to differentiate the same payload. For Bitcoin addresses, the extra
version is used to differentiate the network of otherwise identical public keys
which helps prevent using an address intended for one network on another.
*/
package base58

View file

@ -1,71 +0,0 @@
// Copyright (c) 2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58_test
import (
"fmt"
"maunium.net/go/mautrix/util/base58"
)
// This example demonstrates how to decode modified base58 encoded data.
func ExampleDecode() {
// Decode example modified base58 encoded data.
encoded := "25JnwSn7XKfNQ"
decoded := base58.Decode(encoded)
// Show the decoded data.
fmt.Println("Decoded Data:", string(decoded))
// Output:
// Decoded Data: Test data
}
// This example demonstrates how to encode data using the modified base58
// encoding scheme.
func ExampleEncode() {
// Encode example data with the modified base58 encoding scheme.
data := []byte("Test data")
encoded := base58.Encode(data)
// Show the encoded data.
fmt.Println("Encoded Data:", encoded)
// Output:
// Encoded Data: 25JnwSn7XKfNQ
}
// This example demonstrates how to decode Base58Check encoded data.
func ExampleCheckDecode() {
// Decode an example Base58Check encoded data.
encoded := "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
decoded, version, err := base58.CheckDecode(encoded)
if err != nil {
fmt.Println(err)
return
}
// Show the decoded data.
fmt.Printf("Decoded data: %x\n", decoded)
fmt.Println("Version Byte:", version)
// Output:
// Decoded data: 62e907b15cbf27d5425399ebf6f0fb50ebb88f18
// Version Byte: 0
}
// This example demonstrates how to encode data using the Base58Check encoding
// scheme.
func ExampleCheckEncode() {
// Encode example data with the Base58Check encoding scheme.
data := []byte("Test data")
encoded := base58.CheckEncode(data, 0)
// Show the encoded data.
fmt.Println("Encoded Data:", encoded)
// Output:
// Encoded Data: 182iP79GRURMp7oMHDU
}

View file

@ -1,28 +0,0 @@
// 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 util
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 = util.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

@ -1,288 +0,0 @@
// Copyright (c) 2022 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 configupgrade
import (
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
)
type YAMLMap map[string]YAMLNode
type YAMLList []YAMLNode
type YAMLNode struct {
*yaml.Node
Map YAMLMap
List YAMLList
Key *yaml.Node
}
type YAMLType uint32
const (
Null YAMLType = 1 << iota
Bool
Str
Int
Float
Timestamp
List
Map
Binary
)
func (t YAMLType) String() string {
switch t {
case Null:
return NullTag
case Bool:
return BoolTag
case Str:
return StrTag
case Int:
return IntTag
case Float:
return FloatTag
case Timestamp:
return TimestampTag
case List:
return SeqTag
case Map:
return MapTag
case Binary:
return BinaryTag
default:
panic(fmt.Errorf("can't convert type %d to string", t))
}
}
func tagToType(tag string) YAMLType {
switch tag {
case NullTag:
return Null
case BoolTag:
return Bool
case StrTag:
return Str
case IntTag:
return Int
case FloatTag:
return Float
case TimestampTag:
return Timestamp
case SeqTag:
return List
case MapTag:
return Map
case BinaryTag:
return Binary
default:
return 0
}
}
const (
NullTag = "!!null"
BoolTag = "!!bool"
StrTag = "!!str"
IntTag = "!!int"
FloatTag = "!!float"
TimestampTag = "!!timestamp"
SeqTag = "!!seq"
MapTag = "!!map"
BinaryTag = "!!binary"
)
func fromNode(node, key *yaml.Node) YAMLNode {
switch node.Kind {
case yaml.DocumentNode:
return fromNode(node.Content[0], nil)
case yaml.AliasNode:
return fromNode(node.Alias, nil)
case yaml.MappingNode:
return YAMLNode{
Node: node,
Map: parseYAMLMap(node),
Key: key,
}
case yaml.SequenceNode:
return YAMLNode{
Node: node,
List: parseYAMLList(node),
}
default:
return YAMLNode{Node: node, Key: key}
}
}
func (yn *YAMLNode) toNode() *yaml.Node {
yn.UpdateContent()
return yn.Node
}
func (yn *YAMLNode) UpdateContent() {
switch {
case yn.Map != nil && yn.Node.Kind == yaml.MappingNode:
yn.Content = yn.Map.toNodes()
case yn.List != nil && yn.Node.Kind == yaml.SequenceNode:
yn.Content = yn.List.toNodes()
}
}
func parseYAMLList(node *yaml.Node) YAMLList {
data := make(YAMLList, len(node.Content))
for i, item := range node.Content {
data[i] = fromNode(item, nil)
}
return data
}
func (yl YAMLList) toNodes() []*yaml.Node {
nodes := make([]*yaml.Node, len(yl))
for i, item := range yl {
nodes[i] = item.toNode()
}
return nodes
}
func parseYAMLMap(node *yaml.Node) YAMLMap {
if len(node.Content)%2 != 0 {
panic(fmt.Errorf("uneven number of items in YAML map (%d)", len(node.Content)))
}
data := make(YAMLMap, len(node.Content)/2)
for i := 0; i < len(node.Content); i += 2 {
key := node.Content[i]
value := node.Content[i+1]
if key.Kind == yaml.ScalarNode {
data[key.Value] = fromNode(value, key)
}
}
return data
}
func (ym YAMLMap) toNodes() []*yaml.Node {
nodes := make([]*yaml.Node, len(ym)*2)
i := 0
for key, value := range ym {
nodes[i] = makeStringNode(key)
nodes[i+1] = value.toNode()
i += 2
}
return nodes
}
func makeStringNode(val string) *yaml.Node {
var node yaml.Node
node.SetString(val)
return &node
}
func StringNode(val string) YAMLNode {
return YAMLNode{Node: makeStringNode(val)}
}
type Helper struct {
Base YAMLNode
Config YAMLNode
}
func NewHelper(base, cfg *yaml.Node) *Helper {
return &Helper{
Base: fromNode(base, nil),
Config: fromNode(cfg, nil),
}
}
func (helper *Helper) AddSpaceBeforeComment(path ...string) {
node := helper.GetBaseNode(path...)
if node == nil || node.Key == nil {
panic(fmt.Errorf("didn't find key at %+v", path))
}
node.Key.HeadComment = "\n" + node.Key.HeadComment
}
func (helper *Helper) Copy(allowedTypes YAMLType, path ...string) {
base, cfg := helper.Base, helper.Config
var ok bool
for _, item := range path {
cfg, ok = cfg.Map[item]
if !ok {
return
}
base, ok = base.Map[item]
if !ok {
_, _ = fmt.Fprintf(os.Stderr, "Ignoring config field %s which is missing in base config\n", strings.Join(path, "->"))
return
}
}
if allowedTypes&tagToType(cfg.Tag) == 0 {
_, _ = fmt.Fprintf(os.Stderr, "Ignoring incorrect config field type %s at %s\n", cfg.Tag, strings.Join(path, "->"))
return
}
base.Tag = cfg.Tag
base.Style = cfg.Style
switch base.Kind {
case yaml.ScalarNode:
base.Value = cfg.Value
case yaml.SequenceNode, yaml.MappingNode:
base.Content = cfg.Content
}
}
func getNode(cfg YAMLNode, path []string) *YAMLNode {
var ok bool
for _, item := range path {
cfg, ok = cfg.Map[item]
if !ok {
return nil
}
}
return &cfg
}
func (helper *Helper) GetNode(path ...string) *YAMLNode {
return getNode(helper.Config, path)
}
func (helper *Helper) GetBaseNode(path ...string) *YAMLNode {
return getNode(helper.Base, path)
}
func (helper *Helper) Get(tag YAMLType, path ...string) (string, bool) {
node := helper.GetNode(path...)
if node == nil || node.Kind != yaml.ScalarNode || tag&tagToType(node.Tag) == 0 {
return "", false
}
return node.Value, true
}
func (helper *Helper) GetBase(path ...string) string {
return helper.GetBaseNode(path...).Value
}
func (helper *Helper) Set(tag YAMLType, value string, path ...string) {
base := helper.Base
for _, item := range path {
base = base.Map[item]
}
base.Tag = tag.String()
base.Value = value
}
func (helper *Helper) SetMap(value YAMLMap, path ...string) {
base := helper.Base
for _, item := range path {
base = base.Map[item]
}
if base.Tag != MapTag || base.Kind != yaml.MappingNode {
panic(fmt.Errorf("invalid target for SetMap(%+v): tag:%s, kind:%d", path, base.Tag, base.Kind))
}
base.Content = value.toNodes()
}

View file

@ -1,108 +0,0 @@
// Copyright (c) 2022 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 configupgrade
import (
"fmt"
"os"
"path"
"gopkg.in/yaml.v3"
)
type Upgrader interface {
DoUpgrade(helper *Helper)
}
type SpacedUpgrader interface {
Upgrader
SpacedBlocks() [][]string
}
type BaseUpgrader interface {
Upgrader
GetBase() string
}
type StructUpgrader struct {
SimpleUpgrader
Blocks [][]string
Base string
}
func (su *StructUpgrader) SpacedBlocks() [][]string {
return su.Blocks
}
func (su *StructUpgrader) GetBase() string {
return su.Base
}
type SimpleUpgrader func(helper *Helper)
func (su SimpleUpgrader) DoUpgrade(helper *Helper) {
su(helper)
}
func (helper *Helper) apply(upgrader Upgrader) {
upgrader.DoUpgrade(helper)
helper.addSpaces(upgrader)
}
func (helper *Helper) addSpaces(upgrader Upgrader) {
spaced, ok := upgrader.(SpacedUpgrader)
if ok {
for _, spacePath := range spaced.SpacedBlocks() {
helper.AddSpaceBeforeComment(spacePath...)
}
}
}
func Do(configPath string, save bool, upgrader BaseUpgrader, additional ...Upgrader) ([]byte, bool, error) {
sourceData, err := os.ReadFile(configPath)
if err != nil {
return nil, false, fmt.Errorf("failed to read config: %w", err)
}
var base, cfg yaml.Node
err = yaml.Unmarshal([]byte(upgrader.GetBase()), &base)
if err != nil {
return sourceData, false, fmt.Errorf("failed to unmarshal example config: %w", err)
}
err = yaml.Unmarshal(sourceData, &cfg)
if err != nil {
return sourceData, false, fmt.Errorf("failed to unmarshal config: %w", err)
}
helper := NewHelper(&base, &cfg)
helper.apply(upgrader)
for _, add := range additional {
helper.apply(add)
}
output, err := yaml.Marshal(&base)
if err != nil {
return sourceData, false, fmt.Errorf("failed to marshal updated config: %w", err)
}
if save {
var tempFile *os.File
tempFile, err = os.CreateTemp(path.Dir(configPath), "mautrix-config-*.yaml")
if err != nil {
return output, true, fmt.Errorf("failed to create temp file for writing config: %w", err)
}
_, err = tempFile.Write(output)
if err != nil {
_ = os.Remove(tempFile.Name())
return output, true, fmt.Errorf("failed to write updated config to temp file: %w", err)
}
err = os.Rename(tempFile.Name(), configPath)
if err != nil {
_ = os.Remove(tempFile.Name())
return output, true, fmt.Errorf("failed to override current config with temp file: %w", err)
}
}
return output, true, nil
}

View file

@ -1,54 +0,0 @@
// 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 util
import (
"errors"
"fmt"
"strings"
"time"
)
var Day = 24 * time.Hour
var Week = 7 * Day
func pluralize(value int, unit string) string {
if value == 1 {
return "1 " + unit
}
return fmt.Sprintf("%d %ss", value, unit)
}
func appendDurationPart(time, unit time.Duration, name string, parts *[]string) (remainder time.Duration) {
if time < unit {
return time
}
value := int(time / unit)
remainder = time % unit
*parts = append(*parts, pluralize(value, name))
return
}
func FormatDuration(d time.Duration) string {
if d < 0 {
panic(errors.New("FormatDuration: negative duration"))
} else if d < time.Second {
return "now"
}
parts := make([]string, 0, 2)
d = appendDurationPart(d, Week, "week", &parts)
d = appendDurationPart(d, Day, "day", &parts)
d = appendDurationPart(d, time.Hour, "hour", &parts)
d = appendDurationPart(d, time.Minute, "minute", &parts)
d = appendDurationPart(d, time.Second, "second", &parts)
if len(parts) > 2 {
parts[0] = strings.Join(parts[:len(parts)-1], ", ")
parts[1] = parts[len(parts)-1]
parts = parts[:2]
}
return strings.Join(parts, " and ")
}

View file

@ -1,61 +0,0 @@
// 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 util_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"maunium.net/go/mautrix/util"
)
func TestFormatDuration(t *testing.T) {
tests := []struct {
input time.Duration
expected string
}{
{0, "now"},
{500 * time.Millisecond, "now"},
{999*time.Millisecond + 999*time.Microsecond + 999*time.Nanosecond, "now"},
{999*time.Millisecond + 999*time.Microsecond + 999*time.Nanosecond + 1, "1 second"},
{time.Second, "1 second"},
{2 * time.Second, "2 seconds"},
{59 * time.Second, "59 seconds"},
{time.Minute, "1 minute"},
{2 * time.Minute, "2 minutes"},
{time.Hour, "1 hour"},
{2 * time.Hour, "2 hours"},
{util.Day, "1 day"},
{2 * util.Day, "2 days"},
{util.Week, "1 week"},
{2 * util.Week, "2 weeks"},
{8 * util.Day, "1 week and 1 day"},
{16 * util.Day, "2 weeks and 2 days"},
{time.Minute + time.Second, "1 minute and 1 second"},
{2*time.Minute + 2*time.Second, "2 minutes and 2 seconds"},
{time.Hour + time.Second, "1 hour and 1 second"},
{2*time.Hour + 2*time.Second, "2 hours and 2 seconds"},
{2*time.Hour + time.Minute, "2 hours and 1 minute"},
{time.Hour + time.Minute + time.Second, "1 hour, 1 minute and 1 second"},
{2*time.Hour + 2*time.Minute + 2*time.Second, "2 hours, 2 minutes and 2 seconds"},
{987654 * time.Second, "1 week, 4 days, 10 hours, 20 minutes and 54 seconds"},
{694861 * time.Second, "1 week, 1 day, 1 hour, 1 minute and 1 second"},
{1234 * time.Second, "20 minutes and 34 seconds"},
}
for _, test := range tests {
assert.Equal(t, test.expected, util.FormatDuration(test.input))
}
}
func TestFormatDuration_PanicNegative(t *testing.T) {
assert.Panics(t, func() { util.FormatDuration(-1) })
assert.Panics(t, func() { util.FormatDuration(-time.Second) })
assert.Panics(t, func() { util.FormatDuration(-util.Week) })
}

View file

@ -1,31 +0,0 @@
// Copyright (c) 2022 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 util
import (
"strings"
)
var GJSONEscaper = strings.NewReplacer(
`\`, `\\`,
".", `\.`,
"|", `\|`,
"#", `\#`,
"@", `\@`,
"*", `\*`,
"?", `\?`)
func GJSONPath(path ...string) string {
var result strings.Builder
for i, part := range path {
_, _ = GJSONEscaper.WriteString(&result, part)
if i < len(path)-1 {
result.WriteRune('.')
}
}
return result.String()
}

View file

@ -1,86 +0,0 @@
// Copyright (c) 2022 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 jsontime
import (
"encoding/json"
"time"
)
func UM(time time.Time) UnixMilli {
return UnixMilli{Time: time}
}
func UMInt(ts int64) UnixMilli {
return UM(time.UnixMilli(ts))
}
func UnixMilliNow() UnixMilli {
return UM(time.Now())
}
type UnixMilli struct {
time.Time
}
func (um UnixMilli) MarshalJSON() ([]byte, error) {
if um.IsZero() {
return []byte{'0'}, nil
}
return json.Marshal(um.UnixMilli())
}
func (um *UnixMilli) UnmarshalJSON(data []byte) error {
var val int64
err := json.Unmarshal(data, &val)
if err != nil {
return err
}
if val == 0 {
um.Time = time.Time{}
} else {
um.Time = time.UnixMilli(val)
}
return nil
}
func U(time time.Time) Unix {
return Unix{Time: time}
}
func UInt(ts int64) Unix {
return U(time.Unix(ts, 0))
}
func UnixNow() Unix {
return U(time.Now())
}
type Unix struct {
time.Time
}
func (u Unix) MarshalJSON() ([]byte, error) {
if u.IsZero() {
return []byte{'0'}, nil
}
return json.Marshal(u.Unix())
}
func (u *Unix) UnmarshalJSON(data []byte) error {
var val int64
err := json.Unmarshal(data, &val)
if err != nil {
return err
}
if val == 0 {
u.Time = time.Time{}
} else {
u.Time = time.Unix(val, 0)
}
return nil
}

View file

@ -1,23 +0,0 @@
// 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 util
import "sync"
// ReturnableOnce is a wrapper for sync.Once that can return a value
type ReturnableOnce[Value any] struct {
once sync.Once
output Value
err error
}
func (ronce *ReturnableOnce[Value]) Do(fn func() (Value, error)) (Value, error) {
ronce.once.Do(func() {
ronce.output, ronce.err = fn()
})
return ronce.output, ronce.err
}

View file

@ -1,139 +0,0 @@
// 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 util
import (
"errors"
"sync"
)
type pair[Key comparable, Value any] struct {
Set bool
Key Key
Value Value
}
type RingBuffer[Key comparable, Value any] struct {
ptr int
data []pair[Key, Value]
lock sync.RWMutex
size int
}
func NewRingBuffer[Key comparable, Value any](size int) *RingBuffer[Key, Value] {
return &RingBuffer[Key, Value]{
data: make([]pair[Key, Value], size),
}
}
var (
// StopIteration can be returned by the RingBuffer.Iter or MapRingBuffer callbacks to stop iteration immediately.
StopIteration = errors.New("stop iteration")
// SkipItem can be returned by the MapRingBuffer callback to skip adding a specific item.
SkipItem = errors.New("skip item")
)
func (rb *RingBuffer[Key, Value]) unlockedIter(callback func(key Key, val Value) error) error {
end := rb.ptr
for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) {
entry := rb.data[i]
if !entry.Set {
break
}
err := callback(entry.Key, entry.Value)
if err != nil {
if errors.Is(err, StopIteration) {
return nil
}
return err
}
}
return nil
}
func (rb *RingBuffer[Key, Value]) Iter(callback func(key Key, val Value) error) error {
rb.lock.RLock()
defer rb.lock.RUnlock()
return rb.unlockedIter(callback)
}
func MapRingBuffer[Key comparable, Value, Output any](rb *RingBuffer[Key, Value], callback func(key Key, val Value) (Output, error)) ([]Output, error) {
rb.lock.RLock()
defer rb.lock.RUnlock()
output := make([]Output, 0, rb.size)
err := rb.unlockedIter(func(key Key, val Value) error {
item, err := callback(key, val)
if err != nil {
if errors.Is(err, SkipItem) {
return nil
}
return err
}
output = append(output, item)
return nil
})
return output, err
}
func (rb *RingBuffer[Key, Value]) Size() int {
rb.lock.RLock()
defer rb.lock.RUnlock()
return rb.size
}
func (rb *RingBuffer[Key, Value]) Contains(val Key) bool {
_, ok := rb.Get(val)
return ok
}
func (rb *RingBuffer[Key, Value]) Get(key Key) (val Value, found bool) {
rb.lock.RLock()
end := rb.ptr
for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) {
if rb.data[i].Key == key {
val = rb.data[i].Value
found = true
break
}
}
rb.lock.RUnlock()
return
}
func (rb *RingBuffer[Key, Value]) Replace(key Key, val Value) bool {
rb.lock.Lock()
defer rb.lock.Unlock()
end := rb.ptr
for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) {
if rb.data[i].Key == key {
rb.data[i].Value = val
return true
}
}
return false
}
func (rb *RingBuffer[Key, Value]) Push(key Key, val Value) {
rb.lock.Lock()
rb.data[rb.ptr] = pair[Key, Value]{Key: key, Value: val, Set: true}
rb.ptr = (rb.ptr + 1) % len(rb.data)
if rb.size < len(rb.data) {
rb.size++
}
rb.lock.Unlock()
}
func clamp(index, len int) int {
if index < 0 {
return len + index
} else if index >= len {
return len - index
} else {
return index
}
}

View file

@ -1,94 +0,0 @@
// 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 util
import "sync"
// SyncMap is a simple map with a built-in mutex.
type SyncMap[Key comparable, Value any] struct {
data map[Key]Value
lock sync.RWMutex
}
func NewSyncMap[Key comparable, Value any]() *SyncMap[Key, Value] {
return &SyncMap[Key, Value]{
data: make(map[Key]Value),
}
}
// Set stores a value in the map.
func (sm *SyncMap[Key, Value]) Set(key Key, value Value) {
sm.Swap(key, value)
}
// Swap sets a value in the map and returns the old value.
//
// The boolean return parameter is true if the value already existed, false if not.
func (sm *SyncMap[Key, Value]) Swap(key Key, value Value) (oldValue Value, wasReplaced bool) {
sm.lock.Lock()
oldValue, wasReplaced = sm.data[key]
sm.data[key] = value
sm.lock.Unlock()
return
}
// Delete removes a key from the map.
func (sm *SyncMap[Key, Value]) Delete(key Key) {
sm.Pop(key)
}
// Pop removes a key from the map and returns the old value.
//
// The boolean return parameter is the same as with normal Go map access (true if the key exists, false if not).
func (sm *SyncMap[Key, Value]) Pop(key Key) (value Value, ok bool) {
sm.lock.Lock()
value, ok = sm.data[key]
delete(sm.data, key)
sm.lock.Unlock()
return
}
// Get gets a value in the map.
//
// The boolean return parameter is the same as with normal Go map access (true if the key exists, false if not).
func (sm *SyncMap[Key, Value]) Get(key Key) (value Value, ok bool) {
sm.lock.RLock()
value, ok = sm.data[key]
sm.lock.RUnlock()
return
}
// GetOrSet gets a value in the map if the key already exists, otherwise inserts the given value and returns it.
//
// The boolean return parameter is true if the key already exists, and false if the given value was inserted.
func (sm *SyncMap[Key, Value]) GetOrSet(key Key, value Value) (actual Value, wasGet bool) {
sm.lock.Lock()
defer sm.lock.Unlock()
actual, wasGet = sm.data[key]
if wasGet {
return
}
sm.data[key] = value
actual = value
return
}
// Clone returns a copy of the map.
func (sm *SyncMap[Key, Value]) Clone() *SyncMap[Key, Value] {
return &SyncMap[Key, Value]{data: sm.CopyData()}
}
// CopyData returns a copy of the data in the map as a normal (non-atomic) map.
func (sm *SyncMap[Key, Value]) CopyData() map[Key]Value {
sm.lock.RLock()
copied := make(map[Key]Value, len(sm.data))
for key, value := range sm.data {
copied[key] = value
}
sm.lock.RUnlock()
return copied
}

View file

@ -1 +0,0 @@
["*","0","1","2","3","4","5","6","7","8","9","©","®","‼","⁉","™","","↔","↕","↖","↗","↘","↙","↩","↪","⌚","⌛","⌨","⏏","⏩","⏪","⏭","⏮","⏯","⏱","⏲","⏳","⏸","⏹","⏺","Ⓜ","▪","▫","▶","◀","◻","◼","◽","◾","☀","☁","☂","☃","☄","☎","☑","☔","☕","☘","☝","☠","☢","☣","☦","☪","☮","☯","☸","☹","☺","♀","♂","♈","♉","♊","♋","♌","♍","♎","♏","♐","♑","♒","♓","♟","♠","♣","♥","♦","♨","♻","♾","♿","⚒","⚓","⚔","⚕","⚖","⚗","⚙","⚛","⚜","⚠","⚡","⚧","⚪","⚫","⚰","⚱","⚽","⚾","⛄","⛅","⛈","⛏","⛑","⛓","⛔","⛩","⛪","⛰","⛱","⛲","⛳","⛴","⛵","⛷","⛸","⛹","⛺","⛽","✂","✈","✉","✌","✍","✏","✒","✔","✖","✝","✡","✳","✴","❄","❇","❓","❗","❣","❤","➡","⤴","⤵","⬅","⬆","⬇","⬛","⬜","⭐","⭕","〰","〽","㊗","㊙","🀄","🅰","🅱","🅾","🅿","🈂","🈚","🈯","🈷","🌍","🌎","🌏","🌕","🌜","🌡","🌤","🌥","🌦","🌧","🌨","🌩","🌪","🌫","🌬","🌶","🍸","🍽","🎓","🎖","🎗","🎙","🎚","🎛","🎞","🎟","🎧","🎬","🎭","🎮","🏂","🏄","🏆","🏊","🏋","🏌","🏍","🏎","🏔","🏕","🏖","🏗","🏘","🏙","🏚","🏛","🏜","🏝","🏞","🏟","🏠","🏭","🏳","🏵","🏷","🐈","🐕","🐟","🐦","🐿","👁","👂","👆","👇","👈","👉","👍","👎","👓","👪","👽","💣","💰","💳","💻","💿","📋","📚","📟","📤","📥","📦","📪","📫","📬","📭","📷","📹","📺","📻","📽","🔈","🔍","🔒","🔓","🕉","🕊","🕐","🕑","🕒","🕓","🕔","🕕","🕖","🕗","🕘","🕙","🕚","🕛","🕜","🕝","🕞","🕟","🕠","🕡","🕢","🕣","🕤","🕥","🕦","🕧","🕯","🕰","🕳","🕴","🕵","🕶","🕷","🕸","🕹","🖇","🖊","🖋","🖌","🖍","🖐","🖥","🖨","🖱","🖲","🖼","🗂","🗃","🗄","🗑","🗒","🗓","🗜","🗝","🗞","🗡","🗣","🗨","🗯","🗳","🗺","😐","🚇","🚍","🚑","🚔","🚘","🚭","🚲","🚹","🚺","🚼","🛋","🛍","🛎","🛏","🛠","🛡","🛢","🛣","🛤","🛥","🛩","🛰","🛳"]

File diff suppressed because one or more lines are too long

View file

@ -1,17 +0,0 @@
#!/bin/bash
echo -e "$(
curl -s https://www.unicode.org/Public/15.0.0/ucd/emoji/emoji-variation-sequences.txt \
| grep FE0F \
| awk '{ printf("\\U%8s\n", $1) }' \
| sed 's/ /0/g'
)" | jq -RcM '[inputs]' > emojis-with-variations.json
# Why does this need a \n at the beginning to avoid eating the first emoji?!?!
echo -e "\n$(
curl -s https://unicode.org/Public/emoji/15.0/emoji-test.txt \
| grep '; fully-qualified' \
| grep FE0F \
| sed -E 's/\s+;.*//g' \
| awk '{ for (i = 1; i <= NF; i++) {printf("\\U%8s", $i) }; printf("\n") }' \
| sed 's/ /0/g'
)" | jq -RcM '[inputs]' > fully-qualified-variations.json

View file

@ -1,93 +0,0 @@
// 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 variationselector provides utility functions for adding and removing emoji variation selectors (16)
// that matches the suggestions in the Matrix spec.
package variationselector
import (
_ "embed"
"encoding/json"
"strings"
)
//go:generate ./generate.sh
//go:embed emojis-with-variations.json
var emojisWithVariationsJSON []byte
//go:embed fully-qualified-variations.json
var fullyQualifiedVariationsJSON []byte
var variationReplacer, fullyQualifier *strings.Replacer
// The variation replacer will add incorrect variation selectors before skin tones, this removes those.
var skinToneReplacer = strings.NewReplacer(
"\ufe0f\U0001F3FB", "\U0001F3FB",
"\ufe0f\U0001F3FC", "\U0001F3FC",
"\ufe0f\U0001F3FD", "\U0001F3FD",
"\ufe0f\U0001F3FE", "\U0001F3FE",
"\ufe0f\U0001F3FF", "\U0001F3FF",
)
func init() {
var emojisWithVariations []string
err := json.Unmarshal(emojisWithVariationsJSON, &emojisWithVariations)
if err != nil {
panic(err)
}
replaceInput := make([]string, 2*len(emojisWithVariations))
for i, emoji := range emojisWithVariations {
replaceInput[i*2] = emoji
replaceInput[(i*2)+1] = emoji + VS16
}
variationReplacer = strings.NewReplacer(replaceInput...)
var fullyQualifiedVariations []string
err = json.Unmarshal(fullyQualifiedVariationsJSON, &fullyQualifiedVariations)
if err != nil {
panic(err)
}
replaceInput = make([]string, 2*len(fullyQualifiedVariations))
for i, emoji := range fullyQualifiedVariations {
replaceInput[i*2] = strings.ReplaceAll(emoji, VS16, "")
replaceInput[(i*2)+1] = emoji
}
fullyQualifier = strings.NewReplacer(replaceInput...)
}
const VS16 = "\ufe0f"
// Add adds emoji variation selectors to all emojis that have multiple forms in the given string.
//
// Variation selectors will be added to everything that is allowed to have both a text presentation and
// an emoji presentation according to Unicode Technical Standard #51.
// If you only want to add variation selectors necessary for fully-qualified forms, use FullyQualify instead.
//
// This method uses data from emoji-variation-sequences.txt in the official Unicode emoji data set.
//
// This will remove all variation selectors first to make sure it doesn't add duplicates.
func Add(val string) string {
return skinToneReplacer.Replace(variationReplacer.Replace(Remove(val)))
}
// Remove removes all emoji variation selectors in the given string.
func Remove(val string) string {
return strings.ReplaceAll(val, VS16, "")
}
// FullyQualify converts all emojis to their fully-qualified form by adding variation selectors where necessary.
//
// This will not add variation selectors to all possible emojis, only the ones that require a variation selector
// to be "fully qualified" according to Unicode Technical Standard #51.
// If you want to add variation selectors in all allowed cases, use Add instead.
//
// This method uses data from emoji-test.txt in the official Unicode emoji data set.
//
// N.B. This method is not currently used by the Matrix spec, but it is included as bridging to other networks may need it.
func FullyQualify(val string) string {
return fullyQualifier.Replace(Remove(val))
}

View file

@ -1,82 +0,0 @@
// 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 variationselector_test
import (
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"maunium.net/go/mautrix/util/variationselector"
)
func TestAdd(t *testing.T) {
assert.Equal(t, "\U0001f44d\U0001f3fd", variationselector.Add("\U0001f44d\U0001f3fd"))
assert.Equal(t, "\U0001f44d\ufe0f", variationselector.Add("\U0001f44d"))
assert.Equal(t, "\U0001f44d\ufe0f", variationselector.Add("\U0001f44d\ufe0f"))
assert.Equal(t, "4\ufe0f\u20e3", variationselector.Add("4\u20e3"))
assert.Equal(t, "4\ufe0f\u20e3", variationselector.Add("4\ufe0f\u20e3"))
assert.Equal(t, "\U0001f914", variationselector.Add("\U0001f914"))
}
func TestFullyQualify(t *testing.T) {
assert.Equal(t, "\U0001f44d", variationselector.FullyQualify("\U0001f44d"))
assert.Equal(t, "\U0001f44d", variationselector.FullyQualify("\U0001f44d\ufe0f"))
assert.Equal(t, "4\ufe0f\u20e3", variationselector.FullyQualify("4\u20e3"))
assert.Equal(t, "4\ufe0f\u20e3", variationselector.FullyQualify("4\ufe0f\u20e3"))
assert.Equal(t, "\U0001f914", variationselector.FullyQualify("\U0001f914"))
assert.Equal(t, "\u263a\ufe0f", variationselector.FullyQualify("\u263a"))
assert.Equal(t, "\u263a\ufe0f", variationselector.FullyQualify("\u263a"))
assert.Equal(t, "\U0001f3f3\ufe0f\u200D\U0001f308", variationselector.FullyQualify("\U0001f3f3\u200D\U0001f308"))
assert.Equal(t, "\U0001f3f3\ufe0f\u200D\U0001f308", variationselector.FullyQualify("\U0001f3f3\ufe0f\u200D\U0001f308"))
}
func TestRemove(t *testing.T) {
assert.Equal(t, "\U0001f44d", variationselector.Remove("\U0001f44d"))
assert.Equal(t, "\U0001f44d", variationselector.Remove("\U0001f44d\ufe0f"))
assert.Equal(t, "4\u20e3", variationselector.Remove("4\u20e3"))
assert.Equal(t, "4\u20e3", variationselector.Remove("4\ufe0f\u20e3"))
assert.Equal(t, "\U0001f914", variationselector.Remove("\U0001f914"))
}
func ExampleAdd() {
fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f44d"))) // thumbs up (needs selector)
fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f44d\ufe0f"))) // thumbs up with variation selector (stays as-is)
fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f44d\U0001f3fd"))) // thumbs up with skin tone (shouldn't get selector)
fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f914"))) // thinking face (shouldn't get selector)
// Output:
// "\U0001f44d\ufe0f"
// "\U0001f44d\ufe0f"
// "\U0001f44d\U0001f3fd"
// "\U0001f914"
}
func ExampleFullyQualify() {
fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f44d"))) // thumbs up (already fully qualified)
fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f44d\ufe0f"))) // thumbs up with variation selector (variation selector removed)
fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f44d\U0001f3fd"))) // thumbs up with skin tone (already fully qualified)
fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\u263a"))) // smiling face (unqualified, should get selector)
fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f3f3\u200d\U0001f308"))) // rainbow flag (unqualified, should get selector)
fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f3f3\ufe0f\u200d\U0001f308"))) // rainbow flag with variation selector (already fully qualified)
// Output:
// "\U0001f44d"
// "\U0001f44d"
// "\U0001f44d\U0001f3fd"
// "\u263a\ufe0f"
// "\U0001f3f3\ufe0f\u200d\U0001f308"
// "\U0001f3f3\ufe0f\u200d\U0001f308"
}
func ExampleRemove() {
fmt.Println(strconv.QuoteToASCII(variationselector.Remove("\U0001f44d")))
fmt.Println(strconv.QuoteToASCII(variationselector.Remove("\U0001f44d\ufe0f")))
// Output:
// "\U0001f44d"
// "\U0001f44d"
}