feat(nostr): nostr-based account registration and verification

Rebuilt the nostr registration feature cleanly on top of current ergo
mainline (origin/master), dropping the stale ergo reversions that were
entangled in the original squashed branch.

- New irc/nostr package: NIP-04/NIP-17 DMs, NIP-05 resolution, relay
  connect/auth, and identifier helpers.
- Registration/verification via nostr: parseCallback auto-detects nostr
  identifiers (NIP-05, npub, hex pubkey) and dispatches a verification DM.
- Nostr-based cloaked hostnames for accounts (opt-in via
  ip-cloaking.nostr-hostnames), applied at the account-cloak sites.
- Config: accounts.registration.nostr-verification and the
  nostr-hostnames cloak option, with matching default.yaml and help text.
- Adds github.com/nbd-wtf/go-nostr and vendored dependencies.
This commit is contained in:
cloudfodder
2026-07-28 10:42:09 -07:00
parent a05b379c83
commit eb43ff69c4
919 changed files with 360626 additions and 220 deletions
+17
View File
@@ -366,6 +366,10 @@ server:
# all users will receive simply `netname` as their cloaked hostname.
num-bits: 64
# enable nostr-based hostnames for accounts registered with nostr verification
# (e.g., alice@example.com -> alice.example.com, npub1abc123... -> npub1abc123.nostr)
nostr-hostnames: true
# secure-nets identifies IPs and CIDRs which are secure at layer 3,
# for example, because they are on a trusted internal LAN or a VPN.
# plaintext connections from these IPs and CIDRs will be considered
@@ -477,6 +481,19 @@ accounts:
# time for which a password reset code is valid
timeout: 1d
# options for nostr-based verification of account registrations
nostr-verification:
enabled: true
# hex-encoded private key for signing DMs (generate with: openssl rand -hex 32)
private-key: ""
# default relays to use for sending DMs and relay discovery
default-relays:
- "wss://relay.damus.io"
- "wss://nos.lol"
- "wss://profiles.nostr1.com"
# timeout for nostr operations (NIP-05 resolution, relay connections)
timeout: 30s
# throttle account login attempts (to prevent either password guessing, or DoS
# attacks on the server aimed at forcing repeated expensive bcrypt computations)
login-throttling:
+24 -5
View File
@@ -11,10 +11,8 @@ require (
github.com/ergochat/irc-go v0.5.0
github.com/go-sql-driver/mysql v1.10.0
github.com/gofrs/flock v0.8.1
github.com/gorilla/websocket v1.4.2
github.com/gorilla/websocket v1.5.0
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
github.com/onsi/ginkgo v1.12.0 // indirect
github.com/onsi/gomega v1.9.0 // indirect
github.com/tidwall/buntdb v1.3.2
github.com/xdg-go/scram v1.0.2
golang.org/x/crypto v0.54.0
@@ -28,29 +26,50 @@ require (
github.com/ergochat/webpush-go/v2 v2.0.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/jackc/pgx/v5 v5.10.0
github.com/nbd-wtf/go-nostr v0.52.0
modernc.org/sqlite v1.54.0
)
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.6.1 // indirect
github.com/tidwall/btree v1.4.2 // indirect
github.com/tidwall/gjson v1.14.3 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
golang.org/x/arch v0.15.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
modernc.org/libc v1.74.1 // indirect
+137 -11
View File
@@ -4,14 +4,60 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc=
github.com/emersion/go-msgauth v0.7.0/go.mod h1:mmS9I6HkSovrNgq0HNXTeu8l3sRAAuQ9RMvbM4KU7Ck=
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1 h1:WLHTOodthVyv5NvYLIvWl112kSFv5IInKKrRN2qpons=
@@ -27,6 +73,8 @@ github.com/ergochat/webpush-go/v2 v2.0.0/go.mod h1:OQlhnq8JeHDzRzAy6bdDObr19uqbH
github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=
github.com/ergochat/websocket v1.4.2-oragono1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
@@ -34,13 +82,23 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
@@ -50,6 +108,18 @@ github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@@ -57,29 +127,53 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nbd-wtf/go-nostr v0.52.0 h1:9gtz0VOUPOb0PC2kugr2WJAxThlCSSM62t5VC3tvk1g=
github.com/nbd-wtf/go-nostr v0.52.0/go.mod h1:4avYoc9mDGZ9wHsvCOhHH9vPzKucCfuYBtJUSpHTfNk=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd h1:+iAPaTbi1gZpcpDwe/BW1fx7Xoesv69hLNGPheoyhBs=
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd/go.mod h1:4soZNh0zW0LtYGdQ416i0jO0EIqMGcbtaspRS4BDvRQ=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
@@ -87,60 +181,91 @@ github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYms
github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
github.com/tidwall/buntdb v1.3.2/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -174,3 +299,4 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+67
View File
@@ -25,6 +25,7 @@ import (
"github.com/ergochat/ergo/irc/email"
"github.com/ergochat/ergo/irc/migrations"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/nostr"
"github.com/ergochat/ergo/irc/oauth2"
"github.com/ergochat/ergo/irc/passwd"
"github.com/ergochat/ergo/irc/utils"
@@ -54,6 +55,7 @@ const (
keyAccountChannelToModes = "account.channeltomodes %s"
keyAccountPushSubscriptions = "account.pushsubscriptions %s"
keyAccountMetadata = "account.metadata %s"
keyAccountNostrIdentifier = "account.nostridentifier %s" // stores the nostr identifier used during registration
maxCertfpsPerAccount = 5
)
@@ -865,11 +867,74 @@ func (am *AccountManager) dispatchCallback(client *Client, account string, callb
return "", nil
} else if callbackNamespace == "mailto" {
return am.dispatchMailtoCallback(client, account, callbackValue)
} else if callbackNamespace == "nostr" {
return am.dispatchNostrCallback(client, account, callbackValue)
} else {
return "", fmt.Errorf("Callback not implemented: %s", callbackNamespace)
}
}
func (am *AccountManager) dispatchNostrCallback(_ *Client, account string, callbackValue string) (code string, err error) {
config := am.server.Config().Accounts.Registration.NostrVerification
if !config.Enabled {
return "", fmt.Errorf("Nostr verification is not enabled")
}
code = utils.GenerateSecretToken()
// Create DM config from server config
dmConfig := nostr.DMConfig{
PrivateKey: config.PrivateKey,
DefaultRelays: config.DefaultRelays,
Timeout: time.Duration(config.Timeout),
UserAgent: fmt.Sprintf("Ergo IRC Server %s", am.server.Config().Server.Name),
}
// Send the verification DM
err = nostr.SendVerificationDM(callbackValue, account, code, am.server.Config().Server.Name, dmConfig)
if err != nil {
am.server.logger.Error("internal", "Failed to dispatch nostr DM to", callbackValue, err.Error())
return "", err
}
// Save the nostr identifier for hostname generation
am.saveNostrIdentifier(account, callbackValue)
return code, nil
}
func (am *AccountManager) saveNostrIdentifier(account string, nostrIdentifier string) {
key := fmt.Sprintf(keyAccountNostrIdentifier, account)
am.server.store.Update(func(tx *buntdb.Tx) error {
tx.Set(key, nostrIdentifier, nil)
return nil
})
}
func (am *AccountManager) loadNostrIdentifier(account string) (nostrIdentifier string) {
key := fmt.Sprintf(keyAccountNostrIdentifier, account)
am.server.store.View(func(tx *buntdb.Tx) error {
nostrIdentifier, _ = tx.Get(key)
return nil
})
return
}
// ComputeNostrHostname generates a nostr-based hostname for an account if available
func (am *AccountManager) ComputeNostrHostname(accountName string) string {
config := am.server.Config()
if !config.Server.Cloaks.NostrHostnames {
return ""
}
nostrIdentifier := am.loadNostrIdentifier(accountName)
if nostrIdentifier == "" {
return ""
}
return config.Server.Cloaks.ComputeNostrHostname(nostrIdentifier)
}
func (am *AccountManager) dispatchMailtoCallback(client *Client, account string, callbackValue string) (code string, err error) {
config := am.server.Config().Accounts.Registration.EmailVerification
code = utils.GenerateSecretToken()
@@ -1918,6 +1983,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
emailChangeKey := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
pushSubscriptionsKey := fmt.Sprintf(keyAccountPushSubscriptions, casefoldedAccount)
metadataKey := fmt.Sprintf(keyAccountMetadata, casefoldedAccount)
nostrIdentifierKey := fmt.Sprintf(keyAccountNostrIdentifier, casefoldedAccount)
var clients []*Client
defer func() {
@@ -1978,6 +2044,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
tx.Delete(emailChangeKey)
tx.Delete(pushSubscriptionsKey)
tx.Delete(metadataKey)
tx.Delete(nostrIdentifierKey)
return nil
})
+7 -1
View File
@@ -451,7 +451,13 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus m
rawHostname, cloakedHostname := server.name, ""
if config.Server.Cloaks.EnabledForAlwaysOn {
cloakedHostname = config.Server.Cloaks.ComputeAccountCloak(account.Name)
// Try nostr hostname first, fallback to regular account cloak
if config.Server.Cloaks.NostrHostnames {
cloakedHostname = server.accounts.ComputeNostrHostname(account.Name)
}
if cloakedHostname == "" {
cloakedHostname = config.Server.Cloaks.ComputeAccountCloak(account.Name)
}
}
username := "~u"
+40
View File
@@ -5,9 +5,11 @@ package cloaks
import (
"fmt"
"net"
"strings"
"crypto/sha3"
"github.com/ergochat/ergo/irc/nostr"
"github.com/ergochat/ergo/irc/utils"
)
@@ -19,6 +21,7 @@ type CloakConfig struct {
CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
NumBits int `yaml:"num-bits"`
LegacySecretValue string `yaml:"secret"`
NostrHostnames bool `yaml:"nostr-hostnames"` // enable nostr-based hostnames for accounts registered with nostr
secret string
numBytes int
@@ -93,3 +96,40 @@ func (config *CloakConfig) ComputeAccountCloak(accountName string) string {
copy(paddedAccountName[16:], accountName[:])
return config.macAndCompose(paddedAccountName)
}
// ComputeNostrHostname generates a readable hostname from a nostr identifier
func (config *CloakConfig) ComputeNostrHostname(nostrIdentifier string) string {
if nostrIdentifier == "" {
return config.Netname
}
// Handle NIP-05 addresses (alice@example.com -> alice@example.com)
if strings.Contains(nostrIdentifier, "@") {
parts := strings.SplitN(nostrIdentifier, "@", 2)
if len(parts) == 2 {
// Return the full NIP-05 address as the hostname
return nostrIdentifier
}
}
// Handle npub format (npub1abc123... -> npub1abc123....nostr)
if strings.HasPrefix(nostrIdentifier, "npub1") {
// Use the full npub as hostname
return fmt.Sprintf("%s.nostr", nostrIdentifier)
}
// Handle hex pubkey (3bf0c63f... -> convert to npub and use full npub.nostr)
if len(nostrIdentifier) == 64 {
// Convert hex pubkey to npub format
npub, err := nostr.HexToNpub(nostrIdentifier)
if err != nil {
// Fallback to truncated hex if conversion fails
truncated := nostrIdentifier[:8]
return fmt.Sprintf("%s.nostr", truncated)
}
return fmt.Sprintf("%s.nostr", npub)
}
// Fallback to regular account cloak
return config.ComputeAccountCloak(nostrIdentifier)
}
+7
View File
@@ -406,6 +406,13 @@ type AccountRegistrationConfig struct {
Throttling ThrottleConfig
// new-style (v2.4 email verification config):
EmailVerification email.MailtoConfig `yaml:"email-verification"`
// nostr-based account verification, where we send a DM with verification code
NostrVerification struct {
Enabled bool `yaml:"enabled"`
PrivateKey string `yaml:"private-key"`
DefaultRelays []string `yaml:"default-relays"`
Timeout time.Duration `yaml:"timeout"`
} `yaml:"nostr-verification"`
// old-style email verification config, with "callbacks":
LegacyEnabledCallbacks []string `yaml:"enabled-callbacks"`
LegacyCallbacks struct {
+2
View File
@@ -31,6 +31,8 @@ var (
errAccountSuspended = errors.New(`Account has been suspended`)
errAccountVerificationFailed = errors.New("Account verification failed")
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
errValidNostrIdentifierRequired = errors.New("A valid nostr identifier is required")
errUnsupportedCallbackNamespace = errors.New("Unsupported callback namespace")
errAccountUpdateFailed = errors.New(`Error while updating your account information`)
errAccountMustHoldNick = errors.New(`You must hold that nickname in order to register it`)
errAuthRequired = errors.New("You must be logged into an account to do this")
+38 -13
View File
@@ -32,6 +32,7 @@ import (
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/jwt"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/nostr"
"github.com/ergochat/ergo/irc/oauth2"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
@@ -48,25 +49,42 @@ var (
func parseCallback(spec string, config *Config) (callbackNamespace string, callbackValue string, err error) {
// XXX if we don't require verification, ignore any callback that was passed here
// (to avoid confusion in the case where the ircd has no mail server configured)
if !config.Accounts.Registration.EmailVerification.Enabled {
if !config.Accounts.Registration.EmailVerification.Enabled && !config.Accounts.Registration.NostrVerification.Enabled {
callbackNamespace = "*"
return
}
callback := strings.ToLower(spec)
if colonIndex := strings.IndexByte(callback, ':'); colonIndex != -1 {
callbackNamespace, callbackValue = callback[:colonIndex], callback[colonIndex+1:]
if colonIndex := strings.IndexByte(spec, ':'); colonIndex != -1 {
callbackNamespace, callbackValue = strings.ToLower(spec[:colonIndex]), spec[colonIndex+1:]
} else {
// "If a callback namespace is not ... provided, the IRC server MUST use mailto""
callbackNamespace = "mailto"
callbackValue = callback
// Auto-detect callback type based on format
if nostr.IsNostrIdentifier(spec) {
callbackNamespace = "nostr"
callbackValue = spec
} else {
// "If a callback namespace is not ... provided, the IRC server MUST use mailto""
callbackNamespace = "mailto"
callbackValue = strings.ToLower(spec)
}
}
if config.Accounts.Registration.EmailVerification.Enabled {
if callbackNamespace != "mailto" {
err = errValidEmailRequired
} else if strings.IndexByte(callbackValue, '@') < 1 {
err = errValidEmailRequired
if callbackNamespace == "mailto" {
if config.Accounts.Registration.EmailVerification.Enabled {
if strings.IndexByte(callbackValue, '@') < 1 {
err = errValidEmailRequired
}
} else {
err = errUnsupportedCallbackNamespace
}
} else if callbackNamespace == "nostr" {
if config.Accounts.Registration.NostrVerification.Enabled {
if !nostr.IsValidNostrIdentifier(callbackValue) {
err = errValidNostrIdentifierRequired
}
} else {
err = errUnsupportedCallbackNamespace
}
} else if callbackNamespace != "admin" && callbackNamespace != "none" && callbackNamespace != "*" {
err = errUnsupportedCallbackNamespace
}
return
@@ -137,7 +155,14 @@ func sendSuccessfulAccountAuth(service *ircService, client *Client, rb *Response
if rb.session.isTor {
config := client.server.Config()
if config.Server.Cloaks.EnabledForAlwaysOn {
cloakedHostname := config.Server.Cloaks.ComputeAccountCloak(details.accountName)
// Try nostr hostname first, fallback to regular account cloak
var cloakedHostname string
if config.Server.Cloaks.NostrHostnames {
cloakedHostname = client.server.accounts.ComputeNostrHostname(details.accountName)
}
if cloakedHostname == "" {
cloakedHostname = config.Server.Cloaks.ComputeAccountCloak(details.accountName)
}
client.setCloakedHostname(cloakedHostname)
if client.registered {
client.sendChghost(details.nickMask, client.Hostname())
+11 -4
View File
@@ -135,11 +135,18 @@ INFO gives you information about the given (or your own) user account.`,
handler: nsRegisterHandler,
// TODO: "email" is an oversimplification here; it's actually any callback, e.g.,
// person@example.com, mailto:person@example.com, tel:16505551234.
help: `Syntax: $bREGISTER <password> [email]$b
help: `Syntax: $bREGISTER <password> [nostr-identifier]$b
REGISTER lets you register your current nickname as a user account. If the
server allows anonymous registration, you can omit the e-mail address.
REGISTER lets you register your current nickname as a user account using nostr
verification. You can provide a nostr identifier in one of these formats:
• NIP-05 address: alice@example.com
• npub key: npub1abc123def456...
• hex pubkey: 3bf0c63fcb93c5ef2f068d70b8d70d963b649d75...
The server will contact you via Nostr DM (nip17 or nip04) with verification code.
If the server allows anonymous registration, you can omit the nostr identifier.
If you are currently logged in with a TLS client certificate and wish to use
it instead of a password to log in, send * as the password.`,
helpShort: `$bREGISTER$b lets you register a user account.`,
@@ -1018,7 +1025,7 @@ func nsRegisterHandler(service *ircService, server *Server, client *Client, comm
callbackNamespace, callbackValue, validationErr := parseCallback(email, config)
if validationErr != nil {
service.Notice(rb, client.t("Registration requires a valid e-mail address"))
service.Notice(rb, client.t("Registration invalid, see help"))
return
}
+319
View File
@@ -0,0 +1,319 @@
// Copyright (c) 2024 Ergo Contributors
// released under the MIT license
package nostr
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04"
"github.com/nbd-wtf/go-nostr/nip17"
"github.com/nbd-wtf/go-nostr/nip44"
)
// DMConfig holds configuration for DM operations
type DMConfig struct {
PrivateKey string // Hex-encoded private key for signing
DefaultRelays []string // Fallback relays if none discovered
Timeout time.Duration // Timeout for relay operations
UserAgent string // User agent for HTTP requests
}
// SimpleKeyer implements nostr.Keyer interface for NIP-17
type SimpleKeyer struct {
privateKey string
}
func (k *SimpleKeyer) GetPublicKey(ctx context.Context) (string, error) {
return nostr.GetPublicKey(k.privateKey)
}
func (k *SimpleKeyer) SignEvent(ctx context.Context, event *nostr.Event) error {
return event.Sign(k.privateKey)
}
func (k *SimpleKeyer) Encrypt(ctx context.Context, plaintext, recipientPubkey string) (string, error) {
// Generate conversation key using NIP-44
conversationKey, err := nip44.GenerateConversationKey(recipientPubkey, k.privateKey)
if err != nil {
return "", err
}
return nip44.Encrypt(plaintext, conversationKey)
}
func (k *SimpleKeyer) Decrypt(ctx context.Context, ciphertext, senderPubkey string) (string, error) {
// Generate conversation key using NIP-44
conversationKey, err := nip44.GenerateConversationKey(senderPubkey, k.privateKey)
if err != nil {
return "", err
}
return nip44.Decrypt(ciphertext, conversationKey)
}
// CreateNIP04DM creates a NIP-04 DM event with verification code
func CreateNIP04DM(recipientPubkey, senderPrivkey, account, code, serverName string) (*nostr.Event, error) {
// Create the message content
message := fmt.Sprintf("Account verification for %s\n\nAccount: %s\nVerification code: %s\n\nTo verify your account, issue the following command:\n/MSG NickServ VERIFY %s %s",
serverName, account, code, account, code)
// Compute shared secret for NIP-04 encryption
sharedSecret, err := nip04.ComputeSharedSecret(recipientPubkey, senderPrivkey)
if err != nil {
return nil, fmt.Errorf("%w: failed to compute shared secret: %v", ErrDMEncryptionFailed, err)
}
// Encrypt the message using NIP-04
encryptedContent, err := nip04.Encrypt(message, sharedSecret)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrDMEncryptionFailed, err)
}
// Get sender pubkey from private key
senderPubkey, err := nostr.GetPublicKey(senderPrivkey)
if err != nil {
return nil, fmt.Errorf("failed to derive public key: %w", err)
}
// Create the event
event := &nostr.Event{
PubKey: senderPubkey,
CreatedAt: nostr.Timestamp(time.Now().Unix()),
Kind: 4, // NIP-04 DM event kind
Tags: nostr.Tags{
{"p", recipientPubkey},
},
Content: encryptedContent,
}
// Sign the event
if err := event.Sign(senderPrivkey); err != nil {
return nil, err
}
return event, nil
}
// CreateNIP17DM creates a NIP-17 DM event with verification code
func CreateNIP17DM(recipientPubkey, senderPrivkey, account, code, serverName string) (*nostr.Event, error) {
// Create the message content
message := fmt.Sprintf("Account verification for %s\n\nAccount: %s\nVerification code: %s\n\nTo verify your account, issue the following command:\n/MSG NickServ VERIFY %s %s",
serverName, account, code, account, code)
// Create a SimpleKeyer instance
keyer := &SimpleKeyer{privateKey: senderPrivkey}
// Use nip17.PrepareMessage to create properly gift-wrapped events
ctx := context.Background()
_, toThem, err := nip17.PrepareMessage(
ctx,
message,
nostr.Tags{}, // empty tags, the function will add the "p" tag
keyer,
recipientPubkey,
nil, // no modify function
)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrDMEncryptionFailed, err)
}
// Return the toThem event (the one that goes to the recipient)
return &toThem, nil
}
// SendVerificationDM sends a verification DM to a user
func SendVerificationDM(identifier, account, code, serverName string, config DMConfig) error {
if config.PrivateKey == "" {
return ErrNostrKeyNotConfigured
}
ctx, cancel := context.WithTimeout(context.Background(), config.Timeout)
defer cancel()
// Resolve the identifier to a pubkey
nip05Config := NIP05Config{
Timeout: config.Timeout,
UserAgent: config.UserAgent,
}
pubkey, nip05Relays, err := ResolvePubkey(identifier, nip05Config)
if err != nil {
return fmt.Errorf("failed to resolve pubkey: %w", err)
}
// Discover inbox relays
relayConfig := RelayConfig{
DefaultRelays: config.DefaultRelays,
Timeout: config.Timeout,
MaxRelays: 10,
}
// Combine NIP-05 relays with discovered relays
allRelays := append(nip05Relays, config.DefaultRelays...)
if len(allRelays) > 0 {
relayConfig.DefaultRelays = allRelays
}
// Check if user has private relays (NIP-50 kind 10050)
privateRelays, hasPrivateRelays, err := CheckPrivateRelays(ctx, pubkey, relayConfig)
if err != nil {
// Continue with regular flow if private relay check fails
hasPrivateRelays = false
}
var dmEvent *nostr.Event
var targetRelays []string
if hasPrivateRelays && len(privateRelays) > 0 {
log.Printf("Using NIP-17 DM for user with private relays")
// Use NIP-17 DMs for users with private relays
dmEvent, err = CreateNIP17DM(pubkey, config.PrivateKey, account, code, serverName)
if err != nil {
return fmt.Errorf("failed to create NIP-17 DM: %w", err)
}
targetRelays = privateRelays
log.Printf("Targeting private relays: %v", targetRelays)
} else {
log.Printf("Using NIP-04 DM for regular user")
// Use NIP-04 DMs for regular users
dmEvent, err = CreateNIP04DM(pubkey, config.PrivateKey, account, code, serverName)
if err != nil {
return fmt.Errorf("failed to create NIP-04 DM: %w", err)
}
// Discover inbox relays for NIP-04
inboxRelays, err := DiscoverInboxRelays(pubkey, relayConfig)
if err != nil {
return fmt.Errorf("failed to discover inbox relays: %w", err)
}
targetRelays = inboxRelays
log.Printf("Targeting inbox relays: %v", targetRelays)
}
// Send to target relays with retry logic
var lastErr error
successCount := 0
for _, relayURL := range targetRelays {
// Try connecting and sending
err := sendToRelayWithRetry(ctx, relayURL, *dmEvent, config.PrivateKey)
if err != nil {
lastErr = err
continue
}
successCount++
}
if successCount == 0 {
if lastErr != nil {
return fmt.Errorf("%w: %v", ErrDMSendFailed, lastErr)
}
return ErrDMSendFailed
}
return nil
}
// sendToRelayWithRetry attempts to send a DM to a relay with retry logic for auth failures
func sendToRelayWithRetry(ctx context.Context, relayURL string, event nostr.Event, privkey string) error {
// Create a separate timeout context for this relay connection (15 seconds)
relayCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// First attempt without auth
relay, err := nostr.RelayConnect(relayCtx, relayURL)
if err != nil {
return fmt.Errorf("failed to connect to relay %s: %w", relayURL, err)
}
defer relay.Close()
// Try sending without auth first
err = relay.Publish(relayCtx, event)
if err == nil {
return nil // Success!
}
// If we get an auth error, try with authentication
if strings.Contains(err.Error(), "auth-required") || strings.Contains(err.Error(), "you must auth") {
// Close and reconnect with auth
relay.Close()
// Create a new timeout context for the auth connection
authCtx, authCancel := context.WithTimeout(context.Background(), 15*time.Second)
defer authCancel()
relay, err = ConnectToRelay(authCtx, relayURL, privkey)
if err != nil {
return fmt.Errorf("failed to connect with auth to relay %s: %w", relayURL, err)
}
defer relay.Close()
// Try sending again after auth
err = relay.Publish(authCtx, event)
if err != nil {
return fmt.Errorf("failed to publish to authenticated relay %s: %w", relayURL, err)
}
return nil
}
// Other error, return as-is
return fmt.Errorf("failed to publish to relay %s: %w", relayURL, err)
}
// SendNIP04DM sends a NIP-04 DM to specific relays
func SendNIP04DM(ctx context.Context, event *nostr.Event, relays []string, privkey string) error {
var lastErr error
successCount := 0
for _, relayURL := range relays {
// Try connecting and sending
err := sendToRelayWithRetry(ctx, relayURL, *event, privkey)
if err != nil {
lastErr = err
continue
}
successCount++
}
if successCount == 0 {
if lastErr != nil {
return fmt.Errorf("%w: %v", ErrDMSendFailed, lastErr)
}
return ErrDMSendFailed
}
return nil
}
// SendNIP17DM sends a NIP-17 DM to private relays
func SendNIP17DM(ctx context.Context, event *nostr.Event, privateRelays []string, privkey string) error {
var lastErr error
successCount := 0
for _, relayURL := range privateRelays {
// Try connecting and sending
err := sendToRelayWithRetry(ctx, relayURL, *event, privkey)
if err != nil {
lastErr = err
continue
}
successCount++
}
if successCount == 0 {
if lastErr != nil {
return fmt.Errorf("%w: %v", ErrDMSendFailed, lastErr)
}
return ErrDMSendFailed
}
return nil
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2024 Ergo Contributors
// released under the MIT license
package nostr
import "errors"
var (
// Identifier validation errors
ErrInvalidNostrIdentifier = errors.New("invalid nostr identifier format")
ErrInvalidPubkeyFormat = errors.New("invalid pubkey format")
ErrInvalidPubkeyLength = errors.New("invalid pubkey length")
ErrInvalidHexFormat = errors.New("invalid hex format")
ErrInvalidPrivkeyLength = errors.New("invalid private key length")
ErrInvalidNpubPrefix = errors.New("invalid npub prefix")
// NIP-05 resolution errors
ErrNIP05ResolutionFailed = errors.New("NIP-05 resolution failed")
ErrNIP05NotFound = errors.New("NIP-05 address not found")
ErrNIP05InvalidResponse = errors.New("invalid NIP-05 response")
ErrNIP05HTTPError = errors.New("HTTP error during NIP-05 resolution")
ErrNIP05PubkeyNotFound = errors.New("pubkey not found in NIP-05 response")
// Relay discovery errors
ErrRelayDiscoveryFailed = errors.New("relay discovery failed")
ErrNoInboxRelaysFound = errors.New("no inbox relays found")
ErrRelayConnectionFailed = errors.New("relay connection failed")
ErrRelayAuthFailed = errors.New("relay authentication failed")
// DM sending errors
ErrDMSendFailed = errors.New("DM send failed")
ErrDMEncryptionFailed = errors.New("DM encryption failed")
ErrNostrKeyNotConfigured = errors.New("nostr private key not configured")
// Decoding errors
ErrNpubDecodingFailed = errors.New("npub decoding failed")
)
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2024 Ergo Contributors
// released under the MIT license
package nostr
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
// NIP05Response represents the JSON response from a .well-known/nostr.json endpoint
type NIP05Response struct {
Names map[string]string `json:"names"`
Relays map[string][]string `json:"relays,omitempty"`
}
// NIP05Config holds configuration for NIP-05 resolution
type NIP05Config struct {
Timeout time.Duration
UserAgent string
}
// ResolveNIP05 resolves a NIP-05 identifier to a pubkey
func ResolveNIP05(identifier string, config NIP05Config) (pubkey string, relays []string, err error) {
if !IsNIP05(identifier) {
return "", nil, ErrInvalidPubkeyFormat
}
parts := strings.Split(identifier, "@")
if len(parts) != 2 {
return "", nil, ErrInvalidPubkeyFormat
}
name, domain := parts[0], parts[1]
// Construct the well-known URL
url := fmt.Sprintf("https://%s/.well-known/nostr.json?name=%s", domain, name)
// Create HTTP client with timeout
client := &http.Client{
Timeout: config.Timeout,
}
// Make the request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", nil, fmt.Errorf("%w: %v", ErrNIP05HTTPError, err)
}
if config.UserAgent != "" {
req.Header.Set("User-Agent", config.UserAgent)
}
resp, err := client.Do(req)
if err != nil {
return "", nil, fmt.Errorf("%w: %v", ErrNIP05HTTPError, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", nil, fmt.Errorf("%w: HTTP %d", ErrNIP05HTTPError, resp.StatusCode)
}
// Parse the JSON response
var nip05Resp NIP05Response
if err := json.NewDecoder(resp.Body).Decode(&nip05Resp); err != nil {
return "", nil, fmt.Errorf("%w: %v", ErrNIP05InvalidResponse, err)
}
// Look up the pubkey for this name
pubkey, exists := nip05Resp.Names[name]
if !exists {
return "", nil, ErrNIP05PubkeyNotFound
}
// Validate the returned pubkey
if err := ValidateHexPubkey(pubkey); err != nil {
return "", nil, fmt.Errorf("%w: invalid pubkey in response", ErrNIP05InvalidResponse)
}
// Get relays if available
if nip05Resp.Relays != nil {
relays = nip05Resp.Relays[pubkey]
}
return pubkey, relays, nil
}
// ResolvePubkey resolves any nostr identifier to a hex pubkey
func ResolvePubkey(identifier string, config NIP05Config) (pubkey string, relays []string, err error) {
// Debug logging to see what identifier we're trying to resolve
fmt.Printf("[DEBUG] ResolvePubkey called with identifier: '%s'\n", identifier)
fmt.Printf("[DEBUG] IsNIP05: %v, IsPubkey: %v, IsNpub: %v, IsHexPubkey: %v\n",
IsNIP05(identifier), IsPubkey(identifier), IsNpub(identifier), IsHexPubkey(identifier))
if IsNIP05(identifier) {
fmt.Printf("[DEBUG] Resolving as NIP-05 identifier\n")
return ResolveNIP05(identifier, config)
} else if IsPubkey(identifier) {
fmt.Printf("[DEBUG] Resolving as pubkey, normalizing...\n")
normalizedPubkey, err := NormalizePubkey(identifier)
if err != nil {
fmt.Printf("[DEBUG] NormalizePubkey failed: %v\n", err)
return "", nil, err
}
fmt.Printf("[DEBUG] Normalized pubkey: %s\n", normalizedPubkey)
return normalizedPubkey, nil, nil
}
fmt.Printf("[DEBUG] Identifier doesn't match any known format\n")
return "", nil, ErrInvalidPubkeyFormat
}
+160
View File
@@ -0,0 +1,160 @@
// Copyright (c) 2024 Ergo Contributors
// released under the MIT license
package nostr
import (
"encoding/hex"
"fmt"
"regexp"
"strings"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
var (
// NIP-05 identifier format: name@domain.tld
nip05Regex = regexp.MustCompile(`^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
// npub format (bech32 encoded pubkey)
npubRegex = regexp.MustCompile(`^npub1[a-zA-Z0-9]{58}$`)
// hex pubkey format (64 hex characters)
hexPubkeyRegex = regexp.MustCompile(`^[a-fA-F0-9]{64}$`)
)
// IsNostrIdentifier checks if a string could be a nostr identifier (NIP-05 or pubkey)
func IsNostrIdentifier(identifier string) bool {
return IsNIP05(identifier) || IsPubkey(identifier)
}
// IsValidNostrIdentifier validates a nostr identifier format
func IsValidNostrIdentifier(identifier string) bool {
return IsNostrIdentifier(identifier)
}
// IsNIP05 checks if the identifier is a NIP-05 address
func IsNIP05(identifier string) bool {
return nip05Regex.MatchString(identifier)
}
// IsPubkey checks if the identifier is a pubkey (npub or hex)
func IsPubkey(identifier string) bool {
return IsNpub(identifier) || IsHexPubkey(identifier)
}
// IsNpub checks if the identifier is an npub (bech32) format
func IsNpub(identifier string) bool {
return npubRegex.MatchString(identifier)
}
// IsHexPubkey checks if the identifier is a hex pubkey
func IsHexPubkey(identifier string) bool {
return hexPubkeyRegex.MatchString(identifier)
}
// NormalizePubkey converts npub to hex format, returns hex pubkey as-is
func NormalizePubkey(pubkey string) (string, error) {
fmt.Printf("[DEBUG] NormalizePubkey called with: '%s' (len=%d)\n", pubkey, len(pubkey))
fmt.Printf("[DEBUG] IsHexPubkey: %v, IsNpub: %v\n", IsHexPubkey(pubkey), IsNpub(pubkey))
if IsHexPubkey(pubkey) {
fmt.Printf("[DEBUG] Treating as hex pubkey\n")
return strings.ToLower(pubkey), nil
}
if IsNpub(pubkey) {
fmt.Printf("[DEBUG] Treating as npub, decoding...\n")
// Use go-nostr to decode npub
prefix, data, err := nip19.Decode(pubkey)
if err != nil {
fmt.Printf("[DEBUG] nip19.Decode failed: %v\n", err)
return "", ErrInvalidPubkeyFormat
}
fmt.Printf("[DEBUG] nip19.Decode success - prefix: %s, data type: %T\n", prefix, data)
// Handle both string and []byte return types from nip19.Decode
switch v := data.(type) {
case []byte:
if len(v) == 32 {
hexResult := hex.EncodeToString(v)
fmt.Printf("[DEBUG] Successfully converted npub bytes to hex: %s\n", hexResult)
return hexResult, nil
}
case string:
if len(v) == 64 {
// Already a hex string, validate it
if err := ValidateHexPubkey(v); err != nil {
fmt.Printf("[DEBUG] Invalid hex pubkey from npub: %v\n", err)
return "", ErrInvalidPubkeyFormat
}
fmt.Printf("[DEBUG] Successfully got hex string from npub: %s\n", v)
return strings.ToLower(v), nil
}
}
fmt.Printf("[DEBUG] Unexpected data format from nip19.Decode: %T, value: %v\n", data, data)
return "", ErrInvalidPubkeyFormat
}
fmt.Printf("[DEBUG] Pubkey doesn't match hex or npub format\n")
return "", ErrInvalidPubkeyFormat
}
// ValidateHexPubkey ensures a hex string is a valid pubkey
func ValidateHexPubkey(hexStr string) error {
if len(hexStr) != 64 {
return ErrInvalidPubkeyLength
}
_, err := hex.DecodeString(hexStr)
if err != nil {
return ErrInvalidHexFormat
}
return nil
}
// ValidatePrivateKey validates a hex private key
func ValidatePrivateKey(privkeyHex string) error {
if len(privkeyHex) != 64 {
return ErrInvalidPubkeyLength
}
_, err := hex.DecodeString(privkeyHex)
if err != nil {
return ErrInvalidHexFormat
}
return nil
}
// GetPubkeyFromPrivkey derives public key from private key using go-nostr
func GetPubkeyFromPrivkey(privkeyHex string) (string, error) {
if len(privkeyHex) != 64 {
return "", ErrInvalidPrivkeyLength
}
pubkey, err := nostr.GetPublicKey(privkeyHex)
if err != nil {
return "", err
}
return pubkey, nil
}
// HexToNpub converts a hex pubkey to npub format
func HexToNpub(hexPubkey string) (string, error) {
if !IsHexPubkey(hexPubkey) {
return "", ErrInvalidPubkeyFormat
}
// Encode as npub using nip19 (it expects hex string, not bytes)
npub, err := nip19.EncodePublicKey(hexPubkey)
if err != nil {
return "", err
}
return npub, nil
}
+271
View File
@@ -0,0 +1,271 @@
// Copyright (c) 2024 Ergo Contributors
// released under the MIT license
package nostr
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/nbd-wtf/go-nostr"
)
// RelayConfig holds configuration for relay operations
type RelayConfig struct {
DefaultRelays []string
Timeout time.Duration
MaxRelays int
}
// RelayInfo represents the NIP-11 relay information document
type RelayInfo struct {
Name string `json:"name"`
Description string `json:"description"`
PubKey string `json:"pubkey"`
Contact string `json:"contact"`
Limitation struct {
AuthRequired bool `json:"auth_required"`
} `json:"limitation"`
}
// DiscoverInboxRelays discovers a user's inbox relays using NIP-65
func DiscoverInboxRelays(pubkey string, config RelayConfig) ([]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), config.Timeout)
defer cancel()
// Try to get relays from NIP-65 (kind 10002 events)
inboxRelays, err := queryRelayListMetadata(ctx, pubkey, config)
if err == nil && len(inboxRelays) > 0 {
return inboxRelays, nil
}
// Fallback to default relays if no inbox relays found
if len(config.DefaultRelays) > 0 {
return config.DefaultRelays, nil
}
return nil, ErrNoInboxRelaysFound
}
// queryRelayListMetadata queries for NIP-65 relay list metadata events
func queryRelayListMetadata(ctx context.Context, pubkey string, config RelayConfig) ([]string, error) {
var inboxRelays []string
// Connect to default relays to query for relay list metadata
for _, relayURL := range config.DefaultRelays {
relay, err := nostr.RelayConnect(ctx, relayURL)
if err != nil {
continue // Try next relay
}
// Query for kind 10002 events (NIP-65 relay list metadata)
filters := []nostr.Filter{{
Authors: []string{pubkey},
Kinds: []int{10002},
Limit: 1,
}}
sub, err := relay.Subscribe(ctx, filters)
if err != nil {
relay.Close()
continue
}
// Wait for events with timeout
done := false
select {
case event := <-sub.Events:
relays := parseRelayListEvent(*event)
inboxRelays = append(inboxRelays, relays...)
case <-time.After(5 * time.Second):
// Timeout waiting for event
case <-ctx.Done():
done = true
}
sub.Unsub()
relay.Close()
if done {
break
}
if len(inboxRelays) > 0 {
break // Found relays, no need to query more
}
}
if len(inboxRelays) == 0 {
return nil, ErrRelayDiscoveryFailed
}
return inboxRelays, nil
}
// parseRelayListEvent parses a NIP-65 relay list metadata event
func parseRelayListEvent(event nostr.Event) []string {
var inboxRelays []string
for _, tag := range event.Tags {
if len(tag) >= 2 && tag[0] == "r" {
relayURL := tag[1]
// Check if this is an inbox relay (read capability)
// If no marker is specified, assume both read and write
isInbox := true
if len(tag) >= 3 {
marker := tag[2]
isInbox = marker == "read" || marker == ""
}
if isInbox {
inboxRelays = append(inboxRelays, relayURL)
}
}
}
return inboxRelays
}
// checkRelayRequiresAuth checks if a relay requires authentication via NIP-11
func checkRelayRequiresAuth(url string) bool {
httpURL := strings.Replace(strings.Replace(url, "ws://", "http://", 1), "wss://", "https://", 1)
client := &http.Client{
Timeout: time.Second * 5,
}
req, err := http.NewRequest("GET", httpURL, nil)
if err != nil {
return false
}
req.Header.Set("Accept", "application/nostr+json")
resp, err := client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
var info RelayInfo
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return false
}
return info.Limitation.AuthRequired
}
// ConnectToRelay establishes a connection to a nostr relay with optional NIP-42 auth
func ConnectToRelay(ctx context.Context, url string, privkey string) (*nostr.Relay, error) {
log.Printf("Connecting to relay: %s\n", url)
relay, err := nostr.RelayConnect(ctx, url)
if err != nil {
log.Printf("Failed to connect to relay: %s, error: %v\n", url, err)
return nil, fmt.Errorf("%w: %v", ErrRelayConnectionFailed, err)
}
log.Printf("Connected to relay: %s\n", url)
// Check if relay requires auth before attempting authentication
if privkey != "" && checkRelayRequiresAuth(url) {
log.Printf("Relay requires authentication: %s\n", url)
err = relay.Auth(ctx, func(authEvent *nostr.Event) error {
// Validate challenge tag is present and not empty
challengeTag := authEvent.Tags.Find("challenge")
if len(challengeTag) < 2 || challengeTag[1] == "" || challengeTag[1] == " " {
return fmt.Errorf("invalid or missing challenge in auth event")
}
return authEvent.Sign(privkey)
})
if err != nil {
log.Printf("Failed to authenticate with relay: %s, error: %v\n", url, err)
// Don't fail connection on auth error - some operations might still work
// The relay will reject operations that require auth if needed
} else {
log.Printf("Authenticated with relay: %s\n", url)
}
}
return relay, nil
}
// SendEventToRelay sends a nostr event to a relay
func SendEventToRelay(ctx context.Context, relay *nostr.Relay, event nostr.Event) error {
err := relay.Publish(ctx, event)
if err != nil {
return fmt.Errorf("%w: %v", ErrDMSendFailed, err)
}
return nil
}
// CheckPrivateRelays checks if a user has private relays (NIP-50 kind 10050)
func CheckPrivateRelays(ctx context.Context, pubkey string, config RelayConfig) ([]string, bool, error) {
var privateRelays []string
// Connect to default relays to query for private relay list
for _, relayURL := range config.DefaultRelays {
relay, err := nostr.RelayConnect(ctx, relayURL)
if err != nil {
continue
}
// Query for kind 10050 events (private relay list)
filters := []nostr.Filter{{
Authors: []string{pubkey},
Kinds: []int{10050},
Limit: 1,
}}
sub, err := relay.Subscribe(ctx, filters)
if err != nil {
relay.Close()
continue
}
// Wait for events
done := false
select {
case event := <-sub.Events:
privateRelays = parsePrivateRelayList(*event)
case <-time.After(5 * time.Second):
// Timeout
case <-ctx.Done():
done = true
}
sub.Unsub()
relay.Close()
if done {
break
}
if len(privateRelays) > 0 {
return privateRelays, true, nil
}
}
return nil, false, nil
}
// parsePrivateRelayList parses a NIP-50 private relay list event
func parsePrivateRelayList(event nostr.Event) []string {
var privateRelays []string
for _, tag := range event.Tags {
if len(tag) >= 2 && tag[0] == "relay" {
privateRelays = append(privateRelays, tag[1])
}
}
return privateRelays
}
+2 -2
View File
@@ -43,7 +43,7 @@
"PONG <args>...\n\nReplies to a PING. Used to check link connectivity.": "PONG <args>...\n\nReplies to a PING. Used to check link connectivity.",
"PRIVMSG <target>{,<target>} <text to be sent>\n\nSends the text to the given targets as a PRIVMSG.": "PRIVMSG <target>{,<target>} <text to be sent>\n\nSends the text to the given targets as a PRIVMSG.",
"QUIT [reason]\n\nIndicates that you're leaving the server, and shows everyone the given reason.": "QUIT [reason]\n\nIndicates that you're leaving the server, and shows everyone the given reason.",
"REGISTER <email | *> <password>\n\nRegisters an account in accordance with the draft/register capability.": "REGISTER <email | *> <password>\n\nRegisters an account in accordance with the draft/register capability.",
"REGISTER <nostr-identifier | *> <password>\n\nRegisters an account using nostr verification. The nostr-identifier can be:\n• NIP-05 address: alice@example.com\n• npub key: npub1abc123def456...\n• hex pubkey: 3bf0c63fcb93c5ef2f068d70b8d70d963b649d75...\n\nUse * if you want to register without nostr verification.": "REGISTER <nostr-identifier | *> <password>\n\nRegisters an account using nostr verification. The nostr-identifier can be:\n• NIP-05 address: alice@example.com\n• npub key: npub1abc123def456...\n• hex pubkey: 3bf0c63fcb93c5ef2f068d70b8d70d963b649d75...\n\nUse * if you want to register without nostr verification.",
"REHASH\n\nReloads the config file and updates TLS certificates on listeners": "REHASH\n\nReloads the config file and updates TLS certificates on listeners",
"RELAYMSG <channel> <spoofed nick> :<message>\n\nThis command lets channel operators relay messages to their\nchannel from other messaging systems using relay bots. The\nspoofed nickname MUST contain a forwardslash.\n\nFor example:\n\tRELAYMSG #ircv3 Mallory/D :Welp, we linked Discord...": "RELAYMSG <channel> <spoofed nick> :<message>\n\nThis command lets channel operators relay messages to their\nchannel from other messaging systems using relay bots. The\nspoofed nickname MUST contain a forwardslash.\n\nFor example:\n\tRELAYMSG #ircv3 Mallory/D :Welp, we linked Discord...",
"RENAME <channel> <newname> [<reason>]\n\nRenames the given channel with the given reason, if possible.\n\nFor example:\n\tRENAME #ircv2 #ircv3 :Protocol upgrades!": "RENAME <channel> <newname> [<reason>]\n\nRenames the given channel with the given reason, if possible.\n\nFor example:\n\tRENAME #ircv2 #ircv3 :Protocol upgrades!",
@@ -64,7 +64,7 @@
"USER <username> 0 * <realname>\n\nUsed in connection registration, sets your username and realname to the given\nvalues (though your username may also be looked up with Ident).": "USER <username> 0 * <realname>\n\nUsed in connection registration, sets your username and realname to the given\nvalues (though your username may also be looked up with Ident).",
"USERHOST <nickname>{ <nickname>}\n\t\t\nShows information about the given users. Takes up to 10 nicknames.": "USERHOST <nickname>{ <nickname>}\n\t\t\nShows information about the given users. Takes up to 10 nicknames.",
"USERS [parameters]\n\nThe USERS command is not implemented.": "USERS [parameters]\n\nThe USERS command is not implemented.",
"VERIFY <account> <password>\n\nVerifies an account in accordance with the draft/register capability.": "VERIFY <account> <password>\n\nVerifies an account in accordance with the draft/register capability.",
"VERIFY <account> <code>\n\nVerifies an account using the verification code sent via nostr DM.": "VERIFY <account> <code>\n\nVerifies an account using the verification code sent via nostr DM.",
"VERSION [server]\n\nViews the version of software and the RPL_ISUPPORT tokens for the given server.": "VERSION [server]\n\nViews the version of software and the RPL_ISUPPORT tokens for the given server.",
"WEBIRC <password> <gateway> <hostname> <ip> [:<flags>]\n\nUsed by web<->IRC gateways and bouncers, the WEBIRC command allows gateways to\npass-through the real IP addresses of clients:\nircv3.net/specs/extensions/webirc.html\n\n<flags> is a list of space-separated strings indicating various details about\nthe connection from the client to the gateway, such as:\n\n- tls: this flag indicates that the client->gateway connection is secure": "WEBIRC <password> <gateway> <hostname> <ip> [:<flags>]\n\nUsed by web<->IRC gateways and bouncers, the WEBIRC command allows gateways to\npass-through the real IP addresses of clients:\nircv3.net/specs/extensions/webirc.html\n\n<flags> is a list of space-separated strings indicating various details about\nthe connection from the client to the gateway, such as:\n\n- tls: this flag indicates that the client->gateway connection is secure",
"WHO <name> [o]\n\nReturns information for the given user.": "WHO <name> [o]\n\nReturns information for the given user.",
+10 -10
View File
@@ -7,7 +7,7 @@
"$bCLIENTS$b can list and logout the sessions attached to a nickname.": "$bCLIENTS$b can list and logout the sessions attached to a nickname.",
"$bDM-HISTORY$b\n'dm-history' is only effective for always-on clients. It lets you control\nhow the history of your direct messages is stored. Your options are:\n1. 'off' [no history]\n2. 'ephemeral' [a limited amount of temporary history, not stored on disk]\n3. 'on' [history stored in a permanent database, if available]\n4. 'default' [use the server default]": "$bDM-HISTORY$b\n'dm-history' is only effective for always-on clients. It lets you control\nhow the history of your direct messages is stored. Your options are:\n1. 'off' [no history]\n2. 'ephemeral' [a limited amount of temporary history, not stored on disk]\n3. 'on' [history stored in a permanent database, if available]\n4. 'default' [use the server default]",
"$bDROP$b de-links your current (or the given) nickname from your user account.": "$bDROP$b de-links your current (or the given) nickname from your user account.",
"$bEMAIL$b\n'email' controls the e-mail address associated with your account (if the\nserver operator allows it, this address can be used for password resets).\nAs an additional security measure, if you have a password set, you must\nprovide it as an additional argument to $bSET$b, for example,\nSET EMAIL test@example.com hunter2": "$bEMAIL$b\n'email' controls the e-mail address associated with your account (if the\nserver operator allows it, this address can be used for password resets).\nAs an additional security measure, if you have a password set, you must\nprovide it as an additional argument to $bSET$b, for example,\nSET EMAIL test@example.com hunter2",
"$bNOSTR$b\n'nostr' controls the nostr identifier associated with your account.\nThis can be a NIP-05 address, npub key, or hex pubkey. As an additional\nsecurity measure, if you have a password set, you must provide it as an\nadditional argument to $bSET$b, for example,\nSET NOSTR alice@example.com hunter2": "$bNOSTR$b\n'nostr' controls the nostr identifier associated with your account.\nThis can be a NIP-05 address, npub key, or hex pubkey. As an additional\nsecurity measure, if you have a password set, you must provide it as an\nadditional argument to $bSET$b, for example,\nSET NOSTR alice@example.com hunter2",
"$bENFORCE$b\n'enforce' lets you specify a custom enforcement mechanism for your registered\nnicknames. Your options are:\n1. 'none' [no enforcement, overriding the server default]\n2. 'strict' [you must already be authenticated to use the nick]\n3. 'default' [use the server default]": "$bENFORCE$b\n'enforce' lets you specify a custom enforcement mechanism for your registered\nnicknames. Your options are:\n1. 'none' [no enforcement, overriding the server default]\n2. 'strict' [you must already be authenticated to use the nick]\n3. 'default' [use the server default]",
"$bERASE$b erases all records of an account, allowing reuse.": "$bERASE$b erases all records of an account, allowing reuse.",
"$bGET$b queries the current values of your account settings": "$bGET$b queries the current values of your account settings",
@@ -18,15 +18,15 @@
"$bLIST$b searches the list of registered nicknames.": "$bLIST$b searches the list of registered nicknames.",
"$bMULTICLIENT$b\nIf 'multiclient' is enabled and you are already logged in and using a nick, a\nsecond client of yours that authenticates with SASL and requests the same nick\nis allowed to attach to the nick as well (this is comparable to the behavior\nof IRC \"bouncers\" like ZNC). Your options are 'on' (allow this behavior),\n'off' (disallow it), and 'default' (use the server default value).": "$bMULTICLIENT$b\nIf 'multiclient' is enabled and you are already logged in and using a nick, a\nsecond client of yours that authenticates with SASL and requests the same nick\nis allowed to attach to the nick as well (this is comparable to the behavior\nof IRC \"bouncers\" like ZNC). Your options are 'on' (allow this behavior),\n'off' (disallow it), and 'default' (use the server default value).",
"$bPASSWD$b lets you change your password.": "$bPASSWD$b lets you change your password.",
"$bREGISTER$b lets you register a user account.": "$bREGISTER$b lets you register a user account.",
"$bREGISTER$b lets you register a user account.": "$bREGISTER$b lets you register a user account with nostr verification.",
"$bRENAME$b renames an account": "$bRENAME$b renames an account",
"$bREPLAY-JOINS$b\n'replay-joins' controls whether replayed channel history will include\nlines for join and part. This provides more information about the context of\nmessages, but may be spammy. Your options are 'always' and the default of\n'commands-only' (the messages will be replayed in CHATHISTORY output, but not\nduring autoreplay).": "$bREPLAY-JOINS$b\n'replay-joins' controls whether replayed channel history will include\nlines for join and part. This provides more information about the context of\nmessages, but may be spammy. Your options are 'always' and the default of\n'commands-only' (the messages will be replayed in CHATHISTORY output, but not\nduring autoreplay).",
"$bRESETPASS$b completes an email-based password reset": "$bRESETPASS$b completes an email-based password reset",
"$bRESETPASS$b completes a nostr-based password reset": "$bRESETPASS$b completes a nostr-based password reset",
"$bSADROP$b forcibly de-links the given nickname from its user account.": "$bSADROP$b forcibly de-links the given nickname from its user account.",
"$bSAGET$b queries the current values of another user's account settings": "$bSAGET$b queries the current values of another user's account settings",
"$bSAREGISTER$b registers an account on someone else's behalf.": "$bSAREGISTER$b registers an account on someone else's behalf.",
"$bSASET$b modifies another user's account settings": "$bSASET$b modifies another user's account settings",
"$bSENDPASS$b initiates an email-based password reset": "$bSENDPASS$b initiates an email-based password reset",
"$bSENDPASS$b initiates a nostr-based password reset": "$bSENDPASS$b initiates a nostr-based password reset",
"$bSET$b modifies your account settings": "$bSET$b modifies your account settings",
"$bSUSPEND$b manages account suspensions": "$bSUSPEND$b manages account suspensions",
"$bUNREGISTER$b lets you delete your user account.": "$bUNREGISTER$b lets you delete your user account.",
@@ -39,7 +39,7 @@
"Password incorrect": "Password incorrect",
"Passwords do not match": "Passwords do not match",
"Successfully reset account password": "Successfully reset account password",
"Successfully sent password reset email": "Successfully sent password reset email",
"Successfully sent password reset via nostr DM": "Successfully sent password reset via nostr DM",
"Syntax $bSET <setting> <value>$b\n\nSET modifies your account settings. The following settings are available:": "Syntax $bSET <setting> <value>$b\n\nSET modifies your account settings. The following settings are available:",
"Syntax: $bCERT <LIST | ADD | DEL> [account] [certfp]$b\n\nCERT examines or modifies the SHA-256 TLS certificate fingerprints that can\nbe used to log into an account. Specifically, $bCERT LIST$b lists the\nauthorized fingerprints, $bCERT ADD <fingerprint>$b adds a new fingerprint, and\n$bCERT DEL <fingerprint>$b removes a fingerprint. If you're an IRC operator\nwith the correct permissions, you can act on another user's account, for\nexample with $bCERT ADD <account> <fingerprint>$b. See the operator manual\nfor instructions on how to compute the fingerprint.": "Syntax: $bCERT <LIST | ADD | DEL> [account] [certfp]$b\n\nCERT examines or modifies the SHA-256 TLS certificate fingerprints that can\nbe used to log into an account. Specifically, $bCERT LIST$b lists the\nauthorized fingerprints, $bCERT ADD <fingerprint>$b adds a new fingerprint, and\n$bCERT DEL <fingerprint>$b removes a fingerprint. If you're an IRC operator\nwith the correct permissions, you can act on another user's account, for\nexample with $bCERT ADD <account> <fingerprint>$b. See the operator manual\nfor instructions on how to compute the fingerprint.",
"Syntax: $bCLIENTS LIST [nickname]$b\n\nCLIENTS LIST shows information about the clients currently attached, via\nthe server's multiclient functionality, to your nickname. An administrator\ncan use this command to list another user's clients.\n\nSyntax: $bCLIENTS LOGOUT [nickname] [client_id/all]$b\n\nCLIENTS LOGOUT detaches a single client, or all clients currently attached\nto your nickname. An administrator can use this command to logout another\nuser's clients.": "Syntax: $bCLIENTS LIST [nickname]$b\n\nCLIENTS LIST shows information about the clients currently attached, via\nthe server's multiclient functionality, to your nickname. An administrator\ncan use this command to list another user's clients.\n\nSyntax: $bCLIENTS LOGOUT [nickname] [client_id/all]$b\n\nCLIENTS LOGOUT detaches a single client, or all clients currently attached\nto your nickname. An administrator can use this command to logout another\nuser's clients.",
@@ -53,19 +53,19 @@
"Syntax: $bINFO [username]$b\n\nINFO gives you information about the given (or your own) user account.": "Syntax: $bINFO [username]$b\n\nINFO gives you information about the given (or your own) user account.",
"Syntax: $bLIST [regex]$b\n\nLIST returns the list of registered nicknames, which match the given regex.\nIf no regex is provided, all registered nicknames are returned.": "Syntax: $bLIST [regex]$b\n\nLIST returns the list of registered nicknames, which match the given regex.\nIf no regex is provided, all registered nicknames are returned.",
"Syntax: $bPASSWD <current> <new> <new_again>$b\nOr: $bPASSWD <username> <new>$b\n\nPASSWD lets you change your account password. You must supply your current\npassword and confirm the new one by typing it twice. If you're an IRC operator\nwith the correct permissions, you can use PASSWD to reset someone else's\npassword by supplying their username and then the desired password. To\nindicate an empty password, use * instead.": "Syntax: $bPASSWD <current> <new> <new_again>$b\nOr: $bPASSWD <username> <new>$b\n\nPASSWD lets you change your account password. You must supply your current\npassword and confirm the new one by typing it twice. If you're an IRC operator\nwith the correct permissions, you can use PASSWD to reset someone else's\npassword by supplying their username and then the desired password. To\nindicate an empty password, use * instead.",
"Syntax: $bREGISTER <password> [email]$b\n\nREGISTER lets you register your current nickname as a user account. If the\nserver allows anonymous registration, you can omit the e-mail address.\n\nIf you are currently logged in with a TLS client certificate and wish to use\nit instead of a password to log in, send * as the password.": "Syntax: $bREGISTER <password> [email]$b\n\nREGISTER lets you register your current nickname as a user account. If the\nserver allows anonymous registration, you can omit the e-mail address.\n\nIf you are currently logged in with a TLS client certificate and wish to use\nit instead of a password to log in, send * as the password.",
"Syntax: $bREGISTER <password> [nostr-identifier]$b\n\nREGISTER lets you register your current nickname as a user account using nostr\nverification. You can provide a nostr identifier in one of these formats:\n\n• NIP-05 address: alice@example.com\n• npub key: npub1abc123def456...\n• hex pubkey: 3bf0c63fcb93c5ef2f068d70b8d70d963b649d75...\n\nIf the server allows anonymous registration, you can omit the nostr identifier.\nIf you are currently logged in with a TLS client certificate and wish to use\nit instead of a password to log in, send * as the password.": "Syntax: $bREGISTER <password> [nostr-identifier]$b\n\nREGISTER lets you register your current nickname as a user account using nostr\nverification. You can provide a nostr identifier in one of these formats:\n\n• NIP-05 address: alice@example.com\n• npub key: npub1abc123def456...\n• hex pubkey: 3bf0c63fcb93c5ef2f068d70b8d70d963b649d75...\n\nIf the server allows anonymous registration, you can omit the nostr identifier.\nIf you are currently logged in with a TLS client certificate and wish to use\nit instead of a password to log in, send * as the password.",
"Syntax: $bRENAME <account> <newname>$b\n\nRENAME allows a server administrator to change the name of an account.\nCurrently, you can only change the canonical casefolding of an account\n(e.g., you can change \"Alice\" to \"alice\", but not \"Alice\" to \"Amanda\").": "Syntax: $bRENAME <account> <newname>$b\n\nRENAME allows a server administrator to change the name of an account.\nCurrently, you can only change the canonical casefolding of an account\n(e.g., you can change \"Alice\" to \"alice\", but not \"Alice\" to \"Amanda\").",
"Syntax: $bRESETPASS <account> <code> <password>$b\n\nRESETPASS resets an account password, using a reset code that was emailed as\nthe result of a previous $bSENDPASS$b command.": "Syntax: $bRESETPASS <account> <code> <password>$b\n\nRESETPASS resets an account password, using a reset code that was emailed as\nthe result of a previous $bSENDPASS$b command.",
"Syntax: $bRESETPASS <account> <code> <password>$b\n\nRESETPASS resets an account password, using a reset code that was sent via\nnostr DM as the result of a previous $bSENDPASS$b command.": "Syntax: $bRESETPASS <account> <code> <password>$b\n\nRESETPASS resets an account password, using a reset code that was sent via\nnostr DM as the result of a previous $bSENDPASS$b command.",
"Syntax: $bSADROP <nickname>$b\n\nSADROP forcibly de-links the given nickname from the attached user account.": "Syntax: $bSADROP <nickname>$b\n\nSADROP forcibly de-links the given nickname from the attached user account.",
"Syntax: $bSAGET <account> <setting>$b\n\nSAGET queries the values of someone else's account settings. For more\ninformation on the settings and their possible values, see HELP SET.": "Syntax: $bSAGET <account> <setting>$b\n\nSAGET queries the values of someone else's account settings. For more\ninformation on the settings and their possible values, see HELP SET.",
"Syntax: $bSAREGISTER <username> [password]$b\n\nSAREGISTER registers an account on someone else's behalf.\nThis is for use in configurations that require SASL for all connections;\nan administrator can set use this command to set up user accounts.": "Syntax: $bSAREGISTER <username> [password]$b\n\nSAREGISTER registers an account on someone else's behalf.\nThis is for use in configurations that require SASL for all connections;\nan administrator can set use this command to set up user accounts.",
"Syntax: $bSASET <account> <setting> <value>$b\n\nSASET modifies the values of someone else's account settings. For more\ninformation on the settings and their possible values, see HELP SET.": "Syntax: $bSASET <account> <setting> <value>$b\n\nSASET modifies the values of someone else's account settings. For more\ninformation on the settings and their possible values, see HELP SET.",
"Syntax: $bSENDPASS <account>$b\n\nSENDPASS sends a password reset email to the email address associated with\nthe target account. The reset code in the email can then be used with the\n$bRESETPASS$b command.": "Syntax: $bSENDPASS <account>$b\n\nSENDPASS sends a password reset email to the email address associated with\nthe target account. The reset code in the email can then be used with the\n$bRESETPASS$b command.",
"Syntax: $bSENDPASS <account>$b\n\nSENDPASS sends a password reset code via nostr DM to the nostr identifier\nassociated with the target account. The reset code can then be used with the\n$bRESETPASS$b command.": "Syntax: $bSENDPASS <account>$b\n\nSENDPASS sends a password reset code via nostr DM to the nostr identifier\nassociated with the target account. The reset code can then be used with the\n$bRESETPASS$b command.",
"Syntax: $bSESSIONS [nickname]$b\n\nSESSIONS is an alias for $bCLIENTS LIST$b. See the help entry for $bCLIENTS$b\nfor more information.": "Syntax: $bSESSIONS [nickname]$b\n\nSESSIONS is an alias for $bCLIENTS LIST$b. See the help entry for $bCLIENTS$b\nfor more information.",
"Syntax: $bSUSPEND ADD <nickname> [DURATION duration] [reason]$b\n $bSUSPEND DEL <nickname>$b\n $bSUSPEND LIST$b\n\nSuspending an account disables it (preventing new logins) and disconnects\nall associated clients. You can specify a time limit or a reason for\nthe suspension. The $bDEL$b subcommand reverses a suspension, and the $bLIST$b\ncommand lists all current suspensions.": "Syntax: $bSUSPEND ADD <nickname> [DURATION duration] [reason]$b\n $bSUSPEND DEL <nickname>$b\n $bSUSPEND LIST$b\n\nSuspending an account disables it (preventing new logins) and disconnects\nall associated clients. You can specify a time limit or a reason for\nthe suspension. The $bDEL$b subcommand reverses a suspension, and the $bLIST$b\ncommand lists all current suspensions.",
"Syntax: $bUNREGISTER <username> [code]$b\n\nUNREGISTER lets you delete your user account (or someone else's, if you're an\nIRC operator with the correct permissions). To prevent accidental\nunregistrations, a verification code is required; invoking the command without\na code will display the necessary code.": "Syntax: $bUNREGISTER <username> [code]$b\n\nUNREGISTER lets you delete your user account (or someone else's, if you're an\nIRC operator with the correct permissions). To prevent accidental\nunregistrations, a verification code is required; invoking the command without\na code will display the necessary code.",
"Syntax: $bVERIFY <username> <code>$b\n\nVERIFY lets you complete an account registration, if the server requires email\nor other verification.": "Syntax: $bVERIFY <username> <code>$b\n\nVERIFY lets you complete an account registration, if the server requires email\nor other verification.",
"That account is not associated with an email address": "That account is not associated with an email address",
"Syntax: $bVERIFY <username> <code>$b\n\nVERIFY lets you complete an account registration, if the server requires nostr\nverification. You will receive a verification code via nostr DM.": "Syntax: $bVERIFY <username> <code>$b\n\nVERIFY lets you complete an account registration, if the server requires nostr\nverification. You will receive a verification code via nostr DM.",
"That account is not associated with a nostr identifier": "That account is not associated with a nostr identifier",
"Try again later": "Try again later",
"You must supply a password": "You must supply a password",
"You're not logged into an account": "You're not logged into an account"
+2
View File
@@ -0,0 +1,2 @@
workdir/
fasturl-fuzz.zip
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 V-X
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
gen:
ragel -G2 -Z parser.rl
+8867
View File
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
// Package fasturl is a Go URL parser using a [Ragel](http://www.colm.net/open-source/ragel/) state-machine instead of regex, or the built in standard library `url.Parse`.
//
package fasturl
import "fmt"
%%{
machine url_parser;
action mark { mark = fpc }
action mark_port { port_mark = fpc }
action save_port {
if port_mark > host_mark{
u.Port = data[port_mark:fpc]
}
}
action save_scheme {
u.Protocol = data[0:fpc-1]
}
action mark_host {
host_mark = fpc;
}
action save_host {
u.Host = data[host_mark:fpc]
}
action save_query {
if u.Query == "" {
u.Query = data[mark:fpc]
}
}
action save_path {
if u.Path == "" {
u.Path = data[mark:fpc]
}
}
action save_fragment {
u.Fragment = data[mark:fpc]
}
pct_encoded = "%" xdigit xdigit;
gen_delims = ":" | "/" | "?" | "#" | "[" | "]" | "@";
sub_delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=";
reserved = gen_delims | sub_delims;
unreserved = alpha | digit | "-" | "." | "_" | "~";
# many clients don't encode these, e.g. curl, wget, ...
delims = "<" | ">" | "%" | "#" | '"';
unwise = " " | "{" | "}" | "|" | "\\" | "^" | "[" | "]" | "`";
pchar = unreserved | pct_encoded | sub_delims | ":" | "@" | delims | unwise;
slash = "/" | "\\";
path = (slash ( (pchar - ("?" | "#")) + ( slash (pchar - ("?" | "#"))* )* )? ) >mark %save_path;
drivepath = ( (slash|(alpha ":" slash)) ( (pchar - ("?" | "#")) + ( slash (pchar - ("?" | "#"))* )* )? ) >mark %save_path;
scheme = (alpha ( alpha | digit | "+" | "-" | "." )*);
#simple ipv4 address
dec_octet = digit{1,3};
IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet;
IPvFuture = "v" xdigit+ "." ( unreserved | sub_delims | ":" )+;
# simple ipv6 address
IPv6address = (":" | xdigit)+ IPv4address?;
IP_literal = "[" ( IPv6address | IPvFuture ) "]";
reg_name = ( unreserved | pct_encoded | sub_delims )+;
userinfo = ( unreserved | pct_encoded | sub_delims | ":" | "@" )*;
host = IP_literal | IPv4address | reg_name;
port = (pchar - ("/" | "?" | "#")){1,5} ;
authority = ( userinfo "@" )? (host >mark_host %save_host) ( ":" port >mark_port %save_port)?;
fragment = ( pchar | "/" | "?" )* >mark %save_fragment;
query = (pchar - "#")* >mark %save_query;
full_ref = drivepath? ( "?" query )? ( "#" fragment )?;
relative_ref = path? ( "?" query )? ( "#" fragment )?;
absolute_hier_part = ("//")? authority? full_ref?;
hier_part = ("//")? authority? relative_ref?;
absolute_URI = ((scheme ":") >mark %save_scheme)? absolute_hier_part;
URI = absolute_URI | relative_ref;
main := URI;
write data;
}%%
// URL represents the different parts of a parsed URL
type URL struct {
Protocol string
Host string
Port string
Path string
Query string
Fragment string
}
// ParseURL parses a given URL and returns a `URL` representing the different parts
func ParseURL(data string) (*URL, error){
mark, host_mark, port_mark, cs, p, pe, eof := 0, 0, 0, url_parser_en_main, 0, len(data), len(data)
u := &URL{}
%% write init;
%% write exec;
if cs < url_parser_first_final {
return nil, fmt.Errorf("Failed to match URL")
} else {
return u, nil
}
}
+10
View File
@@ -0,0 +1,10 @@
// +build gofuzz
package fasturl
func Fuzz(data []byte) int {
if _, err := ParseURL(string(data)); err != nil {
return 0
}
return 1
}
+36
View File
@@ -0,0 +1,36 @@
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/ImVexed/fasturl"><img src="https://goreportcard.com/badge/github.com/ImVexed/fasturl?style=flat-square"></a>
<a title="Doc for fasturl" target="_blank" href="https://pkg.go.dev/github.com/ImVexed/fasturl?tab=doc"><img src="https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs"></a>
<a title="fasturl on Sourcegraph" target="_blank" href="https://sourcegraph.com/github.com/ImVexed/fasturl?badge"><img src="https://sourcegraph.com/github.com/ImVexed/fasturl/-/badge.svg?style=flat-square"></a>
## Fast URL
FastURL is a Go URL parser using a [Ragel](http://www.colm.net/open-source/ragel/) state-machine instead of regex, or the built in standard library `url.Parse`.
## Why?
*S P E E D*
## Examples
See our [tests](https://github.com/ImVexed/fasturl/blob/master/parser_test.go#L45)
## Benchmarks
## ns/op
![](/_images/ns.svg)
## B/op
![](/_images/b.svg)
## Raw:
```
goos: linux
goarch: amd64
pkg: github.com/ImVexed/fasturl
BenchmarkRegex-32 464509 2557 ns/op 530 B/op 3 allocs/op
BenchmarkRagel-32 5350304 225 ns/op 96 B/op 1 allocs/op
BenchmarkStd-32 2225313 537 ns/op 128 B/op 1 allocs/op
PASS
ok github.com/ImVexed/fasturl 4.405s
```
## How does this work?
Lots of goto's and determinism, feel free to zoom
![](/_images/graph.svg)
## Credits
[maximecaron](https://github.com/maximecaron) - Creating the initial ragael parser
+16
View File
@@ -0,0 +1,16 @@
ISC License
Copyright (c) 2013-2023 The btcsuite developers
Copyright (c) 2015-2016 The Decred developers
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+40
View File
@@ -0,0 +1,40 @@
btcec
=====
[![Build Status](https://github.com/btcsuite/btcd/workflows/Build%20and%20Test/badge.svg)](https://github.com/btcsuite/btcd/actions)
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![GoDoc](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2?status.png)](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2)
Package btcec implements elliptic curve cryptography needed for working with
Bitcoin (secp256k1 only for now). It is designed so that it may be used with the
standard crypto/ecdsa packages provided with go. A comprehensive suite of test
is provided to ensure proper functionality. Package btcec was originally based
on work from ThePiachu which is licensed under the same terms as Go, but it has
significantly diverged since then. The btcsuite developers original is licensed
under the liberal ISC license.
Although this package was primarily written for btcd, it has intentionally been
designed so it can be used as a standalone package for any projects needing to
use secp256k1 elliptic curve cryptography.
## Installation and Updating
```bash
$ go install -u -v github.com/btcsuite/btcd/btcec/v2
```
## Examples
* [Sign Message](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2#example-package--SignMessage)
Demonstrates signing a message with a secp256k1 private key that is first
parsed form raw bytes and serializing the generated signature.
* [Verify Signature](https://pkg.go.dev/github.com/btcsuite/btcd/btcec/v2#example-package--VerifySignature)
Demonstrates verifying a secp256k1 signature against a public key that is
first parsed from raw bytes. The signature is also parsed from raw bytes.
## License
Package btcec is licensed under the [copyfree](http://copyfree.org) ISC License
except for btcec.go and btcec_test.go which is under the same license as Go.
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Copyright 2011 ThePiachu. All rights reserved.
// Copyright 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 btcec
// References:
// [SECG]: Recommended Elliptic Curve Domain Parameters
// http://www.secg.org/sec2-v2.pdf
//
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
// This package operates, internally, on Jacobian coordinates. For a given
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole
// calculation can be performed within the transform (as in ScalarMult and
// ScalarBaseMult). But even for Add and Double, it's faster to apply and
// reverse the transform than to operate in affine coordinates.
import (
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// KoblitzCurve provides an implementation for secp256k1 that fits the ECC
// Curve interface from crypto/elliptic.
type KoblitzCurve = secp.KoblitzCurve
// S256 returns a Curve which implements secp256k1.
func S256() *KoblitzCurve {
return secp.S256()
}
// CurveParams contains the parameters for the secp256k1 curve.
type CurveParams = secp.CurveParams
// Params returns the secp256k1 curve parameters for convenience.
func Params() *CurveParams {
return secp.Params()
}
// Generator returns the public key at the Generator Point.
func Generator() *PublicKey {
var (
result JacobianPoint
k secp.ModNScalar
)
k.SetInt(1)
ScalarBaseMultNonConst(&k, &result)
result.ToAffine()
return NewPublicKey(&result.X, &result.Y)
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcec
import (
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// GenerateSharedSecret generates a shared secret based on a private key and a
// public key using Diffie-Hellman key exchange (ECDH) (RFC 4753).
// RFC5903 Section 9 states we should only return x.
func GenerateSharedSecret(privkey *PrivateKey, pubkey *PublicKey) []byte {
return secp.GenerateSharedSecret(privkey, pubkey)
}
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2015-2021 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
package btcec
import (
"fmt"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// JacobianPoint is an element of the group formed by the secp256k1 curve in
// Jacobian projective coordinates and thus represents a point on the curve.
type JacobianPoint = secp.JacobianPoint
// infinityPoint is the jacobian representation of the point at infinity.
var infinityPoint JacobianPoint
// MakeJacobianPoint returns a Jacobian point with the provided X, Y, and Z
// coordinates.
func MakeJacobianPoint(x, y, z *FieldVal) JacobianPoint {
return secp.MakeJacobianPoint(x, y, z)
}
// AddNonConst adds the passed Jacobian points together and stores the result
// in the provided result param in *non-constant* time.
func AddNonConst(p1, p2, result *JacobianPoint) {
secp.AddNonConst(p1, p2, result)
}
// DecompressY attempts to calculate the Y coordinate for the given X
// coordinate such that the result pair is a point on the secp256k1 curve. It
// adjusts Y based on the desired oddness and returns whether or not it was
// successful since not all X coordinates are valid.
//
// The magnitude of the provided X coordinate field val must be a max of 8 for
// a correct result. The resulting Y field val will have a max magnitude of 2.
func DecompressY(x *FieldVal, odd bool, resultY *FieldVal) bool {
return secp.DecompressY(x, odd, resultY)
}
// DoubleNonConst doubles the passed Jacobian point and stores the result in
// the provided result parameter in *non-constant* time.
//
// NOTE: The point must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func DoubleNonConst(p, result *JacobianPoint) {
secp.DoubleNonConst(p, result)
}
// ScalarBaseMultNonConst multiplies k*G where G is the base point of the group
// and k is a big endian integer. The result is stored in Jacobian coordinates
// (x1, y1, z1).
//
// NOTE: The resulting point will be normalized.
func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) {
secp.ScalarBaseMultNonConst(k, result)
}
// ScalarMultNonConst multiplies k*P where k is a big endian integer modulo the
// curve order and P is a point in Jacobian projective coordinates and stores
// the result in the provided Jacobian point.
//
// NOTE: The point must be normalized for this function to return the correct
// result. The resulting point will be normalized.
func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) {
secp.ScalarMultNonConst(k, point, result)
}
// ParseJacobian parses a byte slice point as a secp.Publickey and returns the
// pubkey as a JacobianPoint. If the nonce is a zero slice, the infinityPoint
// is returned.
func ParseJacobian(point []byte) (JacobianPoint, error) {
var result JacobianPoint
if len(point) != 33 {
str := fmt.Sprintf("invalid nonce: invalid length: %v",
len(point))
return JacobianPoint{}, makeError(secp.ErrPubKeyInvalidLen, str)
}
if point[0] == 0x00 {
return infinityPoint, nil
}
noncePk, err := secp.ParsePubKey(point)
if err != nil {
return JacobianPoint{}, err
}
noncePk.AsJacobian(&result)
return result, nil
}
// JacobianToByteSlice converts the passed JacobianPoint to a Pubkey
// and serializes that to a byte slice. If the JacobianPoint is the infinity
// point, a zero slice is returned.
func JacobianToByteSlice(point JacobianPoint) []byte {
if point.X == infinityPoint.X && point.Y == infinityPoint.Y {
return make([]byte, 33)
}
point.ToAffine()
return NewPublicKey(
&point.X, &point.Y,
).SerializeCompressed()
}
// GeneratorJacobian sets the passed JacobianPoint to the Generator Point.
func GeneratorJacobian(jacobian *JacobianPoint) {
var k ModNScalar
k.SetInt(1)
ScalarBaseMultNonConst(&k, jacobian)
}
+21
View File
@@ -0,0 +1,21 @@
// 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 btcec implements support for the elliptic curves needed for bitcoin.
Bitcoin uses elliptic curve cryptography using koblitz curves
(specifically secp256k1) for cryptographic functions. See
http://www.secg.org/collateral/sec2_final.pdf for details on the
standard.
This package provides the data structures and functions implementing the
crypto/elliptic Curve interface in order to permit using these curves
with the standard crypto/ecdsa package provided with go. Helper
functionality is provided to parse signatures and public keys from
standard formats. It was designed for use with btcd, but should be
general enough for other uses of elliptic curve crypto. It was originally based
on some initial work by ThePiachu, but has significantly diverged since then.
*/
package btcec
+24
View File
@@ -0,0 +1,24 @@
// Copyright (c) 2013-2021 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
package btcec
import (
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// Error identifies an error related to public key cryptography using a
// sec256k1 curve. It has full support for errors.Is and errors.As, so the
// caller can ascertain the specific reason for the error by checking the
// underlying error.
type Error = secp.Error
// ErrorKind identifies a kind of error. It has full support for errors.Is and
// errors.As, so the caller can directly check against an error kind when
// determining the reason for an error.
type ErrorKind = secp.ErrorKind
// makeError creates an secp.Error given a set of arguments.
func makeError(kind ErrorKind, desc string) Error {
return Error{Err: kind, Description: desc}
}
+43
View File
@@ -0,0 +1,43 @@
package btcec
import secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
// FieldVal implements optimized fixed-precision arithmetic over the secp256k1
// finite field. This means all arithmetic is performed modulo
// '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'.
//
// WARNING: Since it is so important for the field arithmetic to be extremely
// fast for high performance crypto, this type does not perform any validation
// of documented preconditions where it ordinarily would. As a result, it is
// IMPERATIVE for callers to understand some key concepts that are described
// below and ensure the methods are called with the necessary preconditions
// that each method is documented with. For example, some methods only give the
// correct result if the field value is normalized and others require the field
// values involved to have a maximum magnitude and THERE ARE NO EXPLICIT CHECKS
// TO ENSURE THOSE PRECONDITIONS ARE SATISFIED. This does, unfortunately, make
// the type more difficult to use correctly and while I typically prefer to
// ensure all state and input is valid for most code, this is a bit of an
// exception because those extra checks really add up in what ends up being
// critical hot paths.
//
// The first key concept when working with this type is normalization. In order
// to avoid the need to propagate a ton of carries, the internal representation
// provides additional overflow bits for each word of the overall 256-bit
// value. This means that there are multiple internal representations for the
// same value and, as a result, any methods that rely on comparison of the
// value, such as equality and oddness determination, require the caller to
// provide a normalized value.
//
// The second key concept when working with this type is magnitude. As
// previously mentioned, the internal representation provides additional
// overflow bits which means that the more math operations that are performed
// on the field value between normalizations, the more those overflow bits
// accumulate. The magnitude is effectively that maximum possible number of
// those overflow bits that could possibly be required as a result of a given
// operation. Since there are only a limited number of overflow bits available,
// this implies that the max possible magnitude MUST be tracked by the caller
// and the caller MUST normalize the field value if a given operation would
// cause the magnitude of the result to exceed the max allowed value.
//
// IMPORTANT: The max allowed magnitude of a field value is 64.
type FieldVal = secp.FieldVal
+45
View File
@@ -0,0 +1,45 @@
// Copyright (c) 2013-2021 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
package btcec
import (
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// ModNScalar implements optimized 256-bit constant-time fixed-precision
// arithmetic over the secp256k1 group order. This means all arithmetic is
// performed modulo:
//
// 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
//
// It only implements the arithmetic needed for elliptic curve operations,
// however, the operations that are not implemented can typically be worked
// around if absolutely needed. For example, subtraction can be performed by
// adding the negation.
//
// Should it be absolutely necessary, conversion to the standard library
// math/big.Int can be accomplished by using the Bytes method, slicing the
// resulting fixed-size array, and feeding it to big.Int.SetBytes. However,
// that should typically be avoided when possible as conversion to big.Ints
// requires allocations, is not constant time, and is slower when working modulo
// the group order.
type ModNScalar = secp.ModNScalar
// NonceRFC6979 generates a nonce deterministically according to RFC 6979 using
// HMAC-SHA256 for the hashing function. It takes a 32-byte hash as an input
// and returns a 32-byte nonce to be used for deterministic signing. The extra
// and version arguments are optional, but allow additional data to be added to
// the input of the HMAC. When provided, the extra data must be 32-bytes and
// version must be 16 bytes or they will be ignored.
//
// Finally, the extraIterations parameter provides a method to produce a stream
// of deterministic nonces to ensure the signing code is able to produce a nonce
// that results in a valid signature in the extremely unlikely event the
// original nonce produced results in an invalid signature (e.g. R == 0).
// Signing code should start with 0 and increment it if necessary.
func NonceRFC6979(privKey []byte, hash []byte, extra []byte, version []byte,
extraIterations uint32) *ModNScalar {
return secp.NonceRFC6979(privKey, hash, extra, version, extraIterations)
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcec
import (
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// PrivateKey wraps an ecdsa.PrivateKey as a convenience mainly for signing
// things with the private key without having to directly import the ecdsa
// package.
type PrivateKey = secp.PrivateKey
// PrivKeyFromBytes returns a private and public key for `curve' based on the
// private key passed as an argument as a byte slice.
func PrivKeyFromBytes(pk []byte) (*PrivateKey, *PublicKey) {
privKey := secp.PrivKeyFromBytes(pk)
return privKey, privKey.PubKey()
}
// NewPrivateKey is a wrapper for ecdsa.GenerateKey that returns a PrivateKey
// instead of the normal ecdsa.PrivateKey.
func NewPrivateKey() (*PrivateKey, error) {
return secp.GeneratePrivateKey()
}
// PrivKeyFromScalar instantiates a new private key from a scalar encoded as a
// big integer.
func PrivKeyFromScalar(key *ModNScalar) *PrivateKey {
return &PrivateKey{Key: *key}
}
// PrivKeyBytesLen defines the length in bytes of a serialized private key.
const PrivKeyBytesLen = 32
+88
View File
@@ -0,0 +1,88 @@
// 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 btcec
import (
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// These constants define the lengths of serialized public keys.
const (
// PubKeyBytesLenCompressed is the bytes length of a serialized compressed
// public key.
PubKeyBytesLenCompressed = 33
)
const (
pubkeyCompressed byte = 0x2 // y_bit + x coord
pubkeyUncompressed byte = 0x4 // x coord + y coord
pubkeyHybrid byte = 0x6 // y_bit + x coord + y coord
)
// IsCompressedPubKey returns true the passed serialized public key has
// been encoded in compressed format, and false otherwise.
func IsCompressedPubKey(pubKey []byte) bool {
// The public key is only compressed if it is the correct length and
// the format (first byte) is one of the compressed pubkey values.
return len(pubKey) == PubKeyBytesLenCompressed &&
(pubKey[0]&^byte(0x1) == pubkeyCompressed)
}
// ParsePubKey parses a public key for a koblitz curve from a bytestring into a
// ecdsa.Publickey, verifying that it is valid. It supports compressed,
// uncompressed and hybrid signature formats.
func ParsePubKey(pubKeyStr []byte) (*PublicKey, error) {
return secp.ParsePubKey(pubKeyStr)
}
// PublicKey is an ecdsa.PublicKey with additional functions to
// serialize in uncompressed, compressed, and hybrid formats.
type PublicKey = secp.PublicKey
// NewPublicKey instantiates a new public key with the given x and y
// coordinates.
//
// It should be noted that, unlike ParsePubKey, since this accepts arbitrary x
// and y coordinates, it allows creation of public keys that are not valid
// points on the secp256k1 curve. The IsOnCurve method of the returned instance
// can be used to determine validity.
func NewPublicKey(x, y *FieldVal) *PublicKey {
return secp.NewPublicKey(x, y)
}
// SerializedKey is a type for representing a public key in its compressed
// serialized form.
//
// NOTE: This type is useful when using public keys as keys in maps.
type SerializedKey [PubKeyBytesLenCompressed]byte
// ToPubKey returns the public key parsed from the serialized key.
func (s SerializedKey) ToPubKey() (*PublicKey, error) {
return ParsePubKey(s[:])
}
// SchnorrSerialized returns the Schnorr serialized, x-only 32-byte
// representation of the serialized key.
func (s SerializedKey) SchnorrSerialized() [32]byte {
var serializedSchnorr [32]byte
copy(serializedSchnorr[:], s[1:])
return serializedSchnorr
}
// CopyBytes returns a copy of the underlying array as a byte slice.
func (s SerializedKey) CopyBytes() []byte {
c := make([]byte, PubKeyBytesLenCompressed)
copy(c, s[:])
return c
}
// ToSerialized serializes a public key into its compressed form.
func ToSerialized(pubKey *PublicKey) SerializedKey {
var serialized SerializedKey
copy(serialized[:], pubKey.SerializeCompressed())
return serialized
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package schnorr
import (
ecdsa_schnorr "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr"
)
// ErrorKind identifies a kind of error. It has full support for errors.Is
// and errors.As, so the caller can directly check against an error kind
// when determining the reason for an error.
type ErrorKind = ecdsa_schnorr.ErrorKind
// Error identifies an error related to a schnorr signature. It has full
// support for errors.Is and errors.As, so the caller can ascertain the
// specific reason for the error by checking the underlying error.
type Error = ecdsa_schnorr.Error
// signatureError creates an Error given a set of arguments.
func signatureError(kind ErrorKind, desc string) Error {
return Error{Err: kind, Description: desc}
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package schnorr
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// These constants define the lengths of serialized public keys.
const (
PubKeyBytesLen = 32
)
// ParsePubKey parses a public key for a koblitz curve from a bytestring into a
// btcec.Publickey, verifying that it is valid. It only supports public keys in
// the BIP-340 32-byte format.
func ParsePubKey(pubKeyStr []byte) (*btcec.PublicKey, error) {
if pubKeyStr == nil {
err := fmt.Errorf("nil pubkey byte string")
return nil, err
}
if len(pubKeyStr) != PubKeyBytesLen {
err := fmt.Errorf("bad pubkey byte string size (want %v, have %v)",
PubKeyBytesLen, len(pubKeyStr))
return nil, err
}
// We'll manually prepend the compressed byte so we can re-use the
// existing pubkey parsing routine of the main btcec package.
var keyCompressed [btcec.PubKeyBytesLenCompressed]byte
keyCompressed[0] = secp.PubKeyFormatCompressedEven
copy(keyCompressed[1:], pubKeyStr)
return btcec.ParsePubKey(keyCompressed[:])
}
// SerializePubKey serializes a public key as specified by BIP 340. Public keys
// in this format are 32 bytes in length, and are assumed to have an even y
// coordinate.
func SerializePubKey(pub *btcec.PublicKey) []byte {
pBytes := pub.SerializeCompressed()
return pBytes[1:]
}
+538
View File
@@ -0,0 +1,538 @@
// Copyright (c) 2013-2022 The btcsuite developers
package schnorr
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
ecdsa_schnorr "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr"
)
const (
// SignatureSize is the size of an encoded Schnorr signature.
SignatureSize = 64
// scalarSize is the size of an encoded big endian scalar.
scalarSize = 32
)
var (
// rfc6979ExtraDataV0 is the extra data to feed to RFC6979 when
// generating the deterministic nonce for the BIP-340 scheme. This
// ensures the same nonce is not generated for the same message and key
// as for other signing algorithms such as ECDSA.
//
// It is equal to SHA-256([]byte("BIP-340")).
rfc6979ExtraDataV0 = [32]uint8{
0xa3, 0xeb, 0x4c, 0x18, 0x2f, 0xae, 0x7e, 0xf4,
0xe8, 0x10, 0xc6, 0xee, 0x13, 0xb0, 0xe9, 0x26,
0x68, 0x6d, 0x71, 0xe8, 0x7f, 0x39, 0x4f, 0x79,
0x9c, 0x00, 0xa5, 0x21, 0x03, 0xcb, 0x4e, 0x17,
}
)
// Signature is a type representing a Schnorr signature.
type Signature struct {
r btcec.FieldVal
s btcec.ModNScalar
}
// NewSignature instantiates a new signature given some r and s values.
func NewSignature(r *btcec.FieldVal, s *btcec.ModNScalar) *Signature {
var sig Signature
sig.r.Set(r).Normalize()
sig.s.Set(s)
return &sig
}
// Serialize returns the Schnorr signature in the more strict format.
//
// The signatures are encoded as
//
// sig[0:32] x coordinate of the point R, encoded as a big-endian uint256
// sig[32:64] s, encoded also as big-endian uint256
func (sig Signature) Serialize() []byte {
// Total length of returned signature is the length of r and s.
var b [SignatureSize]byte
sig.r.PutBytesUnchecked(b[0:32])
sig.s.PutBytesUnchecked(b[32:64])
return b[:]
}
// ParseSignature parses a signature according to the BIP-340 specification and
// enforces the following additional restrictions specific to secp256k1:
//
// - The r component must be in the valid range for secp256k1 field elements
// - The s component must be in the valid range for secp256k1 scalars
func ParseSignature(sig []byte) (*Signature, error) {
// The signature must be the correct length.
sigLen := len(sig)
if sigLen < SignatureSize {
str := fmt.Sprintf("malformed signature: too short: %d < %d", sigLen,
SignatureSize)
return nil, signatureError(ecdsa_schnorr.ErrSigTooShort, str)
}
if sigLen > SignatureSize {
str := fmt.Sprintf("malformed signature: too long: %d > %d", sigLen,
SignatureSize)
return nil, signatureError(ecdsa_schnorr.ErrSigTooLong, str)
}
// The signature is validly encoded at this point, however, enforce
// additional restrictions to ensure r is in the range [0, p-1], and s is in
// the range [0, n-1] since valid Schnorr signatures are required to be in
// that range per spec.
var r btcec.FieldVal
if overflow := r.SetByteSlice(sig[0:32]); overflow {
str := "invalid signature: r >= field prime"
return nil, signatureError(ecdsa_schnorr.ErrSigRTooBig, str)
}
var s btcec.ModNScalar
s.SetByteSlice(sig[32:64])
// Return the signature.
return NewSignature(&r, &s), nil
}
// IsEqual compares this Signature instance to the one passed, returning true
// if both Signatures are equivalent. A signature is equivalent to another, if
// they both have the same scalar value for R and S.
func (sig Signature) IsEqual(otherSig *Signature) bool {
return sig.r.Equals(&otherSig.r) && sig.s.Equals(&otherSig.s)
}
// schnorrVerify attempt to verify the signature for the provided hash and
// secp256k1 public key and either returns nil if successful or a specific error
// indicating why it failed if not successful.
//
// This differs from the exported Verify method in that it returns a specific
// error to support better testing while the exported method simply returns a
// bool indicating success or failure.
func schnorrVerify(sig *Signature, hash []byte, pubKeyBytes []byte) error {
// The algorithm for producing a BIP-340 signature is described in
// README.md and is reproduced here for reference:
//
// 1. Fail if m is not 32 bytes
// 2. P = lift_x(int(pk)).
// 3. r = int(sig[0:32]); fail is r >= p.
// 4. s = int(sig[32:64]); fail if s >= n.
// 5. e = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || M)) mod n.
// 6. R = s*G - e*P
// 7. Fail if is_infinite(R)
// 8. Fail if not hash_even_y(R)
// 9. Fail is x(R) != r.
// 10. Return success iff failure did not occur before reaching this point.
// Step 1.
//
// Fail if m is not 32 bytes
if len(hash) != scalarSize {
str := fmt.Sprintf("wrong size for message (got %v, want %v)",
len(hash), scalarSize)
return signatureError(ecdsa_schnorr.ErrInvalidHashLen, str)
}
// Step 2.
//
// P = lift_x(int(pk))
//
// Fail if P is not a point on the curve
pubKey, err := ParsePubKey(pubKeyBytes)
if err != nil {
return err
}
if !pubKey.IsOnCurve() {
str := "pubkey point is not on curve"
return signatureError(ecdsa_schnorr.ErrPubKeyNotOnCurve, str)
}
// Step 3.
//
// Fail if r >= p
//
// Note this is already handled by the fact r is a field element.
// Step 4.
//
// Fail if s >= n
//
// Note this is already handled by the fact s is a mod n scalar.
// Step 5.
//
// e = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || M)) mod n.
var rBytes [32]byte
sig.r.PutBytesUnchecked(rBytes[:])
pBytes := SerializePubKey(pubKey)
commitment := chainhash.TaggedHash(
chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash,
)
var e btcec.ModNScalar
e.SetBytes((*[32]byte)(commitment))
// Negate e here so we can use AddNonConst below to subtract the s*G
// point from e*P.
e.Negate()
// Step 6.
//
// R = s*G - e*P
var P, R, sG, eP btcec.JacobianPoint
pubKey.AsJacobian(&P)
btcec.ScalarBaseMultNonConst(&sig.s, &sG)
btcec.ScalarMultNonConst(&e, &P, &eP)
btcec.AddNonConst(&sG, &eP, &R)
// Step 7.
//
// Fail if R is the point at infinity
if (R.X.IsZero() && R.Y.IsZero()) || R.Z.IsZero() {
str := "calculated R point is the point at infinity"
return signatureError(ecdsa_schnorr.ErrSigRNotOnCurve, str)
}
// Step 8.
//
// Fail if R.y is odd
//
// Note that R must be in affine coordinates for this check.
R.ToAffine()
if R.Y.IsOdd() {
str := "calculated R y-value is odd"
return signatureError(ecdsa_schnorr.ErrSigRYIsOdd, str)
}
// Step 9.
//
// Verified if R.x == r
//
// Note that R must be in affine coordinates for this check.
if !sig.r.Equals(&R.X) {
str := "calculated R point was not given R"
return signatureError(ecdsa_schnorr.ErrUnequalRValues, str)
}
// Step 10.
//
// Return success iff failure did not occur before reaching this point.
return nil
}
// Verify returns whether or not the signature is valid for the provided hash
// and secp256k1 public key.
func (sig *Signature) Verify(hash []byte, pubKey *btcec.PublicKey) bool {
pubkeyBytes := SerializePubKey(pubKey)
return schnorrVerify(sig, hash, pubkeyBytes) == nil
}
// zeroArray zeroes the memory of a scalar array.
func zeroArray(a *[scalarSize]byte) {
for i := 0; i < scalarSize; i++ {
a[i] = 0x00
}
}
// schnorrSign generates a BIP-340 signature over the secp256k1 curve for the
// provided hash (which should be the result of hashing a larger message) using
// the given nonce and private key. The produced signature is deterministic
// (same message, nonce, and key yield the same signature) and canonical.
//
// WARNING: The hash MUST be 32 bytes and both the nonce and private keys must
// NOT be 0. Since this is an internal use function, these preconditions MUST
// be satisfied by the caller.
func schnorrSign(privKey, nonce *btcec.ModNScalar, pubKey *btcec.PublicKey, hash []byte,
opts *signOptions) (*Signature, error) {
// The algorithm for producing a BIP-340 signature is described in
// README.md and is reproduced here for reference:
//
// G = curve generator
// n = curve order
// d = private key
// m = message
// a = input randomness
// r, s = signature
//
// 1. d' = int(d)
// 2. Fail if m is not 32 bytes
// 3. Fail if d = 0 or d >= n
// 4. P = d'*G
// 5. Negate d if P.y is odd
// 6. t = bytes(d) xor tagged_hash("BIP0340/aux", t || bytes(P) || m)
// 7. rand = tagged_hash("BIP0340/nonce", a)
// 8. k' = int(rand) mod n
// 9. Fail if k' = 0
// 10. R = 'k*G
// 11. Negate k if R.y id odd
// 12. e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || m) mod n
// 13. sig = bytes(R) || bytes((k + e*d)) mod n
// 14. If Verify(bytes(P), m, sig) fails, abort.
// 15. return sig.
//
// Note that the set of functional options passed in may modify the
// above algorithm. Namely if CustomNonce is used, then steps 6-8 are
// replaced with a process that generates the nonce using rfc6979. If
// FastSign is passed, then we skip set 14.
// NOTE: Steps 1-9 are performed by the caller.
//
// Step 10.
//
// R = kG
var R btcec.JacobianPoint
k := *nonce
btcec.ScalarBaseMultNonConst(&k, &R)
// Step 11.
//
// Negate nonce k if R.y is odd (R.y is the y coordinate of the point R)
//
// Note that R must be in affine coordinates for this check.
R.ToAffine()
if R.Y.IsOdd() {
k.Negate()
}
// Step 12.
//
// e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || m) mod n
pBytes := SerializePubKey(pubKey)
commitment := chainhash.TaggedHash(
chainhash.TagBIP0340Challenge, R.X.Bytes()[:], pBytes, hash,
)
var e btcec.ModNScalar
if overflow := e.SetBytes((*[32]byte)(commitment)); overflow != 0 {
k.Zero()
str := "hash of (r || P || m) too big"
return nil, signatureError(ecdsa_schnorr.ErrSchnorrHashValue, str)
}
// Step 13.
//
// s = k + e*d mod n
s := new(btcec.ModNScalar).Mul2(&e, privKey).Add(&k)
k.Zero()
sig := NewSignature(&R.X, s)
// Step 14.
//
// If Verify(bytes(P), m, sig) fails, abort.
if !opts.fastSign {
if err := schnorrVerify(sig, hash, pBytes); err != nil {
return nil, err
}
}
// Step 15.
//
// Return (r, s)
return sig, nil
}
// SignOption is a functional option argument that allows callers to modify the
// way we generate BIP-340 schnorr signatures.
type SignOption func(*signOptions)
// signOptions houses the set of functional options that can be used to modify
// the method used to generate the BIP-340 signature.
type signOptions struct {
// fastSign determines if we'll skip the check at the end of the routine
// where we attempt to verify the produced signature.
fastSign bool
// authNonce allows the user to pass in their own nonce information, which
// is useful for schemes like mu-sig.
authNonce *[32]byte
}
// defaultSignOptions returns the default set of signing operations.
func defaultSignOptions() *signOptions {
return &signOptions{}
}
// FastSign forces signing to skip the extra verification step at the end.
// Performance sensitive applications may opt to use this option to speed up the
// signing operation.
func FastSign() SignOption {
return func(o *signOptions) {
o.fastSign = true
}
}
// CustomNonce allows users to pass in a custom set of auxData that's used as
// input randomness to generate the nonce used during signing. Users may want
// to specify this custom value when using multi-signatures schemes such as
// Mu-Sig2. If this option isn't set, then rfc6979 will be used to generate the
// nonce material.
func CustomNonce(auxData [32]byte) SignOption {
return func(o *signOptions) {
o.authNonce = &auxData
}
}
// Sign generates an BIP-340 signature over the secp256k1 curve for the
// provided hash (which should be the result of hashing a larger message) using
// the given private key. The produced signature is deterministic (same
// message and same key yield the same signature) and canonical.
//
// Note that the current signing implementation has a few remaining variable
// time aspects which make use of the private key and the generated nonce,
// which can expose the signer to constant time attacks. As a result, this
// function should not be used in situations where there is the possibility of
// someone having EM field/cache/etc access.
func Sign(privKey *btcec.PrivateKey, hash []byte,
signOpts ...SignOption) (*Signature, error) {
// First, parse the set of optional signing options.
opts := defaultSignOptions()
for _, option := range signOpts {
option(opts)
}
// The algorithm for producing a BIP-340 signature is described in
// README.md and is reproduced here for reference:
//
// G = curve generator
// n = curve order
// d = private key
// m = message
// a = input randomness
// r, s = signature
//
// 1. d' = int(d)
// 2. Fail if m is not 32 bytes
// 3. Fail if d = 0 or d >= n
// 4. P = d'*G
// 5. Negate d if P.y is odd
// 6. t = bytes(d) xor tagged_hash("BIP0340/aux", t || bytes(P) || m)
// 7. rand = tagged_hash("BIP0340/nonce", a)
// 8. k' = int(rand) mod n
// 9. Fail if k' = 0
// 10. R = 'k*G
// 11. Negate k if R.y id odd
// 12. e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || mod) mod n
// 13. sig = bytes(R) || bytes((k + e*d)) mod n
// 14. If Verify(bytes(P), m, sig) fails, abort.
// 15. return sig.
//
// Note that the set of functional options passed in may modify the
// above algorithm. Namely if CustomNonce is used, then steps 6-8 are
// replaced with a process that generates the nonce using rfc6979. If
// FastSign is passed, then we skip set 14.
// Step 1.
//
// d' = int(d)
var privKeyScalar btcec.ModNScalar
privKeyScalar.Set(&privKey.Key)
// Step 2.
//
// Fail if m is not 32 bytes
if len(hash) != scalarSize {
str := fmt.Sprintf("wrong size for message hash (got %v, want %v)",
len(hash), scalarSize)
return nil, signatureError(ecdsa_schnorr.ErrInvalidHashLen, str)
}
// Step 3.
//
// Fail if d = 0 or d >= n
if privKeyScalar.IsZero() {
str := "private key is zero"
return nil, signatureError(ecdsa_schnorr.ErrPrivateKeyIsZero, str)
}
// Step 4.
//
// P = 'd*G
pub := privKey.PubKey()
// Step 5.
//
// Negate d if P.y is odd.
pubKeyBytes := pub.SerializeCompressed()
if pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
privKeyScalar.Negate()
}
// At this point, we check to see if a CustomNonce has been passed in,
// and if so, then we'll deviate from the main routine here by
// generating the nonce value as specified by BIP-0340.
if opts.authNonce != nil {
// Step 6.
//
// t = bytes(d) xor tagged_hash("BIP0340/aux", a)
privBytes := privKeyScalar.Bytes()
t := chainhash.TaggedHash(
chainhash.TagBIP0340Aux, (*opts.authNonce)[:],
)
for i := 0; i < len(t); i++ {
t[i] ^= privBytes[i]
}
// Step 7.
//
// rand = tagged_hash("BIP0340/nonce", t || bytes(P) || m)
//
// We snip off the first byte of the serialized pubkey, as we
// only need the x coordinate and not the market byte.
rand := chainhash.TaggedHash(
chainhash.TagBIP0340Nonce, t[:], pubKeyBytes[1:], hash,
)
// Step 8.
//
// k'= int(rand) mod n
var kPrime btcec.ModNScalar
kPrime.SetBytes((*[32]byte)(rand))
// Step 9.
//
// Fail if k' = 0
if kPrime.IsZero() {
str := fmt.Sprintf("generated nonce is zero")
return nil, signatureError(ecdsa_schnorr.ErrSchnorrHashValue, str)
}
sig, err := schnorrSign(&privKeyScalar, &kPrime, pub, hash, opts)
kPrime.Zero()
if err != nil {
return nil, err
}
return sig, nil
}
var privKeyBytes [scalarSize]byte
privKeyScalar.PutBytes(&privKeyBytes)
defer zeroArray(&privKeyBytes)
for iteration := uint32(0); ; iteration++ {
// Step 6-9.
//
// Use RFC6979 to generate a deterministic nonce k in [1, n-1]
// parameterized by the private key, message being signed, extra data
// that identifies the scheme, and an iteration count
k := btcec.NonceRFC6979(
privKeyBytes[:], hash, rfc6979ExtraDataV0[:], nil, iteration,
)
// Steps 10-15.
sig, err := schnorrSign(&privKeyScalar, k, pub, hash, opts)
k.Zero()
if err != nil {
// Try again with a new nonce.
continue
}
return sig, nil
}
}
+16
View File
@@ -0,0 +1,16 @@
ISC License
Copyright (c) 2013-2017 The btcsuite developers
Copyright (c) 2016-2017 The Lightning Network Developers
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+29
View File
@@ -0,0 +1,29 @@
bech32
==========
[![Build Status](http://img.shields.io/travis/btcsuite/btcutil.svg)](https://travis-ci.org/btcsuite/btcutil)
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![GoDoc](https://godoc.org/github.com/btcsuite/btcd/btcutil/bech32?status.png)](http://godoc.org/github.com/btcsuite/btcd/btcutil/bech32)
Package bech32 provides a Go implementation of the bech32 format specified in
[BIP 173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki).
Test vectors from BIP 173 are added to ensure compatibility with the BIP.
## Installation and Updating
```bash
$ go get -u github.com/btcsuite/btcd/btcutil/bech32
```
## Examples
* [Bech32 decode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/bech32#example-Bech32Decode)
Demonstrates how to decode a bech32 encoded string.
* [Bech32 encode Example](http://godoc.org/github.com/btcsuite/btcd/btcutil/bech32#example-BechEncode)
Demonstrates how to encode data into a bech32 string.
## License
Package bech32 is licensed under the [copyfree](http://copyfree.org) ISC
License.
+442
View File
@@ -0,0 +1,442 @@
// Copyright (c) 2017 The btcsuite developers
// Copyright (c) 2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package bech32
import (
"strings"
)
// charset is the set of characters used in the data section of bech32 strings.
// Note that this is ordered, such that for a given charset[i], i is the binary
// value of the character.
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
// gen encodes the generator polynomial for the bech32 BCH checksum.
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
// toBytes converts each character in the string 'chars' to the value of the
// index of the correspoding character in 'charset'.
func toBytes(chars string) ([]byte, error) {
decoded := make([]byte, 0, len(chars))
for i := 0; i < len(chars); i++ {
index := strings.IndexByte(charset, chars[i])
if index < 0 {
return nil, ErrNonCharsetChar(chars[i])
}
decoded = append(decoded, byte(index))
}
return decoded, nil
}
// bech32Polymod calculates the BCH checksum for a given hrp, values and
// checksum data. Checksum is optional, and if nil a 0 checksum is assumed.
//
// Values and checksum (if provided) MUST be encoded as 5 bits per element (base
// 32), otherwise the results are undefined.
//
// For more details on the polymod calculation, please refer to BIP 173.
func bech32Polymod(hrp string, values, checksum []byte) int {
chk := 1
// Account for the high bits of the HRP in the checksum.
for i := 0; i < len(hrp); i++ {
b := chk >> 25
hiBits := int(hrp[i]) >> 5
chk = (chk&0x1ffffff)<<5 ^ hiBits
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
// Account for the separator (0) between high and low bits of the HRP.
// x^0 == x, so we eliminate the redundant xor used in the other rounds.
b := chk >> 25
chk = (chk & 0x1ffffff) << 5
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
// Account for the low bits of the HRP.
for i := 0; i < len(hrp); i++ {
b := chk >> 25
loBits := int(hrp[i]) & 31
chk = (chk&0x1ffffff)<<5 ^ loBits
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
// Account for the values.
for _, v := range values {
b := chk >> 25
chk = (chk&0x1ffffff)<<5 ^ int(v)
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
if checksum == nil {
// A nil checksum is used during encoding, so assume all bytes are zero.
// x^0 == x, so we eliminate the redundant xor used in the other rounds.
for v := 0; v < 6; v++ {
b := chk >> 25
chk = (chk & 0x1ffffff) << 5
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
} else {
// Checksum is provided during decoding, so use it.
for _, v := range checksum {
b := chk >> 25
chk = (chk&0x1ffffff)<<5 ^ int(v)
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
}
return chk
}
// writeBech32Checksum calculates the checksum data expected for a string that
// will have the given hrp and payload data and writes it to the provided string
// builder.
//
// The payload data MUST be encoded as a base 32 (5 bits per element) byte slice
// and the hrp MUST only use the allowed character set (ascii chars between 33
// and 126), otherwise the results are undefined.
//
// For more details on the checksum calculation, please refer to BIP 173.
func writeBech32Checksum(hrp string, data []byte, bldr *strings.Builder,
version Version) {
bech32Const := int(VersionToConsts[version])
polymod := bech32Polymod(hrp, data, nil) ^ bech32Const
for i := 0; i < 6; i++ {
b := byte((polymod >> uint(5*(5-i))) & 31)
// This can't fail, given we explicitly cap the previous b byte by the
// first 31 bits.
c := charset[b]
bldr.WriteByte(c)
}
}
// bech32VerifyChecksum verifies whether the bech32 string specified by the
// provided hrp and payload data (encoded as 5 bits per element byte slice) has
// the correct checksum suffix. The version of bech32 used (bech32 OG, or
// bech32m) is also returned to allow the caller to perform proper address
// validation (segwitv0 should use bech32, v1+ should use bech32m).
//
// Data MUST have more than 6 elements, otherwise this function panics.
//
// For more details on the checksum verification, please refer to BIP 173.
func bech32VerifyChecksum(hrp string, data []byte) (Version, bool) {
checksum := data[len(data)-6:]
values := data[:len(data)-6]
polymod := bech32Polymod(hrp, values, checksum)
// Before BIP-350, we'd always check this against a static constant of
// 1 to know if the checksum was computed properly. As we want to
// generically support decoding for bech32m as well as bech32, we'll
// look up the returned value and compare it to the set of defined
// constants.
bech32Version, ok := ConstsToVersion[ChecksumConst(polymod)]
if ok {
return bech32Version, true
}
return VersionUnknown, false
}
// DecodeNoLimit is a bech32 checksum version aware arbitrary string length
// decoder. This function will return the version of the decoded checksum
// constant so higher level validation can be performed to ensure the correct
// version of bech32 was used when encoding.
func decodeNoLimit(bech string) (string, []byte, Version, error) {
// The minimum allowed size of a bech32 string is 8 characters, since it
// needs a non-empty HRP, a separator, and a 6 character checksum.
if len(bech) < 8 {
return "", nil, VersionUnknown, ErrInvalidLength(len(bech))
}
// Only ASCII characters between 33 and 126 are allowed.
var hasLower, hasUpper bool
for i := 0; i < len(bech); i++ {
if bech[i] < 33 || bech[i] > 126 {
return "", nil, VersionUnknown, ErrInvalidCharacter(bech[i])
}
// The characters must be either all lowercase or all uppercase. Testing
// directly with ascii codes is safe here, given the previous test.
hasLower = hasLower || (bech[i] >= 97 && bech[i] <= 122)
hasUpper = hasUpper || (bech[i] >= 65 && bech[i] <= 90)
if hasLower && hasUpper {
return "", nil, VersionUnknown, ErrMixedCase{}
}
}
// Bech32 standard uses only the lowercase for of strings for checksum
// calculation.
if hasUpper {
bech = strings.ToLower(bech)
}
// The string is invalid if the last '1' is non-existent, it is the
// first character of the string (no human-readable part) or one of the
// last 6 characters of the string (since checksum cannot contain '1').
one := strings.LastIndexByte(bech, '1')
if one < 1 || one+7 > len(bech) {
return "", nil, VersionUnknown, ErrInvalidSeparatorIndex(one)
}
// The human-readable part is everything before the last '1'.
hrp := bech[:one]
data := bech[one+1:]
// Each character corresponds to the byte with value of the index in
// 'charset'.
decoded, err := toBytes(data)
if err != nil {
return "", nil, VersionUnknown, err
}
// Verify if the checksum (stored inside decoded[:]) is valid, given the
// previously decoded hrp.
bech32Version, ok := bech32VerifyChecksum(hrp, decoded)
if !ok {
// Invalid checksum. Calculate what it should have been, so that the
// error contains this information.
// Extract the payload bytes and actual checksum in the string.
actual := bech[len(bech)-6:]
payload := decoded[:len(decoded)-6]
// Calculate the expected checksum, given the hrp and payload
// data. We'll actually compute _both_ possibly valid checksum
// to further aide in debugging.
var expectedBldr strings.Builder
expectedBldr.Grow(6)
writeBech32Checksum(hrp, payload, &expectedBldr, Version0)
expectedVersion0 := expectedBldr.String()
var b strings.Builder
b.Grow(6)
writeBech32Checksum(hrp, payload, &expectedBldr, VersionM)
expectedVersionM := expectedBldr.String()
err = ErrInvalidChecksum{
Expected: expectedVersion0,
ExpectedM: expectedVersionM,
Actual: actual,
}
return "", nil, VersionUnknown, err
}
// We exclude the last 6 bytes, which is the checksum.
return hrp, decoded[:len(decoded)-6], bech32Version, nil
}
// DecodeNoLimit decodes a bech32 encoded string, returning the human-readable
// part and the data part excluding the checksum. This function does NOT
// validate against the BIP-173 maximum length allowed for bech32 strings and
// is meant for use in custom applications (such as lightning network payment
// requests), NOT on-chain addresses.
//
// Note that the returned data is 5-bit (base32) encoded and the human-readable
// part will be lowercase.
func DecodeNoLimit(bech string) (string, []byte, error) {
hrp, data, _, err := decodeNoLimit(bech)
return hrp, data, err
}
// Decode decodes a bech32 encoded string, returning the human-readable part and
// the data part excluding the checksum.
//
// Note that the returned data is 5-bit (base32) encoded and the human-readable
// part will be lowercase.
func Decode(bech string) (string, []byte, error) {
// The maximum allowed length for a bech32 string is 90.
if len(bech) > 90 {
return "", nil, ErrInvalidLength(len(bech))
}
hrp, data, _, err := decodeNoLimit(bech)
return hrp, data, err
}
// DecodeGeneric is identical to the existing Decode method, but will also
// return bech32 version that matches the decoded checksum. This method should
// be used when decoding segwit addresses, as it enables additional
// verification to ensure the proper checksum is used.
func DecodeGeneric(bech string) (string, []byte, Version, error) {
// The maximum allowed length for a bech32 string is 90.
if len(bech) > 90 {
return "", nil, VersionUnknown, ErrInvalidLength(len(bech))
}
return decodeNoLimit(bech)
}
// encodeGeneric is the base bech32 encoding function that is aware of the
// existence of the checksum versions. This method is private, as the Encode
// and EncodeM methods are intended to be used instead.
func encodeGeneric(hrp string, data []byte,
version Version) (string, error) {
// The resulting bech32 string is the concatenation of the lowercase
// hrp, the separator 1, data and the 6-byte checksum.
hrp = strings.ToLower(hrp)
var bldr strings.Builder
bldr.Grow(len(hrp) + 1 + len(data) + 6)
bldr.WriteString(hrp)
bldr.WriteString("1")
// Write the data part, using the bech32 charset.
for _, b := range data {
if int(b) >= len(charset) {
return "", ErrInvalidDataByte(b)
}
bldr.WriteByte(charset[b])
}
// Calculate and write the checksum of the data.
writeBech32Checksum(hrp, data, &bldr, version)
return bldr.String(), nil
}
// Encode encodes a byte slice into a bech32 string with the given
// human-readable part (HRP). The HRP will be converted to lowercase if needed
// since mixed cased encodings are not permitted and lowercase is used for
// checksum purposes. Note that the bytes must each encode 5 bits (base32).
func Encode(hrp string, data []byte) (string, error) {
return encodeGeneric(hrp, data, Version0)
}
// EncodeM is the exactly same as the Encode method, but it uses the new
// bech32m constant instead of the original one. It should be used whenever one
// attempts to encode a segwit address of v1 and beyond.
func EncodeM(hrp string, data []byte) (string, error) {
return encodeGeneric(hrp, data, VersionM)
}
// ConvertBits converts a byte slice where each byte is encoding fromBits bits,
// to a byte slice where each byte is encoding toBits bits.
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) {
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
return nil, ErrInvalidBitGroups{}
}
// Determine the maximum size the resulting array can have after base
// conversion, so that we can size it a single time. This might be off
// by a byte depending on whether padding is used or not and if the input
// data is a multiple of both fromBits and toBits, but we ignore that and
// just size it to the maximum possible.
maxSize := len(data)*int(fromBits)/int(toBits) + 1
// The final bytes, each byte encoding toBits bits.
regrouped := make([]byte, 0, maxSize)
// Keep track of the next byte we create and how many bits we have
// added to it out of the toBits goal.
nextByte := byte(0)
filledBits := uint8(0)
for _, b := range data {
// Discard unused bits.
b <<= 8 - fromBits
// How many bits remaining to extract from the input data.
remFromBits := fromBits
for remFromBits > 0 {
// How many bits remaining to be added to the next byte.
remToBits := toBits - filledBits
// The number of bytes to next extract is the minimum of
// remFromBits and remToBits.
toExtract := remFromBits
if remToBits < toExtract {
toExtract = remToBits
}
// Add the next bits to nextByte, shifting the already
// added bits to the left.
nextByte = (nextByte << toExtract) | (b >> (8 - toExtract))
// Discard the bits we just extracted and get ready for
// next iteration.
b <<= toExtract
remFromBits -= toExtract
filledBits += toExtract
// If the nextByte is completely filled, we add it to
// our regrouped bytes and start on the next byte.
if filledBits == toBits {
regrouped = append(regrouped, nextByte)
filledBits = 0
nextByte = 0
}
}
}
// We pad any unfinished group if specified.
if pad && filledBits > 0 {
nextByte <<= toBits - filledBits
regrouped = append(regrouped, nextByte)
filledBits = 0
nextByte = 0
}
// Any incomplete group must be <= 4 bits, and all zeroes.
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
return nil, ErrInvalidIncompleteGroup{}
}
return regrouped, nil
}
// EncodeFromBase256 converts a base256-encoded byte slice into a base32-encoded
// byte slice and then encodes it into a bech32 string with the given
// human-readable part (HRP). The HRP will be converted to lowercase if needed
// since mixed cased encodings are not permitted and lowercase is used for
// checksum purposes.
func EncodeFromBase256(hrp string, data []byte) (string, error) {
converted, err := ConvertBits(data, 8, 5, true)
if err != nil {
return "", err
}
return Encode(hrp, converted)
}
// DecodeToBase256 decodes a bech32-encoded string into its associated
// human-readable part (HRP) and base32-encoded data, converts that data to a
// base256-encoded byte slice and returns it along with the lowercase HRP.
func DecodeToBase256(bech string) (string, []byte, error) {
hrp, data, err := Decode(bech)
if err != nil {
return "", nil, err
}
converted, err := ConvertBits(data, 5, 8, false)
if err != nil {
return "", nil, err
}
return hrp, converted, nil
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright (c) 2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package bech32 provides a Go implementation of the bech32 format specified in
BIP 173.
Bech32 strings consist of a human-readable part (hrp), followed by the
separator 1, then a checksummed data part encoded using the 32 characters
"qpzry9x8gf2tvdw0s3jn54khce6mua7l".
More info: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
*/
package bech32
+87
View File
@@ -0,0 +1,87 @@
// Copyright (c) 2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package bech32
import (
"fmt"
)
// ErrMixedCase is returned when the bech32 string has both lower and uppercase
// characters.
type ErrMixedCase struct{}
func (e ErrMixedCase) Error() string {
return "string not all lowercase or all uppercase"
}
// ErrInvalidBitGroups is returned when conversion is attempted between byte
// slices using bit-per-element of unsupported value.
type ErrInvalidBitGroups struct{}
func (e ErrInvalidBitGroups) Error() string {
return "only bit groups between 1 and 8 allowed"
}
// ErrInvalidIncompleteGroup is returned when then byte slice used as input has
// data of wrong length.
type ErrInvalidIncompleteGroup struct{}
func (e ErrInvalidIncompleteGroup) Error() string {
return "invalid incomplete group"
}
// ErrInvalidLength is returned when the bech32 string has an invalid length
// given the BIP-173 defined restrictions.
type ErrInvalidLength int
func (e ErrInvalidLength) Error() string {
return fmt.Sprintf("invalid bech32 string length %d", int(e))
}
// ErrInvalidCharacter is returned when the bech32 string has a character
// outside the range of the supported charset.
type ErrInvalidCharacter rune
func (e ErrInvalidCharacter) Error() string {
return fmt.Sprintf("invalid character in string: '%c'", rune(e))
}
// ErrInvalidSeparatorIndex is returned when the separator character '1' is
// in an invalid position in the bech32 string.
type ErrInvalidSeparatorIndex int
func (e ErrInvalidSeparatorIndex) Error() string {
return fmt.Sprintf("invalid separator index %d", int(e))
}
// ErrNonCharsetChar is returned when a character outside of the specific
// bech32 charset is used in the string.
type ErrNonCharsetChar rune
func (e ErrNonCharsetChar) Error() string {
return fmt.Sprintf("invalid character not part of charset: %v", int(e))
}
// ErrInvalidChecksum is returned when the extracted checksum of the string
// is different than what was expected. Both the original version, as well as
// the new bech32m checksum may be specified.
type ErrInvalidChecksum struct {
Expected string
ExpectedM string
Actual string
}
func (e ErrInvalidChecksum) Error() string {
return fmt.Sprintf("invalid checksum (expected (bech32=%v, "+
"bech32m=%v), got %v)", e.Expected, e.ExpectedM, e.Actual)
}
// ErrInvalidDataByte is returned when a byte outside the range required for
// conversion into a string was found.
type ErrInvalidDataByte byte
func (e ErrInvalidDataByte) Error() string {
return fmt.Sprintf("invalid data byte: %v", byte(e))
}
+43
View File
@@ -0,0 +1,43 @@
package bech32
// ChecksumConst is a type that represents the currently defined bech32
// checksum constants.
type ChecksumConst int
const (
// Version0Const is the original constant used in the checksum
// verification for bech32.
Version0Const ChecksumConst = 1
// VersionMConst is the new constant used for bech32m checksum
// verification.
VersionMConst ChecksumConst = 0x2bc830a3
)
// Version defines the current set of bech32 versions.
type Version uint8
const (
// Version0 defines the original bech version.
Version0 Version = iota
// VersionM is the new bech32 version defined in BIP-350, also known as
// bech32m.
VersionM
// VersionUnknown denotes an unknown bech version.
VersionUnknown
)
// VersionToConsts maps bech32 versions to the checksum constant to be used
// when encoding, and asserting a particular version when decoding.
var VersionToConsts = map[Version]ChecksumConst{
Version0: Version0Const,
VersionM: VersionMConst,
}
// ConstsToVersion maps a bech32 constant to the version it's associated with.
var ConstsToVersion = map[ChecksumConst]Version{
Version0Const: Version0,
VersionMConst: VersionM,
}
+16
View File
@@ -0,0 +1,16 @@
ISC License
Copyright (c) 2013-2023 The btcsuite developers
Copyright (c) 2015-2016 The Decred developers
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+41
View File
@@ -0,0 +1,41 @@
chainhash
=========
[![Build Status](https://github.com/btcsuite/btcd/workflows/Build%20and%20Test/badge.svg)](https://github.com/btcsuite/btcd/actions)
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/btcsuite/btcd/chaincfg/chainhash)
=======
chainhash provides a generic hash type and associated functions that allows the
specific hash algorithm to be abstracted.
## Installation and Updating
```bash
$ go get -u github.com/btcsuite/btcd/chaincfg/chainhash
```
## GPG Verification Key
All official release tags are signed by Conformal so users can ensure the code
has not been tampered with and is coming from the btcsuite developers. To
verify the signature perform the following:
- Download the public key from the Conformal website at
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
- Import the public key into your GPG keyring:
```bash
gpg --import GIT-GPG-KEY-conformal.txt
```
- Verify the release tag with the following command where `TAG_NAME` is a
placeholder for the specific tag:
```bash
git tag -v TAG_NAME
```
## License
Package chainhash is licensed under the [copyfree](http://copyfree.org) ISC
License.
+5
View File
@@ -0,0 +1,5 @@
// Package chainhash provides abstracted hash functionality.
//
// This package provides a generic hash type and associated functions that
// allows the specific hash algorithm to be abstracted.
package chainhash
+239
View File
@@ -0,0 +1,239 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package chainhash
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
)
// HashSize of array used to store hashes. See Hash.
const HashSize = 32
// MaxHashStringSize is the maximum length of a Hash hash string.
const MaxHashStringSize = HashSize * 2
var (
// TagBIP0340Challenge is the BIP-0340 tag for challenges.
TagBIP0340Challenge = []byte("BIP0340/challenge")
// TagBIP0340Aux is the BIP-0340 tag for aux data.
TagBIP0340Aux = []byte("BIP0340/aux")
// TagBIP0340Nonce is the BIP-0340 tag for nonces.
TagBIP0340Nonce = []byte("BIP0340/nonce")
// TagTapSighash is the tag used by BIP 341 to generate the sighash
// flags.
TagTapSighash = []byte("TapSighash")
// TagTagTapLeaf is the message tag prefix used to compute the hash
// digest of a tapscript leaf.
TagTapLeaf = []byte("TapLeaf")
// TagTapBranch is the message tag prefix used to compute the
// hash digest of two tap leaves into a taproot branch node.
TagTapBranch = []byte("TapBranch")
// TagTapTweak is the message tag prefix used to compute the hash tweak
// used to enable a public key to commit to the taproot branch root
// for the witness program.
TagTapTweak = []byte("TapTweak")
// precomputedTags is a map containing the SHA-256 hash of the BIP-0340
// tags.
precomputedTags = map[string]Hash{
string(TagBIP0340Challenge): sha256.Sum256(TagBIP0340Challenge),
string(TagBIP0340Aux): sha256.Sum256(TagBIP0340Aux),
string(TagBIP0340Nonce): sha256.Sum256(TagBIP0340Nonce),
string(TagTapSighash): sha256.Sum256(TagTapSighash),
string(TagTapLeaf): sha256.Sum256(TagTapLeaf),
string(TagTapBranch): sha256.Sum256(TagTapBranch),
string(TagTapTweak): sha256.Sum256(TagTapTweak),
}
)
// ErrHashStrSize describes an error that indicates the caller specified a hash
// string that has too many characters.
var ErrHashStrSize = fmt.Errorf("max hash string length is %v bytes", MaxHashStringSize)
// Hash is used in several of the bitcoin messages and common structures. It
// typically represents the double sha256 of data.
type Hash [HashSize]byte
// String returns the Hash as the hexadecimal string of the byte-reversed
// hash.
func (hash Hash) String() string {
for i := 0; i < HashSize/2; i++ {
hash[i], hash[HashSize-1-i] = hash[HashSize-1-i], hash[i]
}
return hex.EncodeToString(hash[:])
}
// CloneBytes returns a copy of the bytes which represent the hash as a byte
// slice.
//
// NOTE: It is generally cheaper to just slice the hash directly thereby reusing
// the same bytes rather than calling this method.
func (hash *Hash) CloneBytes() []byte {
newHash := make([]byte, HashSize)
copy(newHash, hash[:])
return newHash
}
// SetBytes sets the bytes which represent the hash. An error is returned if
// the number of bytes passed in is not HashSize.
func (hash *Hash) SetBytes(newHash []byte) error {
nhlen := len(newHash)
if nhlen != HashSize {
return fmt.Errorf("invalid hash length of %v, want %v", nhlen,
HashSize)
}
copy(hash[:], newHash)
return nil
}
// IsEqual returns true if target is the same as hash.
func (hash *Hash) IsEqual(target *Hash) bool {
if hash == nil && target == nil {
return true
}
if hash == nil || target == nil {
return false
}
return *hash == *target
}
// MarshalJSON serialises the hash as a JSON appropriate string value.
func (hash Hash) MarshalJSON() ([]byte, error) {
return json.Marshal(hash.String())
}
// UnmarshalJSON parses the hash with JSON appropriate string value.
func (hash *Hash) UnmarshalJSON(input []byte) error {
// If the first byte indicates an array, the hash could have been marshalled
// using the legacy method and e.g. persisted.
if len(input) > 0 && input[0] == '[' {
return decodeLegacy(hash, input)
}
var sh string
err := json.Unmarshal(input, &sh)
if err != nil {
return err
}
newHash, err := NewHashFromStr(sh)
if err != nil {
return err
}
return hash.SetBytes(newHash[:])
}
// NewHash returns a new Hash from a byte slice. An error is returned if
// the number of bytes passed in is not HashSize.
func NewHash(newHash []byte) (*Hash, error) {
var sh Hash
err := sh.SetBytes(newHash)
if err != nil {
return nil, err
}
return &sh, err
}
// TaggedHash implements the tagged hash scheme described in BIP-340. We use
// sha-256 to bind a message hash to a specific context using a tag:
// sha256(sha256(tag) || sha256(tag) || msg).
func TaggedHash(tag []byte, msgs ...[]byte) *Hash {
// Check to see if we've already pre-computed the hash of the tag. If
// so then this'll save us an extra sha256 hash.
shaTag, ok := precomputedTags[string(tag)]
if !ok {
shaTag = sha256.Sum256(tag)
}
// h = sha256(sha256(tag) || sha256(tag) || msg)
h := sha256.New()
h.Write(shaTag[:])
h.Write(shaTag[:])
for _, msg := range msgs {
h.Write(msg)
}
taggedHash := h.Sum(nil)
// The function can't error out since the above hash is guaranteed to
// be 32 bytes.
hash, _ := NewHash(taggedHash)
return hash
}
// NewHashFromStr creates a Hash from a hash string. The string should be
// the hexadecimal string of a byte-reversed hash, but any missing characters
// result in zero padding at the end of the Hash.
func NewHashFromStr(hash string) (*Hash, error) {
ret := new(Hash)
err := Decode(ret, hash)
if err != nil {
return nil, err
}
return ret, nil
}
// Decode decodes the byte-reversed hexadecimal string encoding of a Hash to a
// destination.
func Decode(dst *Hash, src string) error {
// Return error if hash string is too long.
if len(src) > MaxHashStringSize {
return ErrHashStrSize
}
// Hex decoder expects the hash to be a multiple of two. When not, pad
// with a leading zero.
var srcBytes []byte
if len(src)%2 == 0 {
srcBytes = []byte(src)
} else {
srcBytes = make([]byte, 1+len(src))
srcBytes[0] = '0'
copy(srcBytes[1:], src)
}
// Hex decode the source bytes to a temporary destination.
var reversedHash Hash
_, err := hex.Decode(reversedHash[HashSize-hex.DecodedLen(len(srcBytes)):], srcBytes)
if err != nil {
return err
}
// Reverse copy from the temporary hash to destination. Because the
// temporary was zeroed, the written result will be correctly padded.
for i, b := range reversedHash[:HashSize/2] {
dst[i], dst[HashSize-1-i] = reversedHash[HashSize-1-i], b
}
return nil
}
// decodeLegacy decodes an Hash that has been encoded with the legacy method
// (i.e. represented as a bytes array) to a destination.
func decodeLegacy(dst *Hash, src []byte) error {
var hashBytes []byte
err := json.Unmarshal(src, &hashBytes)
if err != nil {
return err
}
if len(hashBytes) != HashSize {
return ErrHashStrSize
}
return dst.SetBytes(hashBytes)
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2015 The Decred developers
// Copyright (c) 2016-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package chainhash
import (
"crypto/sha256"
"io"
)
// HashB calculates hash(b) and returns the resulting bytes.
func HashB(b []byte) []byte {
hash := sha256.Sum256(b)
return hash[:]
}
// HashH calculates hash(b) and returns the resulting bytes as a Hash.
func HashH(b []byte) Hash {
return Hash(sha256.Sum256(b))
}
// DoubleHashB calculates hash(hash(b)) and returns the resulting bytes.
func DoubleHashB(b []byte) []byte {
first := sha256.Sum256(b)
second := sha256.Sum256(first[:])
return second[:]
}
// DoubleHashH calculates hash(hash(b)) and returns the resulting bytes as a
// Hash.
func DoubleHashH(b []byte) Hash {
first := sha256.Sum256(b)
return Hash(sha256.Sum256(first[:]))
}
// DoubleHashRaw calculates hash(hash(w)) where w is the resulting bytes from
// the given serialize function and returns the resulting bytes as a Hash.
func DoubleHashRaw(serialize func(w io.Writer) error) Hash {
// Encode the transaction into the hash. Ignore the error returns
// since the only way the encode could fail is being out of memory
// or due to nil pointers, both of which would cause a run-time panic.
h := sha256.New()
_ = serialize(h)
// This buf is here because Sum() will append the result to the passed
// in byte slice. Pre-allocating here saves an allocation on the second
// hash as we can reuse it. This allocation also does not escape to the
// heap, saving an allocation.
buf := make([]byte, 0, HashSize)
first := h.Sum(buf)
h.Reset()
h.Write(first)
res := h.Sum(buf)
return *(*Hash)(res)
}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+135
View File
@@ -0,0 +1,135 @@
//go:build (amd64 && go1.17 && !go1.25) || (arm64 && go1.20 && !go1.25)
// +build amd64,go1.17,!go1.25 arm64,go1.20,!go1.25
/*
* Copyright 2022 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
`runtime`
`unsafe`
`github.com/bytedance/sonic/encoder`
`github.com/bytedance/sonic/internal/native`
`github.com/bytedance/sonic/internal/native/types`
`github.com/bytedance/sonic/internal/rt`
uq `github.com/bytedance/sonic/unquote`
`github.com/bytedance/sonic/utf8`
)
var typeByte = rt.UnpackEface(byte(0)).Type
//go:nocheckptr
func quote(buf *[]byte, val string) {
*buf = append(*buf, '"')
if len(val) == 0 {
*buf = append(*buf, '"')
return
}
sp := rt.IndexChar(val, 0)
nb := len(val)
b := (*rt.GoSlice)(unsafe.Pointer(buf))
// input buffer
for nb > 0 {
// output buffer
dp := unsafe.Pointer(uintptr(b.Ptr) + uintptr(b.Len))
dn := b.Cap - b.Len
// call native.Quote, dn is byte count it outputs
ret := native.Quote(sp, nb, dp, &dn, 0)
// update *buf length
b.Len += dn
// no need more output
if ret >= 0 {
break
}
// double buf size
*b = rt.GrowSlice(typeByte, *b, b.Cap*2)
// ret is the complement of consumed input
ret = ^ret
// update input buffer
nb -= ret
sp = unsafe.Pointer(uintptr(sp) + uintptr(ret))
}
runtime.KeepAlive(buf)
runtime.KeepAlive(sp)
*buf = append(*buf, '"')
}
func unquote(src string) (string, types.ParsingError) {
return uq.String(src)
}
func (self *Parser) decodeValue() (val types.JsonState) {
sv := (*rt.GoString)(unsafe.Pointer(&self.s))
flag := types.F_USE_NUMBER
if self.dbuf != nil {
flag = 0
val.Dbuf = self.dbuf
val.Dcap = types.MaxDigitNums
}
self.p = native.Value(sv.Ptr, sv.Len, self.p, &val, uint64(flag))
return
}
func (self *Parser) skip() (int, types.ParsingError) {
fsm := types.NewStateMachine()
start := native.SkipOne(&self.s, &self.p, fsm, 0)
types.FreeStateMachine(fsm)
if start < 0 {
return self.p, types.ParsingError(-start)
}
return start, 0
}
func (self *Node) encodeInterface(buf *[]byte) error {
//WARN: NOT compatible with json.Encoder
return encoder.EncodeInto(buf, self.packAny(), encoder.NoEncoderNewline)
}
func (self *Parser) skipFast() (int, types.ParsingError) {
start := native.SkipOneFast(&self.s, &self.p)
if start < 0 {
return self.p, types.ParsingError(-start)
}
return start, 0
}
func (self *Parser) getByPath(validate bool, path ...interface{}) (int, types.ParsingError) {
var fsm *types.StateMachine
if validate {
fsm = types.NewStateMachine()
}
start := native.GetByPath(&self.s, &self.p, &path, fsm)
if validate {
types.FreeStateMachine(fsm)
}
runtime.KeepAlive(path)
if start < 0 {
return self.p, types.ParsingError(-start)
}
return start, 0
}
func validate_utf8(str string) bool {
return utf8.ValidateString(str)
}
+115
View File
@@ -0,0 +1,115 @@
// +build !amd64,!arm64 go1.25 !go1.17 arm64,!go1.20
/*
* Copyright 2022 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
`encoding/json`
`unicode/utf8`
`github.com/bytedance/sonic/internal/native/types`
`github.com/bytedance/sonic/internal/rt`
`github.com/bytedance/sonic/internal/compat`
)
func init() {
compat.Warn("sonic/ast")
}
func quote(buf *[]byte, val string) {
quoteString(buf, val)
}
// unquote unescapes an internal JSON string (it doesn't count quotas at the beginning and end)
func unquote(src string) (string, types.ParsingError) {
sp := rt.IndexChar(src, -1)
out, ok := unquoteBytes(rt.BytesFrom(sp, len(src)+2, len(src)+2))
if !ok {
return "", types.ERR_INVALID_ESCAPE
}
return rt.Mem2Str(out), 0
}
func (self *Parser) decodeValue() (val types.JsonState) {
e, v := decodeValue(self.s, self.p, self.dbuf == nil)
if e < 0 {
return v
}
self.p = e
return v
}
func (self *Parser) skip() (int, types.ParsingError) {
e, s := skipValue(self.s, self.p)
if e < 0 {
return self.p, types.ParsingError(-e)
}
self.p = e
return s, 0
}
func (self *Parser) skipFast() (int, types.ParsingError) {
e, s := skipValueFast(self.s, self.p)
if e < 0 {
return self.p, types.ParsingError(-e)
}
self.p = e
return s, 0
}
func (self *Node) encodeInterface(buf *[]byte) error {
out, err := json.Marshal(self.packAny())
if err != nil {
return err
}
*buf = append(*buf, out...)
return nil
}
func (self *Parser) getByPath(validate bool, path ...interface{}) (int, types.ParsingError) {
for _, p := range path {
if idx, ok := p.(int); ok && idx >= 0 {
if err := self.searchIndex(idx); err != 0 {
return self.p, err
}
} else if key, ok := p.(string); ok {
if err := self.searchKey(key); err != 0 {
return self.p, err
}
} else {
panic("path must be either int(>=0) or string")
}
}
var start int
var e types.ParsingError
if validate {
start, e = self.skip()
} else {
start, e = self.skipFast()
}
if e != 0 {
return self.p, e
}
return start, 0
}
func validate_utf8(str string) bool {
return utf8.ValidString(str)
}
View File
+470
View File
@@ -0,0 +1,470 @@
/**
* Copyright 2023 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
"sort"
"unsafe"
"github.com/bytedance/sonic/internal/caching"
)
type nodeChunk [_DEFAULT_NODE_CAP]Node
type linkedNodes struct {
head nodeChunk
tail []*nodeChunk
size int
}
func (self *linkedNodes) Cap() int {
if self == nil {
return 0
}
return (len(self.tail)+1)*_DEFAULT_NODE_CAP
}
func (self *linkedNodes) Len() int {
if self == nil {
return 0
}
return self.size
}
func (self *linkedNodes) At(i int) (*Node) {
if self == nil {
return nil
}
if i >= 0 && i<self.size && i < _DEFAULT_NODE_CAP {
return &self.head[i]
} else if i >= _DEFAULT_NODE_CAP && i<self.size {
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < len(self.tail) {
return &self.tail[a][b]
}
}
return nil
}
func (self *linkedNodes) MoveOne(source int, target int) {
if source == target {
return
}
if source < 0 || source >= self.size || target < 0 || target >= self.size {
return
}
// reserve source
n := *self.At(source)
if source < target {
// move every element (source,target] one step back
for i:=source; i<target; i++ {
*self.At(i) = *self.At(i+1)
}
} else {
// move every element [target,source) one step forward
for i:=source; i>target; i-- {
*self.At(i) = *self.At(i-1)
}
}
// set target
*self.At(target) = n
}
func (self *linkedNodes) Pop() {
if self == nil || self.size == 0 {
return
}
self.Set(self.size-1, Node{})
self.size--
}
func (self *linkedNodes) Push(v Node) {
self.Set(self.size, v)
}
func (self *linkedNodes) Set(i int, v Node) {
if i < _DEFAULT_NODE_CAP {
self.head[i] = v
if self.size <= i {
self.size = i+1
}
return
}
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < 0 {
self.head[b] = v
} else {
self.growTailLength(a+1)
var n = &self.tail[a]
if *n == nil {
*n = new(nodeChunk)
}
(*n)[b] = v
}
if self.size <= i {
self.size = i+1
}
}
func (self *linkedNodes) growTailLength(l int) {
if l <= len(self.tail) {
return
}
c := cap(self.tail)
for c < l {
c += 1 + c>>_APPEND_GROW_SHIFT
}
if c == cap(self.tail) {
self.tail = self.tail[:l]
return
}
tmp := make([]*nodeChunk, l, c)
copy(tmp, self.tail)
self.tail = tmp
}
func (self *linkedNodes) ToSlice(con []Node) {
if len(con) < self.size {
return
}
i := (self.size-1)
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < 0 {
copy(con, self.head[:b+1])
return
} else {
copy(con, self.head[:])
con = con[_DEFAULT_NODE_CAP:]
}
for i:=0; i<a; i++ {
copy(con, self.tail[i][:])
con = con[_DEFAULT_NODE_CAP:]
}
copy(con, self.tail[a][:b+1])
}
func (self *linkedNodes) FromSlice(con []Node) {
self.size = len(con)
i := self.size-1
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < 0 {
copy(self.head[:b+1], con)
return
} else {
copy(self.head[:], con)
con = con[_DEFAULT_NODE_CAP:]
}
if cap(self.tail) <= a {
c := (a+1) + (a+1)>>_APPEND_GROW_SHIFT
self.tail = make([]*nodeChunk, a+1, c)
}
self.tail = self.tail[:a+1]
for i:=0; i<a; i++ {
self.tail[i] = new(nodeChunk)
copy(self.tail[i][:], con)
con = con[_DEFAULT_NODE_CAP:]
}
self.tail[a] = new(nodeChunk)
copy(self.tail[a][:b+1], con)
}
type pairChunk [_DEFAULT_NODE_CAP]Pair
type linkedPairs struct {
index map[uint64]int
head pairChunk
tail []*pairChunk
size int
}
func (self *linkedPairs) BuildIndex() {
if self.index == nil {
self.index = make(map[uint64]int, self.size)
}
for i:=0; i<self.size; i++ {
p := self.At(i)
self.index[p.hash] = i
}
}
func (self *linkedPairs) Cap() int {
if self == nil {
return 0
}
return (len(self.tail)+1)*_DEFAULT_NODE_CAP
}
func (self *linkedPairs) Len() int {
if self == nil {
return 0
}
return self.size
}
func (self *linkedPairs) At(i int) *Pair {
if self == nil {
return nil
}
if i >= 0 && i < _DEFAULT_NODE_CAP && i<self.size {
return &self.head[i]
} else if i >= _DEFAULT_NODE_CAP && i<self.size {
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < len(self.tail) {
return &self.tail[a][b]
}
}
return nil
}
func (self *linkedPairs) Push(v Pair) {
self.Set(self.size, v)
}
func (self *linkedPairs) Pop() {
if self == nil || self.size == 0 {
return
}
self.Unset(self.size-1)
self.size--
}
func (self *linkedPairs) Unset(i int) {
if self.index != nil {
p := self.At(i)
delete(self.index, p.hash)
}
self.set(i, Pair{})
}
func (self *linkedPairs) Set(i int, v Pair) {
if self.index != nil {
h := v.hash
self.index[h] = i
}
self.set(i, v)
}
func (self *linkedPairs) set(i int, v Pair) {
if i < _DEFAULT_NODE_CAP {
self.head[i] = v
if self.size <= i {
self.size = i+1
}
return
}
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < 0 {
self.head[b] = v
} else {
self.growTailLength(a+1)
var n = &self.tail[a]
if *n == nil {
*n = new(pairChunk)
}
(*n)[b] = v
}
if self.size <= i {
self.size = i+1
}
}
func (self *linkedPairs) growTailLength(l int) {
if l <= len(self.tail) {
return
}
c := cap(self.tail)
for c < l {
c += 1 + c>>_APPEND_GROW_SHIFT
}
if c == cap(self.tail) {
self.tail = self.tail[:l]
return
}
tmp := make([]*pairChunk, l, c)
copy(tmp, self.tail)
self.tail = tmp
}
// linear search
func (self *linkedPairs) Get(key string) (*Pair, int) {
if self.index != nil {
// fast-path
i, ok := self.index[caching.StrHash(key)]
if ok {
n := self.At(i)
if n.Key == key {
return n, i
}
// hash conflicts
goto linear_search
} else {
return nil, -1
}
}
linear_search:
for i:=0; i<self.size; i++ {
if n := self.At(i); n.Key == key {
return n, i
}
}
return nil, -1
}
func (self *linkedPairs) ToSlice(con []Pair) {
if len(con) < self.size {
return
}
i := self.size-1
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < 0 {
copy(con, self.head[:b+1])
return
} else {
copy(con, self.head[:])
con = con[_DEFAULT_NODE_CAP:]
}
for i:=0; i<a; i++ {
copy(con, self.tail[i][:])
con = con[_DEFAULT_NODE_CAP:]
}
copy(con, self.tail[a][:b+1])
}
func (self *linkedPairs) ToMap(con map[string]Node) {
for i:=0; i<self.size; i++ {
n := self.At(i)
con[n.Key] = n.Value
}
}
func (self *linkedPairs) copyPairs(to []Pair, from []Pair, l int) {
copy(to, from)
if self.index != nil {
for i:=0; i<l; i++ {
// NOTICE: in case of user not pass hash, just cal it
h := caching.StrHash(from[i].Key)
from[i].hash = h
self.index[h] = i
}
}
}
func (self *linkedPairs) FromSlice(con []Pair) {
self.size = len(con)
i := self.size-1
a, b := i/_DEFAULT_NODE_CAP-1, i%_DEFAULT_NODE_CAP
if a < 0 {
self.copyPairs(self.head[:b+1], con, b+1)
return
} else {
self.copyPairs(self.head[:], con, len(self.head))
con = con[_DEFAULT_NODE_CAP:]
}
if cap(self.tail) <= a {
c := (a+1) + (a+1)>>_APPEND_GROW_SHIFT
self.tail = make([]*pairChunk, a+1, c)
}
self.tail = self.tail[:a+1]
for i:=0; i<a; i++ {
self.tail[i] = new(pairChunk)
self.copyPairs(self.tail[i][:], con, len(self.tail[i]))
con = con[_DEFAULT_NODE_CAP:]
}
self.tail[a] = new(pairChunk)
self.copyPairs(self.tail[a][:b+1], con, b+1)
}
func (self *linkedPairs) Less(i, j int) bool {
return lessFrom(self.At(i).Key, self.At(j).Key, 0)
}
func (self *linkedPairs) Swap(i, j int) {
a, b := self.At(i), self.At(j)
if self.index != nil {
self.index[a.hash] = j
self.index[b.hash] = i
}
*a, *b = *b, *a
}
func (self *linkedPairs) Sort() {
sort.Stable(self)
}
// Compare two strings from the pos d.
func lessFrom(a, b string, d int) bool {
l := len(a)
if l > len(b) {
l = len(b)
}
for i := d; i < l; i++ {
if a[i] == b[i] {
continue
}
return a[i] < b[i]
}
return len(a) < len(b)
}
type parseObjectStack struct {
parser Parser
v linkedPairs
}
type parseArrayStack struct {
parser Parser
v linkedNodes
}
func newLazyArray(p *Parser) Node {
s := new(parseArrayStack)
s.parser = *p
return Node{
t: _V_ARRAY_LAZY,
p: unsafe.Pointer(s),
}
}
func newLazyObject(p *Parser) Node {
s := new(parseObjectStack)
s.parser = *p
return Node{
t: _V_OBJECT_LAZY,
p: unsafe.Pointer(s),
}
}
func (self *Node) getParserAndArrayStack() (*Parser, *parseArrayStack) {
stack := (*parseArrayStack)(self.p)
return &stack.parser, stack
}
func (self *Node) getParserAndObjectStack() (*Parser, *parseObjectStack) {
stack := (*parseObjectStack)(self.p)
return &stack.parser, stack
}
+562
View File
@@ -0,0 +1,562 @@
/*
* Copyright 2022 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
"encoding/base64"
"runtime"
"strconv"
"unsafe"
"github.com/bytedance/sonic/internal/native/types"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/internal/utils"
)
// Hack: this is used for both checking space and cause friendly compile errors in 32-bit arch.
const _Sonic_Not_Support_32Bit_Arch__Checking_32Bit_Arch_Here = (1 << ' ') | (1 << '\t') | (1 << '\r') | (1 << '\n')
var bytesNull = []byte("null")
const (
strNull = "null"
bytesTrue = "true"
bytesFalse = "false"
bytesObject = "{}"
bytesArray = "[]"
)
func isSpace(c byte) bool {
return (int(1<<c) & _Sonic_Not_Support_32Bit_Arch__Checking_32Bit_Arch_Here) != 0
}
//go:nocheckptr
func skipBlank(src string, pos int) int {
se := uintptr(rt.IndexChar(src, len(src)))
sp := uintptr(rt.IndexChar(src, pos))
for sp < se {
if !isSpace(*(*byte)(unsafe.Pointer(sp))) {
break
}
sp += 1
}
if sp >= se {
return -int(types.ERR_EOF)
}
runtime.KeepAlive(src)
return int(sp - uintptr(rt.IndexChar(src, 0)))
}
func decodeNull(src string, pos int) (ret int) {
ret = pos + 4
if ret > len(src) {
return -int(types.ERR_EOF)
}
if src[pos:ret] == strNull {
return ret
} else {
return -int(types.ERR_INVALID_CHAR)
}
}
func decodeTrue(src string, pos int) (ret int) {
ret = pos + 4
if ret > len(src) {
return -int(types.ERR_EOF)
}
if src[pos:ret] == bytesTrue {
return ret
} else {
return -int(types.ERR_INVALID_CHAR)
}
}
func decodeFalse(src string, pos int) (ret int) {
ret = pos + 5
if ret > len(src) {
return -int(types.ERR_EOF)
}
if src[pos:ret] == bytesFalse {
return ret
}
return -int(types.ERR_INVALID_CHAR)
}
//go:nocheckptr
func decodeString(src string, pos int) (ret int, v string) {
ret, ep := skipString(src, pos)
if ep == -1 {
(*rt.GoString)(unsafe.Pointer(&v)).Ptr = rt.IndexChar(src, pos+1)
(*rt.GoString)(unsafe.Pointer(&v)).Len = ret - pos - 2
return ret, v
}
vv, ok := unquoteBytes(rt.Str2Mem(src[pos:ret]))
if !ok {
return -int(types.ERR_INVALID_CHAR), ""
}
runtime.KeepAlive(src)
return ret, rt.Mem2Str(vv)
}
func decodeBinary(src string, pos int) (ret int, v []byte) {
var vv string
ret, vv = decodeString(src, pos)
if ret < 0 {
return ret, nil
}
var err error
v, err = base64.StdEncoding.DecodeString(vv)
if err != nil {
return -int(types.ERR_INVALID_CHAR), nil
}
return ret, v
}
func isDigit(c byte) bool {
return c >= '0' && c <= '9'
}
//go:nocheckptr
func decodeInt64(src string, pos int) (ret int, v int64, err error) {
sp := uintptr(rt.IndexChar(src, pos))
ss := uintptr(sp)
se := uintptr(rt.IndexChar(src, len(src)))
if uintptr(sp) >= se {
return -int(types.ERR_EOF), 0, nil
}
if c := *(*byte)(unsafe.Pointer(sp)); c == '-' {
sp += 1
}
if sp == se {
return -int(types.ERR_EOF), 0, nil
}
for ; sp < se; sp += uintptr(1) {
if !isDigit(*(*byte)(unsafe.Pointer(sp))) {
break
}
}
if sp < se {
if c := *(*byte)(unsafe.Pointer(sp)); c == '.' || c == 'e' || c == 'E' {
return -int(types.ERR_INVALID_NUMBER_FMT), 0, nil
}
}
var vv string
ret = int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
(*rt.GoString)(unsafe.Pointer(&vv)).Ptr = unsafe.Pointer(ss)
(*rt.GoString)(unsafe.Pointer(&vv)).Len = ret - pos
v, err = strconv.ParseInt(vv, 10, 64)
if err != nil {
//NOTICE: allow overflow here
if err.(*strconv.NumError).Err == strconv.ErrRange {
return ret, 0, err
}
return -int(types.ERR_INVALID_CHAR), 0, err
}
runtime.KeepAlive(src)
return ret, v, nil
}
func isNumberChars(c byte) bool {
return (c >= '0' && c <= '9') || c == '+' || c == '-' || c == 'e' || c == 'E' || c == '.'
}
//go:nocheckptr
func decodeFloat64(src string, pos int) (ret int, v float64, err error) {
sp := uintptr(rt.IndexChar(src, pos))
ss := uintptr(sp)
se := uintptr(rt.IndexChar(src, len(src)))
if uintptr(sp) >= se {
return -int(types.ERR_EOF), 0, nil
}
if c := *(*byte)(unsafe.Pointer(sp)); c == '-' {
sp += 1
}
if sp == se {
return -int(types.ERR_EOF), 0, nil
}
for ; sp < se; sp += uintptr(1) {
if !isNumberChars(*(*byte)(unsafe.Pointer(sp))) {
break
}
}
var vv string
ret = int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
(*rt.GoString)(unsafe.Pointer(&vv)).Ptr = unsafe.Pointer(ss)
(*rt.GoString)(unsafe.Pointer(&vv)).Len = ret - pos
v, err = strconv.ParseFloat(vv, 64)
if err != nil {
//NOTICE: allow overflow here
if err.(*strconv.NumError).Err == strconv.ErrRange {
return ret, 0, err
}
return -int(types.ERR_INVALID_CHAR), 0, err
}
runtime.KeepAlive(src)
return ret, v, nil
}
func decodeValue(src string, pos int, skipnum bool) (ret int, v types.JsonState) {
pos = skipBlank(src, pos)
if pos < 0 {
return pos, types.JsonState{Vt: types.ValueType(pos)}
}
switch c := src[pos]; c {
case 'n':
ret = decodeNull(src, pos)
if ret < 0 {
return ret, types.JsonState{Vt: types.ValueType(ret)}
}
return ret, types.JsonState{Vt: types.V_NULL}
case '"':
var ep int
ret, ep = skipString(src, pos)
if ret < 0 {
return ret, types.JsonState{Vt: types.ValueType(ret)}
}
return ret, types.JsonState{Vt: types.V_STRING, Iv: int64(pos + 1), Ep: ep}
case '{':
return pos + 1, types.JsonState{Vt: types.V_OBJECT}
case '[':
return pos + 1, types.JsonState{Vt: types.V_ARRAY}
case 't':
ret = decodeTrue(src, pos)
if ret < 0 {
return ret, types.JsonState{Vt: types.ValueType(ret)}
}
return ret, types.JsonState{Vt: types.V_TRUE}
case 'f':
ret = decodeFalse(src, pos)
if ret < 0 {
return ret, types.JsonState{Vt: types.ValueType(ret)}
}
return ret, types.JsonState{Vt: types.V_FALSE}
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
if skipnum {
ret = skipNumber(src, pos)
if ret >= 0 {
return ret, types.JsonState{Vt: types.V_DOUBLE, Iv: 0, Ep: pos}
} else {
return ret, types.JsonState{Vt: types.ValueType(ret)}
}
} else {
var iv int64
ret, iv, _ = decodeInt64(src, pos)
if ret >= 0 {
return ret, types.JsonState{Vt: types.V_INTEGER, Iv: iv, Ep: pos}
} else if ret != -int(types.ERR_INVALID_NUMBER_FMT) {
return ret, types.JsonState{Vt: types.ValueType(ret)}
}
var fv float64
ret, fv, _ = decodeFloat64(src, pos)
if ret >= 0 {
return ret, types.JsonState{Vt: types.V_DOUBLE, Dv: fv, Ep: pos}
} else {
return ret, types.JsonState{Vt: types.ValueType(ret)}
}
}
default:
return -int(types.ERR_INVALID_CHAR), types.JsonState{Vt:-types.ValueType(types.ERR_INVALID_CHAR)}
}
}
//go:nocheckptr
func skipNumber(src string, pos int) (ret int) {
return utils.SkipNumber(src, pos)
}
//go:nocheckptr
func skipString(src string, pos int) (ret int, ep int) {
if pos+1 >= len(src) {
return -int(types.ERR_EOF), -1
}
sp := uintptr(rt.IndexChar(src, pos))
se := uintptr(rt.IndexChar(src, len(src)))
// not start with quote
if *(*byte)(unsafe.Pointer(sp)) != '"' {
return -int(types.ERR_INVALID_CHAR), -1
}
sp += 1
ep = -1
for sp < se {
c := *(*byte)(unsafe.Pointer(sp))
if c == '\\' {
if ep == -1 {
ep = int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
}
sp += 2
continue
}
sp += 1
if c == '"' {
return int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr)), ep
}
}
runtime.KeepAlive(src)
// not found the closed quote until EOF
return -int(types.ERR_EOF), -1
}
//go:nocheckptr
func skipPair(src string, pos int, lchar byte, rchar byte) (ret int) {
if pos+1 >= len(src) {
return -int(types.ERR_EOF)
}
sp := uintptr(rt.IndexChar(src, pos))
se := uintptr(rt.IndexChar(src, len(src)))
if *(*byte)(unsafe.Pointer(sp)) != lchar {
return -int(types.ERR_INVALID_CHAR)
}
sp += 1
nbrace := 1
inquote := false
for sp < se {
c := *(*byte)(unsafe.Pointer(sp))
if c == '\\' {
sp += 2
continue
} else if c == '"' {
inquote = !inquote
} else if c == lchar {
if !inquote {
nbrace += 1
}
} else if c == rchar {
if !inquote {
nbrace -= 1
if nbrace == 0 {
sp += 1
break
}
}
}
sp += 1
}
if nbrace != 0 {
return -int(types.ERR_INVALID_CHAR)
}
runtime.KeepAlive(src)
return int(uintptr(sp) - uintptr((*rt.GoString)(unsafe.Pointer(&src)).Ptr))
}
func skipValueFast(src string, pos int) (ret int, start int) {
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
switch c := src[pos]; c {
case 'n':
ret = decodeNull(src, pos)
case '"':
ret, _ = skipString(src, pos)
case '{':
ret = skipPair(src, pos, '{', '}')
case '[':
ret = skipPair(src, pos, '[', ']')
case 't':
ret = decodeTrue(src, pos)
case 'f':
ret = decodeFalse(src, pos)
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
ret = skipNumber(src, pos)
default:
ret = -int(types.ERR_INVALID_CHAR)
}
return ret, pos
}
func skipValue(src string, pos int) (ret int, start int) {
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
switch c := src[pos]; c {
case 'n':
ret = decodeNull(src, pos)
case '"':
ret, _ = skipString(src, pos)
case '{':
ret, _ = skipObject(src, pos)
case '[':
ret, _ = skipArray(src, pos)
case 't':
ret = decodeTrue(src, pos)
case 'f':
ret = decodeFalse(src, pos)
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
ret = skipNumber(src, pos)
default:
ret = -int(types.ERR_INVALID_CHAR)
}
return ret, pos
}
func skipObject(src string, pos int) (ret int, start int) {
start = skipBlank(src, pos)
if start < 0 {
return start, -1
}
if src[start] != '{' {
return -int(types.ERR_INVALID_CHAR), -1
}
pos = start + 1
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
if src[pos] == '}' {
return pos + 1, start
}
for {
pos, _ = skipString(src, pos)
if pos < 0 {
return pos, -1
}
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
if src[pos] != ':' {
return -int(types.ERR_INVALID_CHAR), -1
}
pos++
pos, _ = skipValue(src, pos)
if pos < 0 {
return pos, -1
}
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
if src[pos] == '}' {
return pos + 1, start
}
if src[pos] != ',' {
return -int(types.ERR_INVALID_CHAR), -1
}
pos++
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
}
}
func skipArray(src string, pos int) (ret int, start int) {
start = skipBlank(src, pos)
if start < 0 {
return start, -1
}
if src[start] != '[' {
return -int(types.ERR_INVALID_CHAR), -1
}
pos = start + 1
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
if src[pos] == ']' {
return pos + 1, start
}
for {
pos, _ = skipValue(src, pos)
if pos < 0 {
return pos, -1
}
pos = skipBlank(src, pos)
if pos < 0 {
return pos, -1
}
if src[pos] == ']' {
return pos + 1, start
}
if src[pos] != ',' {
return -int(types.ERR_INVALID_CHAR), -1
}
pos++
}
}
// DecodeString decodes a JSON string from pos and return golang string.
// - needEsc indicates if to unescaped escaping chars
// - hasEsc tells if the returned string has escaping chars
// - validStr enables validating UTF8 charset
//
func _DecodeString(src string, pos int, needEsc bool, validStr bool) (v string, ret int, hasEsc bool) {
p := NewParserObj(src)
p.p = pos
switch val := p.decodeValue(); val.Vt {
case types.V_STRING:
str := p.s[val.Iv : p.p-1]
if validStr && !validate_utf8(str) {
return "", -int(types.ERR_INVALID_UTF8), false
}
/* fast path: no escape sequence */
if val.Ep == -1 {
return str, p.p, false
} else if !needEsc {
return str, p.p, true
}
/* unquote the string */
out, err := unquote(str)
/* check for errors */
if err != 0 {
return "", -int(err), true
} else {
return out, p.p, true
}
default:
return "", -int(_ERR_UNSUPPORT_TYPE), false
}
}
+274
View File
@@ -0,0 +1,274 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
"sync"
"unicode/utf8"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/option"
)
func quoteString(e *[]byte, s string) {
*e = append(*e, '"')
start := 0
for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf {
if rt.SafeSet[b] {
i++
continue
}
if start < i {
*e = append(*e, s[start:i]...)
}
*e = append(*e, '\\')
switch b {
case '\\', '"':
*e = append(*e, b)
case '\n':
*e = append(*e, 'n')
case '\r':
*e = append(*e, 'r')
case '\t':
*e = append(*e, 't')
default:
// This encodes bytes < 0x20 except for \t, \n and \r.
// If escapeHTML is set, it also escapes <, >, and &
// because they can lead to security holes when
// user-controlled strings are rendered into JSON
// and served to some browsers.
*e = append(*e, `u00`...)
*e = append(*e, rt.Hex[b>>4])
*e = append(*e, rt.Hex[b&0xF])
}
i++
start = i
continue
}
c, size := utf8.DecodeRuneInString(s[i:])
// if c == utf8.RuneError && size == 1 {
// if start < i {
// e.Write(s[start:i])
// }
// e.WriteString(`\ufffd`)
// i += size
// start = i
// continue
// }
if c == '\u2028' || c == '\u2029' {
if start < i {
*e = append(*e, s[start:i]...)
}
*e = append(*e, `\u202`...)
*e = append(*e, rt.Hex[c&0xF])
i += size
start = i
continue
}
i += size
}
if start < len(s) {
*e = append(*e, s[start:]...)
}
*e = append(*e, '"')
}
var bytesPool = sync.Pool{}
func (self *Node) MarshalJSON() ([]byte, error) {
if self == nil {
return bytesNull, nil
}
buf := newBuffer()
err := self.encode(buf)
if err != nil {
freeBuffer(buf)
return nil, err
}
var ret []byte
if !rt.CanSizeResue(cap(*buf)) {
ret = *buf
} else {
ret = make([]byte, len(*buf))
copy(ret, *buf)
freeBuffer(buf)
}
return ret, err
}
func newBuffer() *[]byte {
if ret := bytesPool.Get(); ret != nil {
return ret.(*[]byte)
} else {
buf := make([]byte, 0, option.DefaultAstBufferSize)
return &buf
}
}
func freeBuffer(buf *[]byte) {
if !rt.CanSizeResue(cap(*buf)) {
return
}
*buf = (*buf)[:0]
bytesPool.Put(buf)
}
func (self *Node) encode(buf *[]byte) error {
if self.isRaw() {
return self.encodeRaw(buf)
}
switch int(self.itype()) {
case V_NONE : return ErrNotExist
case V_ERROR : return self.Check()
case V_NULL : return self.encodeNull(buf)
case V_TRUE : return self.encodeTrue(buf)
case V_FALSE : return self.encodeFalse(buf)
case V_ARRAY : return self.encodeArray(buf)
case V_OBJECT: return self.encodeObject(buf)
case V_STRING: return self.encodeString(buf)
case V_NUMBER: return self.encodeNumber(buf)
case V_ANY : return self.encodeInterface(buf)
default : return ErrUnsupportType
}
}
func (self *Node) encodeRaw(buf *[]byte) error {
lock := self.rlock()
if !self.isRaw() {
self.runlock()
return self.encode(buf)
}
raw := self.toString()
if lock {
self.runlock()
}
*buf = append(*buf, raw...)
return nil
}
func (self *Node) encodeNull(buf *[]byte) error {
*buf = append(*buf, strNull...)
return nil
}
func (self *Node) encodeTrue(buf *[]byte) error {
*buf = append(*buf, bytesTrue...)
return nil
}
func (self *Node) encodeFalse(buf *[]byte) error {
*buf = append(*buf, bytesFalse...)
return nil
}
func (self *Node) encodeNumber(buf *[]byte) error {
str := self.toString()
*buf = append(*buf, str...)
return nil
}
func (self *Node) encodeString(buf *[]byte) error {
if self.l == 0 {
*buf = append(*buf, '"', '"')
return nil
}
quote(buf, self.toString())
return nil
}
func (self *Node) encodeArray(buf *[]byte) error {
if self.isLazy() {
if err := self.skipAllIndex(); err != nil {
return err
}
}
nb := self.len()
if nb == 0 {
*buf = append(*buf, bytesArray...)
return nil
}
*buf = append(*buf, '[')
var started bool
for i := 0; i < nb; i++ {
n := self.nodeAt(i)
if !n.Exists() {
continue
}
if started {
*buf = append(*buf, ',')
}
started = true
if err := n.encode(buf); err != nil {
return err
}
}
*buf = append(*buf, ']')
return nil
}
func (self *Pair) encode(buf *[]byte) error {
if len(*buf) == 0 {
*buf = append(*buf, '"', '"', ':')
return self.Value.encode(buf)
}
quote(buf, self.Key)
*buf = append(*buf, ':')
return self.Value.encode(buf)
}
func (self *Node) encodeObject(buf *[]byte) error {
if self.isLazy() {
if err := self.skipAllKey(); err != nil {
return err
}
}
nb := self.len()
if nb == 0 {
*buf = append(*buf, bytesObject...)
return nil
}
*buf = append(*buf, '{')
var started bool
for i := 0; i < nb; i++ {
n := self.pairAt(i)
if n == nil || !n.Value.Exists() {
continue
}
if started {
*buf = append(*buf, ',')
}
started = true
if err := n.encode(buf); err != nil {
return err
}
}
*buf = append(*buf, '}')
return nil
}
+134
View File
@@ -0,0 +1,134 @@
package ast
import (
`fmt`
`strings`
`unsafe`
`github.com/bytedance/sonic/internal/native/types`
)
func newError(err types.ParsingError, msg string) *Node {
return &Node{
t: V_ERROR,
l: uint(err),
p: unsafe.Pointer(&msg),
}
}
func newErrorPair(err SyntaxError) *Pair {
return &Pair{0, "", *newSyntaxError(err)}
}
// Error returns error message if the node is invalid
func (self Node) Error() string {
if self.t != V_ERROR {
return ""
} else {
return *(*string)(self.p)
}
}
func newSyntaxError(err SyntaxError) *Node {
msg := err.Description()
return &Node{
t: V_ERROR,
l: uint(err.Code),
p: unsafe.Pointer(&msg),
}
}
func (self *Parser) syntaxError(err types.ParsingError) SyntaxError {
return SyntaxError{
Pos : self.p,
Src : self.s,
Code: err,
}
}
func unwrapError(err error) *Node {
if se, ok := err.(*Node); ok {
return se
}else if sse, ok := err.(Node); ok {
return &sse
} else {
msg := err.Error()
return &Node{
t: V_ERROR,
p: unsafe.Pointer(&msg),
}
}
}
type SyntaxError struct {
Pos int
Src string
Code types.ParsingError
Msg string
}
func (self SyntaxError) Error() string {
return fmt.Sprintf("%q", self.Description())
}
func (self SyntaxError) Description() string {
return "Syntax error " + self.description()
}
func (self SyntaxError) description() string {
i := 16
p := self.Pos - i
q := self.Pos + i
/* check for empty source */
if self.Src == "" {
return fmt.Sprintf("no sources available, the input json is empty: %#v", self)
}
/* prevent slicing before the beginning */
if p < 0 {
p, q, i = 0, q - p, i + p
}
/* prevent slicing beyond the end */
if n := len(self.Src); q > n {
n = q - n
q = len(self.Src)
/* move the left bound if possible */
if p > n {
i += n
p -= n
}
}
/* left and right length */
x := clamp_zero(i)
y := clamp_zero(q - p - i - 1)
/* compose the error description */
return fmt.Sprintf(
"at index %d: %s\n\n\t%s\n\t%s^%s\n",
self.Pos,
self.Message(),
self.Src[p:q],
strings.Repeat(".", x),
strings.Repeat(".", y),
)
}
func (self SyntaxError) Message() string {
if self.Msg == "" {
return self.Code.Message()
}
return self.Msg
}
func clamp_zero(v int) int {
if v < 0 {
return 0
} else {
return v
}
}
+216
View File
@@ -0,0 +1,216 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
"fmt"
"github.com/bytedance/sonic/internal/caching"
"github.com/bytedance/sonic/internal/native/types"
)
type Pair struct {
hash uint64
Key string
Value Node
}
func NewPair(key string, val Node) Pair {
return Pair{
hash: caching.StrHash(key),
Key: key,
Value: val,
}
}
// Values returns iterator for array's children traversal
func (self *Node) Values() (ListIterator, error) {
if err := self.should(types.V_ARRAY); err != nil {
return ListIterator{}, err
}
return self.values(), nil
}
func (self *Node) values() ListIterator {
return ListIterator{Iterator{p: self}}
}
// Properties returns iterator for object's children traversal
func (self *Node) Properties() (ObjectIterator, error) {
if err := self.should(types.V_OBJECT); err != nil {
return ObjectIterator{}, err
}
return self.properties(), nil
}
func (self *Node) properties() ObjectIterator {
return ObjectIterator{Iterator{p: self}}
}
type Iterator struct {
i int
p *Node
}
func (self *Iterator) Pos() int {
return self.i
}
func (self *Iterator) Len() int {
return self.p.len()
}
// HasNext reports if it is the end of iteration or has error.
func (self *Iterator) HasNext() bool {
if !self.p.isLazy() {
return self.p.Valid() && self.i < self.p.len()
} else if self.p.t == _V_ARRAY_LAZY {
return self.p.skipNextNode().Valid()
} else if self.p.t == _V_OBJECT_LAZY {
pair := self.p.skipNextPair()
if pair == nil {
return false
}
return pair.Value.Valid()
}
return false
}
// ListIterator is specialized iterator for V_ARRAY
type ListIterator struct {
Iterator
}
// ObjectIterator is specialized iterator for V_ARRAY
type ObjectIterator struct {
Iterator
}
func (self *ListIterator) next() *Node {
next_start:
if !self.HasNext() {
return nil
} else {
n := self.p.nodeAt(self.i)
self.i++
if !n.Exists() {
goto next_start
}
return n
}
}
// Next scans through children of underlying V_ARRAY,
// copies each child to v, and returns .HasNext().
func (self *ListIterator) Next(v *Node) bool {
n := self.next()
if n == nil {
return false
}
*v = *n
return true
}
func (self *ObjectIterator) next() *Pair {
next_start:
if !self.HasNext() {
return nil
} else {
n := self.p.pairAt(self.i)
self.i++
if n == nil || !n.Value.Exists() {
goto next_start
}
return n
}
}
// Next scans through children of underlying V_OBJECT,
// copies each child to v, and returns .HasNext().
func (self *ObjectIterator) Next(p *Pair) bool {
n := self.next()
if n == nil {
return false
}
*p = *n
return true
}
// Sequence represents scanning path of single-layer nodes.
// Index indicates the value's order in both V_ARRAY and V_OBJECT json.
// Key is the value's key (for V_OBJECT json only, otherwise it will be nil).
type Sequence struct {
Index int
Key *string
// Level int
}
// String is string representation of one Sequence
func (s Sequence) String() string {
k := ""
if s.Key != nil {
k = *s.Key
}
return fmt.Sprintf("Sequence(%d, %q)", s.Index, k)
}
type Scanner func(path Sequence, node *Node) bool
// ForEach scans one V_OBJECT node's children from JSON head to tail,
// and pass the Sequence and Node of corresponding JSON value.
//
// Especially, if the node is not V_ARRAY or V_OBJECT,
// the node itself will be returned and Sequence.Index == -1.
//
// NOTICE: A unsetted node WON'T trigger sc, but its index still counts into Path.Index
func (self *Node) ForEach(sc Scanner) error {
if err := self.checkRaw(); err != nil {
return err
}
switch self.itype() {
case types.V_ARRAY:
iter, err := self.Values()
if err != nil {
return err
}
v := iter.next()
for v != nil {
if !sc(Sequence{iter.i-1, nil}, v) {
return nil
}
v = iter.next()
}
case types.V_OBJECT:
iter, err := self.Properties()
if err != nil {
return err
}
v := iter.next()
for v != nil {
if !sc(Sequence{iter.i-1, &v.Key}, &v.Value) {
return nil
}
v = iter.next()
}
default:
if self.Check() != nil {
return self
}
sc(Sequence{-1, nil}, self)
}
return nil
}
+1843
View File
File diff suppressed because it is too large Load Diff
+766
View File
@@ -0,0 +1,766 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
"fmt"
"sync"
"sync/atomic"
"github.com/bytedance/sonic/internal/native/types"
"github.com/bytedance/sonic/internal/rt"
)
const (
_DEFAULT_NODE_CAP int = 16
_APPEND_GROW_SHIFT = 1
)
const (
_ERR_NOT_FOUND types.ParsingError = 33
_ERR_UNSUPPORT_TYPE types.ParsingError = 34
)
var (
// ErrNotExist means both key and value doesn't exist
ErrNotExist error = newError(_ERR_NOT_FOUND, "value not exists")
// ErrUnsupportType means API on the node is unsupported
ErrUnsupportType error = newError(_ERR_UNSUPPORT_TYPE, "unsupported type")
)
type Parser struct {
p int
s string
noLazy bool
loadOnce bool
skipValue bool
dbuf *byte
}
/** Parser Private Methods **/
func (self *Parser) delim() types.ParsingError {
n := len(self.s)
p := self.lspace(self.p)
/* check for EOF */
if p >= n {
return types.ERR_EOF
}
/* check for the delimtier */
if self.s[p] != ':' {
return types.ERR_INVALID_CHAR
}
/* update the read pointer */
self.p = p + 1
return 0
}
func (self *Parser) object() types.ParsingError {
n := len(self.s)
p := self.lspace(self.p)
/* check for EOF */
if p >= n {
return types.ERR_EOF
}
/* check for the delimtier */
if self.s[p] != '{' {
return types.ERR_INVALID_CHAR
}
/* update the read pointer */
self.p = p + 1
return 0
}
func (self *Parser) array() types.ParsingError {
n := len(self.s)
p := self.lspace(self.p)
/* check for EOF */
if p >= n {
return types.ERR_EOF
}
/* check for the delimtier */
if self.s[p] != '[' {
return types.ERR_INVALID_CHAR
}
/* update the read pointer */
self.p = p + 1
return 0
}
func (self *Parser) lspace(sp int) int {
ns := len(self.s)
for ; sp<ns && isSpace(self.s[sp]); sp+=1 {}
return sp
}
func (self *Parser) backward() {
for ; self.p >= 0 && isSpace(self.s[self.p]); self.p-=1 {}
}
func (self *Parser) decodeArray(ret *linkedNodes) (Node, types.ParsingError) {
sp := self.p
ns := len(self.s)
/* check for EOF */
if self.p = self.lspace(sp); self.p >= ns {
return Node{}, types.ERR_EOF
}
/* check for empty array */
if self.s[self.p] == ']' {
self.p++
return Node{t: types.V_ARRAY}, 0
}
/* allocate array space and parse every element */
for {
var val Node
var err types.ParsingError
if self.skipValue {
/* skip the value */
var start int
if start, err = self.skipFast(); err != 0 {
return Node{}, err
}
if self.p > ns {
return Node{}, types.ERR_EOF
}
t := switchRawType(self.s[start])
if t == _V_NONE {
return Node{}, types.ERR_INVALID_CHAR
}
val = newRawNode(self.s[start:self.p], t, false)
}else{
/* decode the value */
if val, err = self.Parse(); err != 0 {
return Node{}, err
}
}
/* add the value to result */
ret.Push(val)
self.p = self.lspace(self.p)
/* check for EOF */
if self.p >= ns {
return Node{}, types.ERR_EOF
}
/* check for the next character */
switch self.s[self.p] {
case ',' : self.p++
case ']' : self.p++; return newArray(ret), 0
default:
// if val.isLazy() {
// return newLazyArray(self, ret), 0
// }
return Node{}, types.ERR_INVALID_CHAR
}
}
}
func (self *Parser) decodeObject(ret *linkedPairs) (Node, types.ParsingError) {
sp := self.p
ns := len(self.s)
/* check for EOF */
if self.p = self.lspace(sp); self.p >= ns {
return Node{}, types.ERR_EOF
}
/* check for empty object */
if self.s[self.p] == '}' {
self.p++
return Node{t: types.V_OBJECT}, 0
}
/* decode each pair */
for {
var val Node
var njs types.JsonState
var err types.ParsingError
/* decode the key */
if njs = self.decodeValue(); njs.Vt != types.V_STRING {
return Node{}, types.ERR_INVALID_CHAR
}
/* extract the key */
idx := self.p - 1
key := self.s[njs.Iv:idx]
/* check for escape sequence */
if njs.Ep != -1 {
if key, err = unquote(key); err != 0 {
return Node{}, err
}
}
/* expect a ':' delimiter */
if err = self.delim(); err != 0 {
return Node{}, err
}
if self.skipValue {
/* skip the value */
var start int
if start, err = self.skipFast(); err != 0 {
return Node{}, err
}
if self.p > ns {
return Node{}, types.ERR_EOF
}
t := switchRawType(self.s[start])
if t == _V_NONE {
return Node{}, types.ERR_INVALID_CHAR
}
val = newRawNode(self.s[start:self.p], t, false)
} else {
/* decode the value */
if val, err = self.Parse(); err != 0 {
return Node{}, err
}
}
/* add the value to result */
// FIXME: ret's address may change here, thus previous referred node in ret may be invalid !!
ret.Push(NewPair(key, val))
self.p = self.lspace(self.p)
/* check for EOF */
if self.p >= ns {
return Node{}, types.ERR_EOF
}
/* check for the next character */
switch self.s[self.p] {
case ',' : self.p++
case '}' : self.p++; return newObject(ret), 0
default:
// if val.isLazy() {
// return newLazyObject(self, ret), 0
// }
return Node{}, types.ERR_INVALID_CHAR
}
}
}
func (self *Parser) decodeString(iv int64, ep int) (Node, types.ParsingError) {
p := self.p - 1
s := self.s[iv:p]
/* fast path: no escape sequence */
if ep == -1 {
return NewString(s), 0
}
/* unquote the string */
out, err := unquote(s)
/* check for errors */
if err != 0 {
return Node{}, err
} else {
return newBytes(rt.Str2Mem(out)), 0
}
}
/** Parser Interface **/
func (self *Parser) Pos() int {
return self.p
}
// Parse returns a ast.Node representing the parser's JSON.
// NOTICE: the specific parsing lazy dependens parser's option
// It only parse first layer and first child for Object or Array be default
func (self *Parser) Parse() (Node, types.ParsingError) {
switch val := self.decodeValue(); val.Vt {
case types.V_EOF : return Node{}, types.ERR_EOF
case types.V_NULL : return nullNode, 0
case types.V_TRUE : return trueNode, 0
case types.V_FALSE : return falseNode, 0
case types.V_STRING : return self.decodeString(val.Iv, val.Ep)
case types.V_ARRAY:
s := self.p - 1;
if p := skipBlank(self.s, self.p); p >= self.p && self.s[p] == ']' {
self.p = p + 1
return Node{t: types.V_ARRAY}, 0
}
if self.noLazy {
if self.loadOnce {
self.noLazy = false
}
return self.decodeArray(new(linkedNodes))
}
// NOTICE: loadOnce always keep raw json for object or array
if self.loadOnce {
self.p = s
s, e := self.skipFast()
if e != 0 {
return Node{}, e
}
return newRawNode(self.s[s:self.p], types.V_ARRAY, true), 0
}
return newLazyArray(self), 0
case types.V_OBJECT:
s := self.p - 1;
if p := skipBlank(self.s, self.p); p >= self.p && self.s[p] == '}' {
self.p = p + 1
return Node{t: types.V_OBJECT}, 0
}
// NOTICE: loadOnce always keep raw json for object or array
if self.noLazy {
if self.loadOnce {
self.noLazy = false
}
return self.decodeObject(new(linkedPairs))
}
if self.loadOnce {
self.p = s
s, e := self.skipFast()
if e != 0 {
return Node{}, e
}
return newRawNode(self.s[s:self.p], types.V_OBJECT, true), 0
}
return newLazyObject(self), 0
case types.V_DOUBLE : return NewNumber(self.s[val.Ep:self.p]), 0
case types.V_INTEGER : return NewNumber(self.s[val.Ep:self.p]), 0
default : return Node{}, types.ParsingError(-val.Vt)
}
}
func (self *Parser) searchKey(match string) types.ParsingError {
ns := len(self.s)
if err := self.object(); err != 0 {
return err
}
/* check for EOF */
if self.p = self.lspace(self.p); self.p >= ns {
return types.ERR_EOF
}
/* check for empty object */
if self.s[self.p] == '}' {
self.p++
return _ERR_NOT_FOUND
}
var njs types.JsonState
var err types.ParsingError
/* decode each pair */
for {
/* decode the key */
if njs = self.decodeValue(); njs.Vt != types.V_STRING {
return types.ERR_INVALID_CHAR
}
/* extract the key */
idx := self.p - 1
key := self.s[njs.Iv:idx]
/* check for escape sequence */
if njs.Ep != -1 {
if key, err = unquote(key); err != 0 {
return err
}
}
/* expect a ':' delimiter */
if err = self.delim(); err != 0 {
return err
}
/* skip value */
if key != match {
if _, err = self.skipFast(); err != 0 {
return err
}
} else {
return 0
}
/* check for EOF */
self.p = self.lspace(self.p)
if self.p >= ns {
return types.ERR_EOF
}
/* check for the next character */
switch self.s[self.p] {
case ',':
self.p++
case '}':
self.p++
return _ERR_NOT_FOUND
default:
return types.ERR_INVALID_CHAR
}
}
}
func (self *Parser) searchIndex(idx int) types.ParsingError {
ns := len(self.s)
if err := self.array(); err != 0 {
return err
}
/* check for EOF */
if self.p = self.lspace(self.p); self.p >= ns {
return types.ERR_EOF
}
/* check for empty array */
if self.s[self.p] == ']' {
self.p++
return _ERR_NOT_FOUND
}
var err types.ParsingError
/* allocate array space and parse every element */
for i := 0; i < idx; i++ {
/* decode the value */
if _, err = self.skipFast(); err != 0 {
return err
}
/* check for EOF */
self.p = self.lspace(self.p)
if self.p >= ns {
return types.ERR_EOF
}
/* check for the next character */
switch self.s[self.p] {
case ',':
self.p++
case ']':
self.p++
return _ERR_NOT_FOUND
default:
return types.ERR_INVALID_CHAR
}
}
return 0
}
func (self *Node) skipNextNode() *Node {
if !self.isLazy() {
return nil
}
parser, stack := self.getParserAndArrayStack()
ret := &stack.v
sp := parser.p
ns := len(parser.s)
/* check for EOF */
if parser.p = parser.lspace(sp); parser.p >= ns {
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
}
/* check for empty array */
if parser.s[parser.p] == ']' {
parser.p++
self.setArray(ret)
return nil
}
var val Node
/* skip the value */
if start, err := parser.skipFast(); err != 0 {
return newSyntaxError(parser.syntaxError(err))
} else {
t := switchRawType(parser.s[start])
if t == _V_NONE {
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
}
val = newRawNode(parser.s[start:parser.p], t, false)
}
/* add the value to result */
ret.Push(val)
self.l++
parser.p = parser.lspace(parser.p)
/* check for EOF */
if parser.p >= ns {
return newSyntaxError(parser.syntaxError(types.ERR_EOF))
}
/* check for the next character */
switch parser.s[parser.p] {
case ',':
parser.p++
return ret.At(ret.Len()-1)
case ']':
parser.p++
self.setArray(ret)
return ret.At(ret.Len()-1)
default:
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
}
}
func (self *Node) skipNextPair() (*Pair) {
if !self.isLazy() {
return nil
}
parser, stack := self.getParserAndObjectStack()
ret := &stack.v
sp := parser.p
ns := len(parser.s)
/* check for EOF */
if parser.p = parser.lspace(sp); parser.p >= ns {
return newErrorPair(parser.syntaxError(types.ERR_EOF))
}
/* check for empty object */
if parser.s[parser.p] == '}' {
parser.p++
self.setObject(ret)
return nil
}
/* decode one pair */
var val Node
var njs types.JsonState
var err types.ParsingError
/* decode the key */
if njs = parser.decodeValue(); njs.Vt != types.V_STRING {
return newErrorPair(parser.syntaxError(types.ERR_INVALID_CHAR))
}
/* extract the key */
idx := parser.p - 1
key := parser.s[njs.Iv:idx]
/* check for escape sequence */
if njs.Ep != -1 {
if key, err = unquote(key); err != 0 {
return newErrorPair(parser.syntaxError(err))
}
}
/* expect a ':' delimiter */
if err = parser.delim(); err != 0 {
return newErrorPair(parser.syntaxError(err))
}
/* skip the value */
if start, err := parser.skipFast(); err != 0 {
return newErrorPair(parser.syntaxError(err))
} else {
t := switchRawType(parser.s[start])
if t == _V_NONE {
return newErrorPair(parser.syntaxError(types.ERR_INVALID_CHAR))
}
val = newRawNode(parser.s[start:parser.p], t, false)
}
/* add the value to result */
ret.Push(NewPair(key, val))
self.l++
parser.p = parser.lspace(parser.p)
/* check for EOF */
if parser.p >= ns {
return newErrorPair(parser.syntaxError(types.ERR_EOF))
}
/* check for the next character */
switch parser.s[parser.p] {
case ',':
parser.p++
return ret.At(ret.Len()-1)
case '}':
parser.p++
self.setObject(ret)
return ret.At(ret.Len()-1)
default:
return newErrorPair(parser.syntaxError(types.ERR_INVALID_CHAR))
}
}
/** Parser Factory **/
// Loads parse all json into interface{}
func Loads(src string) (int, interface{}, error) {
ps := &Parser{s: src}
np, err := ps.Parse()
/* check for errors */
if err != 0 {
return 0, nil, ps.ExportError(err)
} else {
x, err := np.Interface()
if err != nil {
return 0, nil, err
}
return ps.Pos(), x, nil
}
}
// LoadsUseNumber parse all json into interface{}, with numeric nodes casted to json.Number
func LoadsUseNumber(src string) (int, interface{}, error) {
ps := &Parser{s: src}
np, err := ps.Parse()
/* check for errors */
if err != 0 {
return 0, nil, err
} else {
x, err := np.InterfaceUseNumber()
if err != nil {
return 0, nil, err
}
return ps.Pos(), x, nil
}
}
// NewParser returns pointer of new allocated parser
func NewParser(src string) *Parser {
return &Parser{s: src}
}
// NewParser returns new allocated parser
func NewParserObj(src string) Parser {
return Parser{s: src}
}
// decodeNumber controls if parser decodes the number values instead of skip them
// WARN: once you set decodeNumber(true), please set decodeNumber(false) before you drop the parser
// otherwise the memory CANNOT be reused
func (self *Parser) decodeNumber(decode bool) {
if !decode && self.dbuf != nil {
types.FreeDbuf(self.dbuf)
self.dbuf = nil
return
}
if decode && self.dbuf == nil {
self.dbuf = types.NewDbuf()
}
}
// ExportError converts types.ParsingError to std Error
func (self *Parser) ExportError(err types.ParsingError) error {
if err == _ERR_NOT_FOUND {
return ErrNotExist
}
return fmt.Errorf("%q", SyntaxError{
Pos : self.p,
Src : self.s,
Code: err,
}.Description())
}
func backward(src string, i int) int {
for ; i>=0 && isSpace(src[i]); i-- {}
return i
}
func newRawNode(str string, typ types.ValueType, lock bool) Node {
ret := Node{
t: typ | _V_RAW,
p: rt.StrPtr(str),
l: uint(len(str)),
}
if lock {
ret.m = new(sync.RWMutex)
}
return ret
}
var typeJumpTable = [256]types.ValueType{
'"' : types.V_STRING,
'-' : _V_NUMBER,
'0' : _V_NUMBER,
'1' : _V_NUMBER,
'2' : _V_NUMBER,
'3' : _V_NUMBER,
'4' : _V_NUMBER,
'5' : _V_NUMBER,
'6' : _V_NUMBER,
'7' : _V_NUMBER,
'8' : _V_NUMBER,
'9' : _V_NUMBER,
'[' : types.V_ARRAY,
'f' : types.V_FALSE,
'n' : types.V_NULL,
't' : types.V_TRUE,
'{' : types.V_OBJECT,
}
func switchRawType(c byte) types.ValueType {
return typeJumpTable[c]
}
func (self *Node) loadt() types.ValueType {
return (types.ValueType)(atomic.LoadInt64(&self.t))
}
func (self *Node) lock() bool {
if m := self.m; m != nil {
m.Lock()
return true
}
return false
}
func (self *Node) unlock() {
if m := self.m; m != nil {
m.Unlock()
}
}
func (self *Node) rlock() bool {
if m := self.m; m != nil {
m.RLock()
return true
}
return false
}
func (self *Node) runlock() {
if m := self.m; m != nil {
m.RUnlock()
}
}
+157
View File
@@ -0,0 +1,157 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
`github.com/bytedance/sonic/internal/rt`
`github.com/bytedance/sonic/internal/native/types`
)
// SearchOptions controls Searcher's behavior
type SearchOptions struct {
// ValidateJSON indicates the searcher to validate the entire JSON
ValidateJSON bool
// CopyReturn indicates the searcher to copy the result JSON instead of refer from the input
// This can help to reduce memory usage if you cache the results
CopyReturn bool
// ConcurrentRead indicates the searcher to return a concurrently-READ-safe node,
// including: GetByPath/Get/Index/GetOrIndex/Int64/Bool/Float64/String/Number/Interface/Array/Map/Raw/MarshalJSON
ConcurrentRead bool
}
type Searcher struct {
parser Parser
SearchOptions
}
func NewSearcher(str string) *Searcher {
return &Searcher{
parser: Parser{
s: str,
noLazy: false,
},
SearchOptions: SearchOptions{
ValidateJSON: true,
},
}
}
// GetByPathCopy search in depth from top json and returns a **Copied** json node at the path location
func (self *Searcher) GetByPathCopy(path ...interface{}) (Node, error) {
self.CopyReturn = true
return self.getByPath(path...)
}
// GetByPathNoCopy search in depth from top json and returns a **Referenced** json node at the path location
//
// WARN: this search directly refer partial json from top json, which has faster speed,
// may consumes more memory.
func (self *Searcher) GetByPath(path ...interface{}) (Node, error) {
return self.getByPath(path...)
}
func (self *Searcher) getByPath(path ...interface{}) (Node, error) {
var err types.ParsingError
var start int
self.parser.p = 0
start, err = self.parser.getByPath(self.ValidateJSON, path...)
if err != 0 {
// for compatibility with old version
if err == types.ERR_NOT_FOUND {
return Node{}, ErrNotExist
}
if err == types.ERR_UNSUPPORT_TYPE {
panic("path must be either int(>=0) or string")
}
return Node{}, self.parser.syntaxError(err)
}
t := switchRawType(self.parser.s[start])
if t == _V_NONE {
return Node{}, self.parser.ExportError(err)
}
// copy string to reducing memory usage
var raw string
if self.CopyReturn {
raw = rt.Mem2Str([]byte(self.parser.s[start:self.parser.p]))
} else {
raw = self.parser.s[start:self.parser.p]
}
return newRawNode(raw, t, self.ConcurrentRead), nil
}
// GetByPath searches a path and returns relaction and types of target
func _GetByPath(src string, path ...interface{}) (start int, end int, typ int, err error) {
p := NewParserObj(src)
s, e := p.getByPath(false, path...)
if e != 0 {
// for compatibility with old version
if e == types.ERR_NOT_FOUND {
return -1, -1, 0, ErrNotExist
}
if e == types.ERR_UNSUPPORT_TYPE {
panic("path must be either int(>=0) or string")
}
return -1, -1, 0, p.syntaxError(e)
}
t := switchRawType(p.s[s])
if t == _V_NONE {
return -1, -1, 0, ErrNotExist
}
if t == _V_NUMBER {
p.p = 1 + backward(p.s, p.p-1)
}
return s, p.p, int(t), nil
}
// ValidSyntax check if a json has a valid JSON syntax,
// while not validate UTF-8 charset
func _ValidSyntax(json string) bool {
p := NewParserObj(json)
_, e := p.skip()
if e != 0 {
return false
}
if skipBlank(p.s, p.p) != -int(types.ERR_EOF) {
return false
}
return true
}
// SkipFast skip a json value in fast-skip algs,
// while not strictly validate JSON syntax and UTF-8 charset.
func _SkipFast(src string, i int) (int, int, error) {
p := NewParserObj(src)
p.p = i
s, e := p.skipFast()
if e != 0 {
return -1, -1, p.ExportError(e)
}
t := switchRawType(p.s[s])
if t == _V_NONE {
return -1, -1, ErrNotExist
}
if t == _V_NUMBER {
p.p = 1 + backward(p.s, p.p-1)
}
return s, p.p, nil
}
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
"unsafe"
"github.com/bytedance/sonic/internal/rt"
)
//go:nosplit
func mem2ptr(s []byte) unsafe.Pointer {
return (*rt.GoSlice)(unsafe.Pointer(&s)).Ptr
}
//go:linkname unquoteBytes encoding/json.unquoteBytes
func unquoteBytes(s []byte) (t []byte, ok bool)
+332
View File
@@ -0,0 +1,332 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ast
import (
`encoding/json`
`errors`
`github.com/bytedance/sonic/internal/native/types`
)
// Visitor handles the callbacks during preorder traversal of a JSON AST.
//
// According to the JSON RFC8259, a JSON AST can be defined by
// the following rules without separator / whitespace tokens.
//
// JSON-AST = value
// value = false / null / true / object / array / number / string
// object = begin-object [ member *( member ) ] end-object
// member = string value
// array = begin-array [ value *( value ) ] end-array
//
type Visitor interface {
// OnNull handles a JSON null value.
OnNull() error
// OnBool handles a JSON true / false value.
OnBool(v bool) error
// OnString handles a JSON string value.
OnString(v string) error
// OnInt64 handles a JSON number value with int64 type.
OnInt64(v int64, n json.Number) error
// OnFloat64 handles a JSON number value with float64 type.
OnFloat64(v float64, n json.Number) error
// OnObjectBegin handles the beginning of a JSON object value with a
// suggested capacity that can be used to make your custom object container.
//
// After this point the visitor will receive a sequence of callbacks like
// [string, value, string, value, ......, ObjectEnd].
//
// Note:
// 1. This is a recursive definition which means the value can
// also be a JSON object / array described by a sequence of callbacks.
// 2. The suggested capacity will be 0 if current object is empty.
// 3. Currently sonic use a fixed capacity for non-empty object (keep in
// sync with ast.Node) which might not be very suitable. This may be
// improved in future version.
OnObjectBegin(capacity int) error
// OnObjectKey handles a JSON object key string in member.
OnObjectKey(key string) error
// OnObjectEnd handles the ending of a JSON object value.
OnObjectEnd() error
// OnArrayBegin handles the beginning of a JSON array value with a
// suggested capacity that can be used to make your custom array container.
//
// After this point the visitor will receive a sequence of callbacks like
// [value, value, value, ......, ArrayEnd].
//
// Note:
// 1. This is a recursive definition which means the value can
// also be a JSON object / array described by a sequence of callbacks.
// 2. The suggested capacity will be 0 if current array is empty.
// 3. Currently sonic use a fixed capacity for non-empty array (keep in
// sync with ast.Node) which might not be very suitable. This may be
// improved in future version.
OnArrayBegin(capacity int) error
// OnArrayEnd handles the ending of a JSON array value.
OnArrayEnd() error
}
// VisitorOptions contains all Visitor's options. The default value is an
// empty VisitorOptions{}.
type VisitorOptions struct {
// OnlyNumber indicates parser to directly return number value without
// conversion, then the first argument of OnInt64 / OnFloat64 will always
// be zero.
OnlyNumber bool
}
var defaultVisitorOptions = &VisitorOptions{}
// Preorder decodes the whole JSON string and callbacks each AST node to visitor
// during preorder traversal. Any visitor method with an error returned will
// break the traversal and the given error will be directly returned. The opts
// argument can be reused after every call.
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error {
if opts == nil {
opts = defaultVisitorOptions
}
// process VisitorOptions first to guarantee that all options will be
// constant during decoding and make options more readable.
var (
optDecodeNumber = !opts.OnlyNumber
)
tv := &traverser{
parser: Parser{
s: str,
noLazy: true,
skipValue: false,
},
visitor: visitor,
}
if optDecodeNumber {
tv.parser.decodeNumber(true)
}
err := tv.decodeValue()
if optDecodeNumber {
tv.parser.decodeNumber(false)
}
return err
}
type traverser struct {
parser Parser
visitor Visitor
}
// NOTE: keep in sync with (*Parser).Parse method.
func (self *traverser) decodeValue() error {
switch val := self.parser.decodeValue(); val.Vt {
case types.V_EOF:
return types.ERR_EOF
case types.V_NULL:
return self.visitor.OnNull()
case types.V_TRUE:
return self.visitor.OnBool(true)
case types.V_FALSE:
return self.visitor.OnBool(false)
case types.V_STRING:
return self.decodeString(val.Iv, val.Ep)
case types.V_DOUBLE:
return self.visitor.OnFloat64(val.Dv,
json.Number(self.parser.s[val.Ep:self.parser.p]))
case types.V_INTEGER:
return self.visitor.OnInt64(val.Iv,
json.Number(self.parser.s[val.Ep:self.parser.p]))
case types.V_ARRAY:
return self.decodeArray()
case types.V_OBJECT:
return self.decodeObject()
default:
return types.ParsingError(-val.Vt)
}
}
// NOTE: keep in sync with (*Parser).decodeArray method.
func (self *traverser) decodeArray() error {
sp := self.parser.p
ns := len(self.parser.s)
/* allocate array space and parse every element */
if err := self.visitor.OnArrayBegin(_DEFAULT_NODE_CAP); err != nil {
if err == VisitOPSkip {
// NOTICE: for user needs to skip entiry object
self.parser.p -= 1
if _, e := self.parser.skipFast(); e != 0 {
return e
}
return self.visitor.OnArrayEnd()
}
return err
}
/* check for EOF */
self.parser.p = self.parser.lspace(sp)
if self.parser.p >= ns {
return types.ERR_EOF
}
/* check for empty array */
if self.parser.s[self.parser.p] == ']' {
self.parser.p++
return self.visitor.OnArrayEnd()
}
for {
/* decode the value */
if err := self.decodeValue(); err != nil {
return err
}
self.parser.p = self.parser.lspace(self.parser.p)
/* check for EOF */
if self.parser.p >= ns {
return types.ERR_EOF
}
/* check for the next character */
switch self.parser.s[self.parser.p] {
case ',':
self.parser.p++
case ']':
self.parser.p++
return self.visitor.OnArrayEnd()
default:
return types.ERR_INVALID_CHAR
}
}
}
// NOTE: keep in sync with (*Parser).decodeObject method.
func (self *traverser) decodeObject() error {
sp := self.parser.p
ns := len(self.parser.s)
/* allocate object space and decode each pair */
if err := self.visitor.OnObjectBegin(_DEFAULT_NODE_CAP); err != nil {
if err == VisitOPSkip {
// NOTICE: for user needs to skip entiry object
self.parser.p -= 1
if _, e := self.parser.skipFast(); e != 0 {
return e
}
return self.visitor.OnObjectEnd()
}
return err
}
/* check for EOF */
self.parser.p = self.parser.lspace(sp)
if self.parser.p >= ns {
return types.ERR_EOF
}
/* check for empty object */
if self.parser.s[self.parser.p] == '}' {
self.parser.p++
return self.visitor.OnObjectEnd()
}
for {
var njs types.JsonState
var err types.ParsingError
/* decode the key */
if njs = self.parser.decodeValue(); njs.Vt != types.V_STRING {
return types.ERR_INVALID_CHAR
}
/* extract the key */
idx := self.parser.p - 1
key := self.parser.s[njs.Iv:idx]
/* check for escape sequence */
if njs.Ep != -1 {
if key, err = unquote(key); err != 0 {
return err
}
}
if err := self.visitor.OnObjectKey(key); err != nil {
return err
}
/* expect a ':' delimiter */
if err = self.parser.delim(); err != 0 {
return err
}
/* decode the value */
if err := self.decodeValue(); err != nil {
return err
}
self.parser.p = self.parser.lspace(self.parser.p)
/* check for EOF */
if self.parser.p >= ns {
return types.ERR_EOF
}
/* check for the next character */
switch self.parser.s[self.parser.p] {
case ',':
self.parser.p++
case '}':
self.parser.p++
return self.visitor.OnObjectEnd()
default:
return types.ERR_INVALID_CHAR
}
}
}
// NOTE: keep in sync with (*Parser).decodeString method.
func (self *traverser) decodeString(iv int64, ep int) error {
p := self.parser.p - 1
s := self.parser.s[iv:p]
/* fast path: no escape sequence */
if ep == -1 {
return self.visitor.OnString(s)
}
/* unquote the string */
out, err := unquote(s)
if err != 0 {
return err
}
return self.visitor.OnString(out)
}
// If visitor return this error on `OnObjectBegin()` or `OnArrayBegin()`,
// the transverer will skip entiry object or array
var VisitOPSkip = errors.New("")
+262
View File
@@ -0,0 +1,262 @@
// +build !amd64,!arm64 go1.25 !go1.17 arm64,!go1.20
/*
* Copyright 2023 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
import (
`io`
`bytes`
`encoding/json`
`reflect`
`github.com/bytedance/sonic/option`
`github.com/bytedance/sonic/internal/compat`
)
func init() {
compat.Warn("sonic/encoder")
}
// EnableFallback indicates if encoder use fallback
const EnableFallback = true
// Options is a set of encoding options.
type Options uint64
const (
bitSortMapKeys = iota
bitEscapeHTML
bitCompactMarshaler
bitNoQuoteTextMarshaler
bitNoNullSliceOrMap
bitValidateString
bitNoValidateJSONMarshaler
bitNoEncoderNewline
// used for recursive compile
bitPointerValue = 63
)
const (
// SortMapKeys indicates that the keys of a map needs to be sorted
// before serializing into JSON.
// WARNING: This hurts performance A LOT, USE WITH CARE.
SortMapKeys Options = 1 << bitSortMapKeys
// EscapeHTML indicates encoder to escape all HTML characters
// after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape).
// WARNING: This hurts performance A LOT, USE WITH CARE.
EscapeHTML Options = 1 << bitEscapeHTML
// CompactMarshaler indicates that the output JSON from json.Marshaler
// is always compact and needs no validation
CompactMarshaler Options = 1 << bitCompactMarshaler
// NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler
// is always escaped string and needs no quoting
NoQuoteTextMarshaler Options = 1 << bitNoQuoteTextMarshaler
// NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}',
// instead of 'null'
NoNullSliceOrMap Options = 1 << bitNoNullSliceOrMap
// ValidateString indicates that encoder should validate the input string
// before encoding it into JSON.
ValidateString Options = 1 << bitValidateString
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
// after encoding the JSONMarshaler to JSON.
NoValidateJSONMarshaler Options = 1 << bitNoValidateJSONMarshaler
// NoEncoderNewline indicates that the encoder should not add a newline after every message
NoEncoderNewline Options = 1 << bitNoEncoderNewline
// CompatibleWithStd is used to be compatible with std encoder.
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
)
// Encoder represents a specific set of encoder configurations.
type Encoder struct {
Opts Options
prefix string
indent string
}
// Encode returns the JSON encoding of v.
func (self *Encoder) Encode(v interface{}) ([]byte, error) {
if self.indent != "" || self.prefix != "" {
return EncodeIndented(v, self.prefix, self.indent, self.Opts)
}
return Encode(v, self.Opts)
}
// SortKeys enables the SortMapKeys option.
func (self *Encoder) SortKeys() *Encoder {
self.Opts |= SortMapKeys
return self
}
// SetEscapeHTML specifies if option EscapeHTML opens
func (self *Encoder) SetEscapeHTML(f bool) {
if f {
self.Opts |= EscapeHTML
} else {
self.Opts &= ^EscapeHTML
}
}
// SetValidateString specifies if option ValidateString opens
func (self *Encoder) SetValidateString(f bool) {
if f {
self.Opts |= ValidateString
} else {
self.Opts &= ^ValidateString
}
}
// SetNoValidateJSONMarshaler specifies if option NoValidateJSONMarshaler opens
func (self *Encoder) SetNoValidateJSONMarshaler(f bool) {
if f {
self.Opts |= NoValidateJSONMarshaler
} else {
self.Opts &= ^NoValidateJSONMarshaler
}
}
// SetNoEncoderNewline specifies if option NoEncoderNewline opens
func (self *Encoder) SetNoEncoderNewline(f bool) {
if f {
self.Opts |= NoEncoderNewline
} else {
self.Opts &= ^NoEncoderNewline
}
}
// SetCompactMarshaler specifies if option CompactMarshaler opens
func (self *Encoder) SetCompactMarshaler(f bool) {
if f {
self.Opts |= CompactMarshaler
} else {
self.Opts &= ^CompactMarshaler
}
}
// SetNoQuoteTextMarshaler specifies if option NoQuoteTextMarshaler opens
func (self *Encoder) SetNoQuoteTextMarshaler(f bool) {
if f {
self.Opts |= NoQuoteTextMarshaler
} else {
self.Opts &= ^NoQuoteTextMarshaler
}
}
// SetIndent instructs the encoder to format each subsequent encoded
// value as if indented by the package-level function EncodeIndent().
// Calling SetIndent("", "") disables indentation.
func (enc *Encoder) SetIndent(prefix, indent string) {
enc.prefix = prefix
enc.indent = indent
}
// Quote returns the JSON-quoted version of s.
func Quote(s string) string {
/* check for empty string */
if s == "" {
return `""`
}
out, _ := json.Marshal(s)
return string(out)
}
// Encode returns the JSON encoding of val, encoded with opts.
func Encode(val interface{}, opts Options) ([]byte, error) {
return json.Marshal(val)
}
// EncodeInto is like Encode but uses a user-supplied buffer instead of allocating
// a new one.
func EncodeInto(buf *[]byte, val interface{}, opts Options) error {
if buf == nil {
panic("user-supplied buffer buf is nil")
}
w := bytes.NewBuffer(*buf)
enc := json.NewEncoder(w)
enc.SetEscapeHTML((opts & EscapeHTML) != 0)
err := enc.Encode(val)
*buf = w.Bytes()
l := len(*buf)
if l > 0 && (opts & NoEncoderNewline != 0) && (*buf)[l-1] == '\n' {
*buf = (*buf)[:l-1]
}
return err
}
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
// so that the JSON will be safe to embed inside HTML <script> tags.
// For historical reasons, web browsers don't honor standard HTML
// escaping within <script> tags, so an alternative JSON encoding must
// be used.
func HTMLEscape(dst []byte, src []byte) []byte {
d := bytes.NewBuffer(dst)
json.HTMLEscape(d, src)
return d.Bytes()
}
// EncodeIndented is like Encode but applies Indent to format the output.
// Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting.
func EncodeIndented(val interface{}, prefix string, indent string, opts Options) ([]byte, error) {
w := bytes.NewBuffer([]byte{})
enc := json.NewEncoder(w)
enc.SetEscapeHTML((opts & EscapeHTML) != 0)
enc.SetIndent(prefix, indent)
err := enc.Encode(val)
out := w.Bytes()
return out, err
}
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
// order to reduce the first-hit latency.
//
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
// a compile option to set the depth of recursive compile for the nested struct type.
func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
return nil
}
// Valid validates json and returns first non-blank character position,
// if it is only one valid json value.
// Otherwise returns invalid character position using start.
//
// Note: it does not check for the invalid UTF-8 characters.
func Valid(data []byte) (ok bool, start int) {
return json.Valid(data), 0
}
// StreamEncoder uses io.Writer as
type StreamEncoder = json.Encoder
// NewStreamEncoder adapts to encoding/json.NewDecoder API.
//
// NewStreamEncoder returns a new encoder that write to w.
func NewStreamEncoder(w io.Writer) *StreamEncoder {
return json.NewEncoder(w)
}
+120
View File
@@ -0,0 +1,120 @@
// +build amd64,go1.17,!go1.25 arm64,go1.20,!go1.25
/*
* Copyright 2023 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
import (
`github.com/bytedance/sonic/internal/encoder`
)
// EnableFallback indicates if encoder use fallback
const EnableFallback = false
// Encoder represents a specific set of encoder configurations.
type Encoder = encoder.Encoder
// StreamEncoder uses io.Writer as input.
type StreamEncoder = encoder.StreamEncoder
// Options is a set of encoding options.
type Options = encoder.Options
const (
// SortMapKeys indicates that the keys of a map needs to be sorted
// before serializing into JSON.
// WARNING: This hurts performance A LOT, USE WITH CARE.
SortMapKeys Options = encoder.SortMapKeys
// EscapeHTML indicates encoder to escape all HTML characters
// after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape).
// WARNING: This hurts performance A LOT, USE WITH CARE.
EscapeHTML Options = encoder.EscapeHTML
// CompactMarshaler indicates that the output JSON from json.Marshaler
// is always compact and needs no validation
CompactMarshaler Options = encoder.CompactMarshaler
// NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler
// is always escaped string and needs no quoting
NoQuoteTextMarshaler Options = encoder.NoQuoteTextMarshaler
// NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}',
// instead of 'null'
NoNullSliceOrMap Options = encoder.NoNullSliceOrMap
// ValidateString indicates that encoder should validate the input string
// before encoding it into JSON.
ValidateString Options = encoder.ValidateString
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
// after encoding the JSONMarshaler to JSON.
NoValidateJSONMarshaler Options = encoder.NoValidateJSONMarshaler
// NoEncoderNewline indicates that the encoder should not add a newline after every message
NoEncoderNewline Options = encoder.NoEncoderNewline
// CompatibleWithStd is used to be compatible with std encoder.
CompatibleWithStd Options = encoder.CompatibleWithStd
// Encode Infinity or Nan float into `null`, instead of returning an error.
EncodeNullForInfOrNan Options = encoder.EncodeNullForInfOrNan
)
var (
// Encode returns the JSON encoding of val, encoded with opts.
Encode = encoder.Encode
// EncodeInto is like Encode but uses a user-supplied buffer instead of allocating a new one.
EncodeIndented = encoder.EncodeIndented
// EncodeIndented is like Encode but applies Indent to format the output.
// Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting.
EncodeInto = encoder.EncodeInto
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
// so that the JSON will be safe to embed inside HTML <script> tags.
// For historical reasons, web browsers don't honor standard HTML
// escaping within <script> tags, so an alternative JSON encoding must
// be used.
HTMLEscape = encoder.HTMLEscape
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
// order to reduce the first-hit latency.
//
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
// a compile option to set the depth of recursive compile for the nested struct type.
Pretouch = encoder.Pretouch
// Quote returns the JSON-quoted version of s.
Quote = encoder.Quote
// Valid validates json and returns first non-blank character position,
// if it is only one valid json value.
// Otherwise returns invalid character position using start.
//
// Note: it does not check for the invalid UTF-8 characters.
Valid = encoder.Valid
// NewStreamEncoder adapts to encoding/json.NewDecoder API.
//
// NewStreamEncoder returns a new encoder that write to w.
NewStreamEncoder = encoder.NewStreamEncoder
)
View File
+115
View File
@@ -0,0 +1,115 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package caching
import (
`strings`
`unsafe`
`github.com/bytedance/sonic/internal/rt`
)
type FieldMap struct {
N uint64
b unsafe.Pointer
m map[string]int
}
type FieldEntry struct {
ID int
Name string
Hash uint64
}
const (
FieldMap_N = int64(unsafe.Offsetof(FieldMap{}.N))
FieldMap_b = int64(unsafe.Offsetof(FieldMap{}.b))
FieldEntrySize = int64(unsafe.Sizeof(FieldEntry{}))
)
func newBucket(n int) unsafe.Pointer {
v := make([]FieldEntry, n)
return (*rt.GoSlice)(unsafe.Pointer(&v)).Ptr
}
func CreateFieldMap(n int) *FieldMap {
return &FieldMap {
N: uint64(n * 2),
b: newBucket(n * 2), // LoadFactor = 0.5
m: make(map[string]int, n * 2),
}
}
func (self *FieldMap) At(p uint64) *FieldEntry {
off := uintptr(p) * uintptr(FieldEntrySize)
return (*FieldEntry)(unsafe.Pointer(uintptr(self.b) + off))
}
// Get searches FieldMap by name. JIT generated assembly does NOT call this
// function, rather it implements its own version directly in assembly. So
// we must ensure this function stays in sync with the JIT generated one.
func (self *FieldMap) Get(name string) int {
h := StrHash(name)
p := h % self.N
s := self.At(p)
/* find the element;
* the hash map is never full, so the loop will always terminate */
for s.Hash != 0 {
if s.Hash == h && s.Name == name {
return s.ID
} else {
p = (p + 1) % self.N
s = self.At(p)
}
}
/* not found */
return -1
}
func (self *FieldMap) Set(name string, i int) {
h := StrHash(name)
p := h % self.N
s := self.At(p)
/* searching for an empty slot;
* the hash map is never full, so the loop will always terminate */
for s.Hash != 0 {
p = (p + 1) % self.N
s = self.At(p)
}
/* set the value */
s.ID = i
s.Hash = h
s.Name = name
/* add the case-insensitive version, prefer the one with smaller field ID */
key := strings.ToLower(name)
if v, ok := self.m[key]; !ok || i < v {
self.m[key] = i
}
}
func (self *FieldMap) GetCaseInsensitive(name string) int {
if i, ok := self.m[strings.ToLower(name)]; ok {
return i
} else {
return -1
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package caching
import (
`unsafe`
`github.com/bytedance/sonic/internal/rt`
)
var (
V_strhash = rt.UnpackEface(rt.Strhash)
S_strhash = *(*uintptr)(V_strhash.Value)
)
func StrHash(s string) uint64 {
if v := rt.Strhash(unsafe.Pointer(&s), 0); v == 0 {
return 1
} else {
return uint64(v)
}
}
+173
View File
@@ -0,0 +1,173 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package caching
import (
`sync`
`sync/atomic`
`unsafe`
`github.com/bytedance/sonic/internal/rt`
)
/** Program Map **/
const (
_LoadFactor = 0.5
_InitCapacity = 4096 // must be a power of 2
)
type _ProgramMap struct {
n uint64
m uint32
b []_ProgramEntry
}
type _ProgramEntry struct {
vt *rt.GoType
fn interface{}
}
func newProgramMap() *_ProgramMap {
return &_ProgramMap {
n: 0,
m: _InitCapacity - 1,
b: make([]_ProgramEntry, _InitCapacity),
}
}
func (self *_ProgramMap) copy() *_ProgramMap {
fork := &_ProgramMap{
n: self.n,
m: self.m,
b: make([]_ProgramEntry, len(self.b)),
}
for i, f := range self.b {
fork.b[i] = f
}
return fork
}
func (self *_ProgramMap) get(vt *rt.GoType) interface{} {
i := self.m + 1
p := vt.Hash & self.m
/* linear probing */
for ; i > 0; i-- {
if b := self.b[p]; b.vt == vt {
return b.fn
} else if b.vt == nil {
break
} else {
p = (p + 1) & self.m
}
}
/* not found */
return nil
}
func (self *_ProgramMap) add(vt *rt.GoType, fn interface{}) *_ProgramMap {
p := self.copy()
f := float64(atomic.LoadUint64(&p.n) + 1) / float64(p.m + 1)
/* check for load factor */
if f > _LoadFactor {
p = p.rehash()
}
/* insert the value */
p.insert(vt, fn)
return p
}
func (self *_ProgramMap) rehash() *_ProgramMap {
c := (self.m + 1) << 1
r := &_ProgramMap{m: c - 1, b: make([]_ProgramEntry, int(c))}
/* rehash every entry */
for i := uint32(0); i <= self.m; i++ {
if b := self.b[i]; b.vt != nil {
r.insert(b.vt, b.fn)
}
}
/* rebuild successful */
return r
}
func (self *_ProgramMap) insert(vt *rt.GoType, fn interface{}) {
h := vt.Hash
p := h & self.m
/* linear probing */
for i := uint32(0); i <= self.m; i++ {
if b := &self.b[p]; b.vt != nil {
p += 1
p &= self.m
} else {
b.vt = vt
b.fn = fn
atomic.AddUint64(&self.n, 1)
return
}
}
/* should never happens */
panic("no available slots")
}
/** RCU Program Cache **/
type ProgramCache struct {
m sync.Mutex
p unsafe.Pointer
}
func CreateProgramCache() *ProgramCache {
return &ProgramCache {
m: sync.Mutex{},
p: unsafe.Pointer(newProgramMap()),
}
}
func (self *ProgramCache) Get(vt *rt.GoType) interface{} {
return (*_ProgramMap)(atomic.LoadPointer(&self.p)).get(vt)
}
func (self *ProgramCache) Compute(vt *rt.GoType, compute func(*rt.GoType, ... interface{}) (interface{}, error), ex ...interface{}) (interface{}, error) {
var err error
var val interface{}
/* use defer to prevent inlining of this function */
self.m.Lock()
defer self.m.Unlock()
/* double check with write lock held */
if val = self.Get(vt); val != nil {
return val, nil
}
/* compute the value */
if val, err = compute(vt, ex...); err != nil {
return nil, err
}
/* update the RCU cache */
atomic.StorePointer(&self.p, unsafe.Pointer((*_ProgramMap)(atomic.LoadPointer(&self.p)).add(vt, val)))
return val, nil
}
+12
View File
@@ -0,0 +1,12 @@
// +build !amd64,!arm64 go1.25 !go1.17 arm64,!go1.20
package compat
import (
"fmt"
"os"
)
func Warn(prefix string) {
fmt.Fprintf(os.Stderr, "WARNING: %s only supports (go1.17~1.24 && amd64 CPU) or (go1.20~1.24 && arm64 CPU), but your environment is not suitable and will fallback to encoding/json\n", prefix)
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cpu
import (
`fmt`
`os`
`github.com/klauspost/cpuid/v2`
)
var (
HasAVX2 = cpuid.CPU.Has(cpuid.AVX2)
HasSSE = cpuid.CPU.Has(cpuid.SSE)
)
func init() {
switch v := os.Getenv("SONIC_MODE"); v {
case "" : break
case "auto" : break
case "noavx" : HasAVX2 = false
// will also disable avx, act as `noavx`, we remain it to make sure forward compatibility
case "noavx2" : HasAVX2 = false
default : panic(fmt.Sprintf("invalid mode: '%s', should be one of 'auto', 'noavx', 'noavx2'", v))
}
}
+207
View File
@@ -0,0 +1,207 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package alg
import (
"encoding"
"reflect"
"strconv"
"sync"
"unsafe"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/rt"
)
type _MapPair struct {
k string // when the map key is integer, k is pointed to m
v unsafe.Pointer
m [32]byte
}
type MapIterator struct {
It rt.GoMapIterator // must be the first field
kv rt.GoSlice // slice of _MapPair
ki int
}
var (
iteratorPool = sync.Pool{}
iteratorPair = rt.UnpackType(reflect.TypeOf(_MapPair{}))
)
func init() {
if unsafe.Offsetof(MapIterator{}.It) != 0 {
panic("_MapIterator.it is not the first field")
}
}
func newIterator() *MapIterator {
if v := iteratorPool.Get(); v == nil {
return new(MapIterator)
} else {
return resetIterator(v.(*MapIterator))
}
}
func resetIterator(p *MapIterator) *MapIterator {
p.ki = 0
p.It = rt.GoMapIterator{}
p.kv.Len = 0
return p
}
func (self *MapIterator) at(i int) *_MapPair {
return (*_MapPair)(unsafe.Pointer(uintptr(self.kv.Ptr) + uintptr(i) * unsafe.Sizeof(_MapPair{})))
}
func (self *MapIterator) add() (p *_MapPair) {
p = self.at(self.kv.Len)
self.kv.Len++
return
}
func (self *MapIterator) data() (p []_MapPair) {
*(*rt.GoSlice)(unsafe.Pointer(&p)) = self.kv
return
}
func (self *MapIterator) append(t *rt.GoType, k unsafe.Pointer, v unsafe.Pointer) (err error) {
p := self.add()
p.v = v
/* check for strings */
if tk := t.Kind(); tk != reflect.String {
return self.appendGeneric(p, t, tk, k)
}
/* fast path for strings */
p.k = *(*string)(k)
return nil
}
func (self *MapIterator) appendGeneric(p *_MapPair, t *rt.GoType, v reflect.Kind, k unsafe.Pointer) error {
switch v {
case reflect.Int : p.k = rt.Mem2Str(strconv.AppendInt(p.m[:0], int64(*(*int)(k)), 10)) ; return nil
case reflect.Int8 : p.k = rt.Mem2Str(strconv.AppendInt(p.m[:0], int64(*(*int8)(k)), 10)) ; return nil
case reflect.Int16 : p.k = rt.Mem2Str(strconv.AppendInt(p.m[:0], int64(*(*int16)(k)), 10)) ; return nil
case reflect.Int32 : p.k = rt.Mem2Str(strconv.AppendInt(p.m[:0], int64(*(*int32)(k)), 10)) ; return nil
case reflect.Int64 : p.k = rt.Mem2Str(strconv.AppendInt(p.m[:0], int64(*(*int64)(k)), 10)) ; return nil
case reflect.Uint : p.k = rt.Mem2Str(strconv.AppendUint(p.m[:0], uint64(*(*uint)(k)), 10)) ; return nil
case reflect.Uint8 : p.k = rt.Mem2Str(strconv.AppendUint(p.m[:0], uint64(*(*uint8)(k)), 10)) ; return nil
case reflect.Uint16 : p.k = rt.Mem2Str(strconv.AppendUint(p.m[:0], uint64(*(*uint16)(k)), 10)) ; return nil
case reflect.Uint32 : p.k = rt.Mem2Str(strconv.AppendUint(p.m[:0], uint64(*(*uint32)(k)), 10)) ; return nil
case reflect.Uint64 : p.k = rt.Mem2Str(strconv.AppendUint(p.m[:0], uint64(*(*uint64)(k)), 10)) ; return nil
case reflect.Uintptr : p.k = rt.Mem2Str(strconv.AppendUint(p.m[:0], uint64(*(*uintptr)(k)), 10)) ; return nil
case reflect.Interface : return self.appendInterface(p, t, k)
case reflect.Struct, reflect.Ptr : return self.appendConcrete(p, t, k)
default : panic("unexpected map key type")
}
}
func (self *MapIterator) appendConcrete(p *_MapPair, t *rt.GoType, k unsafe.Pointer) (err error) {
// compiler has already checked that the type implements the encoding.MarshalText interface
if !t.Indirect() {
k = *(*unsafe.Pointer)(k)
}
eface := rt.GoEface{Value: k, Type: t}.Pack()
out, err := eface.(encoding.TextMarshaler).MarshalText()
if err != nil {
return err
}
p.k = rt.Mem2Str(out)
return
}
func (self *MapIterator) appendInterface(p *_MapPair, t *rt.GoType, k unsafe.Pointer) (err error) {
if len(rt.IfaceType(t).Methods) == 0 {
panic("unexpected map key type")
} else if p.k, err = asText(k); err == nil {
return nil
} else {
return
}
}
func IteratorStop(p *MapIterator) {
iteratorPool.Put(p)
}
func IteratorNext(p *MapIterator) {
i := p.ki
t := &p.It
/* check for unordered iteration */
if i < 0 {
rt.Mapiternext(t)
return
}
/* check for end of iteration */
if p.ki >= p.kv.Len {
t.K = nil
t.V = nil
return
}
/* update the key-value pair, and increase the pointer */
t.K = unsafe.Pointer(&p.at(p.ki).k)
t.V = p.at(p.ki).v
p.ki++
}
func IteratorStart(t *rt.GoMapType, m unsafe.Pointer, fv uint64) (*MapIterator, error) {
it := newIterator()
rt.Mapiterinit(t, m, &it.It)
count := rt.Maplen(m)
/* check for key-sorting, empty map don't need sorting */
if count == 0 || (fv & (1<<BitSortMapKeys)) == 0 {
it.ki = -1
return it, nil
}
/* pre-allocate space if needed */
if count > it.kv.Cap {
it.kv = rt.GrowSlice(iteratorPair, it.kv, count)
}
/* dump all the key-value pairs */
for ; it.It.K != nil; rt.Mapiternext(&it.It) {
if err := it.append(t.Key, it.It.K, it.It.V); err != nil {
IteratorStop(it)
return nil, err
}
}
/* sort the keys, map with only 1 item don't need sorting */
if it.ki = 1; count > 1 {
radixQsort(it.data(), 0, maxDepth(it.kv.Len))
}
/* load the first pair into iterator */
it.It.V = it.at(0).v
it.It.K = unsafe.Pointer(&it.at(0).k)
return it, nil
}
func asText(v unsafe.Pointer) (string, error) {
text := rt.AssertI2I(rt.UnpackType(vars.EncodingTextMarshalerType), *(*rt.GoIface)(v))
r, e := (*(*encoding.TextMarshaler)(unsafe.Pointer(&text))).MarshalText()
return rt.Mem2Str(r), e
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package alg
const (
BitSortMapKeys = iota
BitEscapeHTML
BitCompactMarshaler
BitNoQuoteTextMarshaler
BitNoNullSliceOrMap
BitValidateString
BitNoValidateJSONMarshaler
BitNoEncoderNewline
BitEncodeNullForInfOrNan
BitPointerValue = 63
)
+104
View File
@@ -0,0 +1,104 @@
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package alg
import (
"encoding"
"encoding/json"
"reflect"
"unsafe"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/resolver"
"github.com/bytedance/sonic/internal/rt"
)
func Compact(p *[]byte, v []byte) error {
buf := vars.NewBuffer()
err := json.Compact(buf, v)
/* check for errors */
if err != nil {
return err
}
/* add to result */
v = buf.Bytes()
*p = append(*p, v...)
/* return the buffer into pool */
vars.FreeBuffer(buf)
return nil
}
func EncodeNil(rb *[]byte) error {
*rb = append(*rb, 'n', 'u', 'l', 'l')
return nil
}
// func Make_EncodeTypedPointer(computor func(*rt.GoType, ...interface{}) (interface{}, error)) func(*[]byte, *rt.GoType, *unsafe.Pointer, *vars.Stack, uint64) error {
// return func(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *vars.Stack, fv uint64) error {
// if vt == nil {
// return EncodeNil(buf)
// } else if fn, err := vars.FindOrCompile(vt, (fv&(1<<BitPointerValue)) != 0, computor); err != nil {
// return err
// } else if vt.Indirect() {
// err := fn(buf, *vp, sb, fv)
// return err
// } else {
// err := fn(buf, unsafe.Pointer(vp), sb, fv)
// return err
// }
// }
// }
func EncodeJsonMarshaler(buf *[]byte, val json.Marshaler, opt uint64) error {
if ret, err := val.MarshalJSON(); err != nil {
return err
} else {
if opt&(1<<BitCompactMarshaler) != 0 {
return Compact(buf, ret)
}
if opt&(1<<BitNoValidateJSONMarshaler) == 0 {
if ok, s := Valid(ret); !ok {
return vars.Error_marshaler(ret, s)
}
}
*buf = append(*buf, ret...)
return nil
}
}
func EncodeTextMarshaler(buf *[]byte, val encoding.TextMarshaler, opt uint64) error {
if ret, err := val.MarshalText(); err != nil {
return err
} else {
if opt&(1<<BitNoQuoteTextMarshaler) != 0 {
*buf = append(*buf, ret...)
return nil
}
*buf = Quote(*buf, rt.Mem2Str(ret), false)
return nil
}
}
func IsZero(val unsafe.Pointer, fv *resolver.FieldMeta) bool {
rv := reflect.NewAt(fv.Type, val).Elem()
b1 := fv.IsZero == nil && rv.IsZero()
b2 := fv.IsZero != nil && fv.IsZero(rv)
return b1 || b2
}
+206
View File
@@ -0,0 +1,206 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package alg
// Algorithm 3-way Radix Quicksort, d means the radix.
// Reference: https://algs4.cs.princeton.edu/51radix/Quick3string.java.html
func radixQsort(kvs []_MapPair, d, maxDepth int) {
for len(kvs) > 11 {
// To avoid the worst case of quickSort (time: O(n^2)), use introsort here.
// Reference: https://en.wikipedia.org/wiki/Introsort and
// https://github.com/golang/go/issues/467
if maxDepth == 0 {
heapSort(kvs, 0, len(kvs))
return
}
maxDepth--
p := pivot(kvs, d)
lt, i, gt := 0, 0, len(kvs)
for i < gt {
c := byteAt(kvs[i].k, d)
if c < p {
swap(kvs, lt, i)
i++
lt++
} else if c > p {
gt--
swap(kvs, i, gt)
} else {
i++
}
}
// kvs[0:lt] < v = kvs[lt:gt] < kvs[gt:len(kvs)]
// Native implementation:
// radixQsort(kvs[:lt], d, maxDepth)
// if p > -1 {
// radixQsort(kvs[lt:gt], d+1, maxDepth)
// }
// radixQsort(kvs[gt:], d, maxDepth)
// Optimize as follows: make recursive calls only for the smaller parts.
// Reference: https://www.geeksforgeeks.org/quicksort-tail-call-optimization-reducing-worst-case-space-log-n/
if p == -1 {
if lt > len(kvs) - gt {
radixQsort(kvs[gt:], d, maxDepth)
kvs = kvs[:lt]
} else {
radixQsort(kvs[:lt], d, maxDepth)
kvs = kvs[gt:]
}
} else {
ml := maxThree(lt, gt-lt, len(kvs)-gt)
if ml == lt {
radixQsort(kvs[lt:gt], d+1, maxDepth)
radixQsort(kvs[gt:], d, maxDepth)
kvs = kvs[:lt]
} else if ml == gt-lt {
radixQsort(kvs[:lt], d, maxDepth)
radixQsort(kvs[gt:], d, maxDepth)
kvs = kvs[lt:gt]
d += 1
} else {
radixQsort(kvs[:lt], d, maxDepth)
radixQsort(kvs[lt:gt], d+1, maxDepth)
kvs = kvs[gt:]
}
}
}
insertRadixSort(kvs, d)
}
func insertRadixSort(kvs []_MapPair, d int) {
for i := 1; i < len(kvs); i++ {
for j := i; j > 0 && lessFrom(kvs[j].k, kvs[j-1].k, d); j-- {
swap(kvs, j, j-1)
}
}
}
func pivot(kvs []_MapPair, d int) int {
m := len(kvs) >> 1
if len(kvs) > 40 {
// Tukey's ``Ninther,'' median of three mediankvs of three.
t := len(kvs) / 8
return medianThree(
medianThree(byteAt(kvs[0].k, d), byteAt(kvs[t].k, d), byteAt(kvs[2*t].k, d)),
medianThree(byteAt(kvs[m].k, d), byteAt(kvs[m-t].k, d), byteAt(kvs[m+t].k, d)),
medianThree(byteAt(kvs[len(kvs)-1].k, d),
byteAt(kvs[len(kvs)-1-t].k, d),
byteAt(kvs[len(kvs)-1-2*t].k, d)))
}
return medianThree(byteAt(kvs[0].k, d), byteAt(kvs[m].k, d), byteAt(kvs[len(kvs)-1].k, d))
}
func medianThree(i, j, k int) int {
if i > j {
i, j = j, i
} // i < j
if k < i {
return i
}
if k > j {
return j
}
return k
}
func maxThree(i, j, k int) int {
max := i
if max < j {
max = j
}
if max < k {
max = k
}
return max
}
// maxDepth returns a threshold at which quicksort should switch
// to heapsort. It returnkvs 2*ceil(lg(n+1)).
func maxDepth(n int) int {
var depth int
for i := n; i > 0; i >>= 1 {
depth++
}
return depth * 2
}
// siftDown implements the heap property on kvs[lo:hi].
// first is an offset into the array where the root of the heap lies.
func siftDown(kvs []_MapPair, lo, hi, first int) {
root := lo
for {
child := 2*root + 1
if child >= hi {
break
}
if child+1 < hi && kvs[first+child].k < kvs[first+child+1].k {
child++
}
if kvs[first+root].k >= kvs[first+child].k {
return
}
swap(kvs, first+root, first+child)
root = child
}
}
func heapSort(kvs []_MapPair, a, b int) {
first := a
lo := 0
hi := b - a
// Build heap with the greatest element at top.
for i := (hi - 1) / 2; i >= 0; i-- {
siftDown(kvs, i, hi, first)
}
// Pop elements, the largest first, into end of kvs.
for i := hi - 1; i >= 0; i-- {
swap(kvs, first, first+i)
siftDown(kvs, lo, i, first)
}
}
// Note that _MapPair.k is NOT pointed to _MapPair.m when map key is integer after swap
func swap(kvs []_MapPair, a, b int) {
kvs[a].k, kvs[b].k = kvs[b].k, kvs[a].k
kvs[a].v, kvs[b].v = kvs[b].v, kvs[a].v
}
// Compare two strings from the pos d.
func lessFrom(a, b string, d int) bool {
l := len(a)
if l > len(b) {
l = len(b)
}
for i := d; i < l; i++ {
if a[i] == b[i] {
continue
}
return a[i] < b[i]
}
return len(a) < len(b)
}
func byteAt(b string, p int) int {
if p < len(b) {
return int(b[p])
}
return -1
}
+198
View File
@@ -0,0 +1,198 @@
//go:build (amd64 && go1.16 && !go1.25) || (arm64 && go1.20 && !go1.25)
// +build amd64,go1.16,!go1.25 arm64,go1.20,!go1.25
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package alg
import (
"runtime"
"unsafe"
"github.com/bytedance/sonic/internal/native"
"github.com/bytedance/sonic/internal/native/types"
"github.com/bytedance/sonic/internal/rt"
)
// Valid validates json and returns first non-blank character position,
// if it is only one valid json value.
// Otherwise returns invalid character position using start.
//
// Note: it does not check for the invalid UTF-8 characters.
func Valid(data []byte) (ok bool, start int) {
n := len(data)
if n == 0 {
return false, -1
}
s := rt.Mem2Str(data)
p := 0
m := types.NewStateMachine()
ret := native.ValidateOne(&s, &p, m, 0)
types.FreeStateMachine(m)
if ret < 0 {
return false, p-1
}
/* check for trailing spaces */
for ;p < n; p++ {
if (types.SPACE_MASK & (1 << data[p])) == 0 {
return false, p
}
}
return true, ret
}
var typeByte = rt.UnpackEface(byte(0)).Type
//go:nocheckptr
func Quote(buf []byte, val string, double bool) []byte {
if len(val) == 0 {
if double {
return append(buf, `"\"\""`...)
}
return append(buf, `""`...)
}
if double {
buf = append(buf, `"\"`...)
} else {
buf = append(buf, `"`...)
}
sp := rt.IndexChar(val, 0)
nb := len(val)
b := (*rt.GoSlice)(unsafe.Pointer(&buf))
// input buffer
for nb > 0 {
// output buffer
dp := unsafe.Pointer(uintptr(b.Ptr) + uintptr(b.Len))
dn := b.Cap - b.Len
// call native.Quote, dn is byte count it outputs
opts := uint64(0)
if double {
opts = types.F_DOUBLE_UNQUOTE
}
ret := native.Quote(sp, nb, dp, &dn, opts)
// update *buf length
b.Len += dn
// no need more output
if ret >= 0 {
break
}
// double buf size
*b = rt.GrowSlice(typeByte, *b, b.Cap*2)
// ret is the complement of consumed input
ret = ^ret
// update input buffer
nb -= ret
sp = unsafe.Pointer(uintptr(sp) + uintptr(ret))
}
runtime.KeepAlive(buf)
runtime.KeepAlive(sp)
if double {
buf = append(buf, `\""`...)
} else {
buf = append(buf, `"`...)
}
return buf
}
func HtmlEscape(dst []byte, src []byte) []byte {
var sidx int
dst = append(dst, src[:0]...) // avoid check nil dst
sbuf := (*rt.GoSlice)(unsafe.Pointer(&src))
dbuf := (*rt.GoSlice)(unsafe.Pointer(&dst))
/* grow dst if it is shorter */
if cap(dst)-len(dst) < len(src)+types.BufPaddingSize {
cap := len(src)*3/2 + types.BufPaddingSize
*dbuf = rt.GrowSlice(typeByte, *dbuf, cap)
}
for sidx < sbuf.Len {
sp := rt.Add(sbuf.Ptr, uintptr(sidx))
dp := rt.Add(dbuf.Ptr, uintptr(dbuf.Len))
sn := sbuf.Len - sidx
dn := dbuf.Cap - dbuf.Len
nb := native.HTMLEscape(sp, sn, dp, &dn)
/* check for errors */
if dbuf.Len += dn; nb >= 0 {
break
}
/* not enough space, grow the slice and try again */
sidx += ^nb
*dbuf = rt.GrowSlice(typeByte, *dbuf, dbuf.Cap*2)
}
return dst
}
func F64toa(buf []byte, v float64) ([]byte) {
if v == 0 {
return append(buf, '0')
}
buf = rt.GuardSlice2(buf, 64)
ret := native.F64toa((*byte)(rt.IndexByte(buf, len(buf))), v)
if ret > 0 {
return buf[:len(buf)+ret]
} else {
return buf
}
}
func F32toa(buf []byte, v float32) ([]byte) {
if v == 0 {
return append(buf, '0')
}
buf = rt.GuardSlice2(buf, 64)
ret := native.F32toa((*byte)(rt.IndexByte(buf, len(buf))), v)
if ret > 0 {
return buf[:len(buf)+ret]
} else {
return buf
}
}
func I64toa(buf []byte, v int64) ([]byte) {
buf = rt.GuardSlice2(buf, 32)
ret := native.I64toa((*byte)(rt.IndexByte(buf, len(buf))), v)
if ret > 0 {
return buf[:len(buf)+ret]
} else {
return buf
}
}
func U64toa(buf []byte, v uint64) ([]byte) {
buf = rt.GuardSlice2(buf, 32)
ret := native.U64toa((*byte)(rt.IndexByte(buf, len(buf))), v)
if ret > 0 {
return buf[:len(buf)+ret]
} else {
return buf
}
}
+148
View File
@@ -0,0 +1,148 @@
// +build !amd64,!arm64 go1.25 !go1.16 arm64,!go1.20
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package alg
import (
_ "unsafe"
"unicode/utf8"
"strconv"
"bytes"
"encoding/json"
"github.com/bytedance/sonic/internal/rt"
)
// Valid validates json and returns first non-blank character position,
// if it is only one valid json value.
// Otherwise returns invalid character position using start.
//
// Note: it does not check for the invalid UTF-8 characters.
func Valid(data []byte) (ok bool, start int) {
ok = json.Valid(data)
return ok, 0
}
var typeByte = rt.UnpackEface(byte(0)).Type
func Quote(e []byte, s string, double bool) []byte {
if len(s) == 0 {
if double {
return append(e, `"\"\""`...)
}
return append(e, `""`...)
}
b := e
ss := len(e)
e = append(e, '"')
start := 0
for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf {
if rt.SafeSet[b] {
i++
continue
}
if start < i {
e = append(e, s[start:i]...)
}
e = append(e, '\\')
switch b {
case '\\', '"':
e = append(e, b)
case '\n':
e = append(e, 'n')
case '\r':
e = append(e, 'r')
case '\t':
e = append(e, 't')
default:
// This encodes bytes < 0x20 except for \t, \n and \r.
// If escapeHTML is set, it also escapes <, >, and &
// because they can lead to security holes when
// user-controlled strings are rendered into JSON
// and served to some browsers.
e = append(e, `u00`...)
e = append(e, rt.Hex[b>>4])
e = append(e, rt.Hex[b&0xF])
}
i++
start = i
continue
}
c, size := utf8.DecodeRuneInString(s[i:])
// if correct && c == utf8.RuneError && size == 1 {
// if start < i {
// e = append(e, s[start:i]...)
// }
// e = append(e, `\ufffd`...)
// i += size
// start = i
// continue
// }
if c == '\u2028' || c == '\u2029' {
if start < i {
e = append(e, s[start:i]...)
}
e = append(e, `\u202`...)
e = append(e, rt.Hex[c&0xF])
i += size
start = i
continue
}
i += size
}
if start < len(s) {
e = append(e, s[start:]...)
}
e = append(e, '"')
if double {
return strconv.AppendQuote(b, string(e[ss:]))
} else {
return e
}
}
func HtmlEscape(dst []byte, src []byte) []byte {
buf := bytes.NewBuffer(dst)
json.HTMLEscape(buf, src)
return buf.Bytes()
}
func F64toa(buf []byte, v float64) ([]byte) {
bs := bytes.NewBuffer(buf)
_ = json.NewEncoder(bs).Encode(v)
return bs.Bytes()
}
func F32toa(buf []byte, v float32) ([]byte) {
bs := bytes.NewBuffer(buf)
_ = json.NewEncoder(bs).Encode(v)
return bs.Bytes()
}
func I64toa(buf []byte, v int64) ([]byte) {
return strconv.AppendInt(buf, int64(v), 10)
}
func U64toa(buf []byte, v uint64) ([]byte) {
return strconv.AppendUint(buf, v, 10)
}
+687
View File
@@ -0,0 +1,687 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
import (
"reflect"
"unsafe"
"github.com/bytedance/sonic/internal/encoder/ir"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/encoder/vm"
"github.com/bytedance/sonic/internal/resolver"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/option"
)
func ForceUseVM() {
vm.SetCompiler(makeEncoderVM)
pretouchType = pretouchTypeVM
encodeTypedPointer = vm.EncodeTypedPointer
vars.UseVM = true
}
var encodeTypedPointer func(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *vars.Stack, fv uint64) error
func makeEncoderVM(vt *rt.GoType, ex ...interface{}) (interface{}, error) {
pp, err := NewCompiler().Compile(vt.Pack(), ex[0].(bool))
if err != nil {
return nil, err
}
return &pp, nil
}
var pretouchType func(_vt reflect.Type, opts option.CompileOptions, v uint8) (map[reflect.Type]uint8, error)
func pretouchTypeVM(_vt reflect.Type, opts option.CompileOptions, v uint8) (map[reflect.Type]uint8, error) {
/* compile function */
compiler := NewCompiler().apply(opts)
/* find or compile */
vt := rt.UnpackType(_vt)
if val := vars.GetProgram(vt); val != nil {
return nil, nil
} else if _, err := vars.ComputeProgram(vt, makeEncoderVM, v == 1); err == nil {
return compiler.rec, nil
} else {
return nil, err
}
}
func pretouchRec(vtm map[reflect.Type]uint8, opts option.CompileOptions) error {
if opts.RecursiveDepth < 0 || len(vtm) == 0 {
return nil
}
next := make(map[reflect.Type]uint8)
for vt, v := range vtm {
sub, err := pretouchType(vt, opts, v)
if err != nil {
return err
}
for svt, v := range sub {
next[svt] = v
}
}
opts.RecursiveDepth -= 1
return pretouchRec(next, opts)
}
type Compiler struct {
opts option.CompileOptions
pv bool
tab map[reflect.Type]bool
rec map[reflect.Type]uint8
}
func NewCompiler() *Compiler {
return &Compiler{
opts: option.DefaultCompileOptions(),
tab: map[reflect.Type]bool{},
rec: map[reflect.Type]uint8{},
}
}
func (self *Compiler) apply(opts option.CompileOptions) *Compiler {
self.opts = opts
if self.opts.RecursiveDepth > 0 {
self.rec = map[reflect.Type]uint8{}
}
return self
}
func (self *Compiler) rescue(ep *error) {
if val := recover(); val != nil {
if err, ok := val.(error); ok {
*ep = err
} else {
panic(val)
}
}
}
func (self *Compiler) Compile(vt reflect.Type, pv bool) (ret ir.Program, err error) {
defer self.rescue(&err)
self.compileOne(&ret, 0, vt, pv)
return
}
func (self *Compiler) compileOne(p *ir.Program, sp int, vt reflect.Type, pv bool) {
if self.tab[vt] {
p.Vp(ir.OP_recurse, vt, pv)
} else {
self.compileRec(p, sp, vt, pv)
}
}
func (self *Compiler) tryCompileMarshaler(p *ir.Program, vt reflect.Type, pv bool) bool {
pt := reflect.PtrTo(vt)
/* check for addressable `json.Marshaler` with pointer receiver */
if pv && pt.Implements(vars.JsonMarshalerType) {
addMarshalerOp(p, ir.OP_marshal_p, pt, vars.JsonMarshalerType)
return true
}
/* check for `json.Marshaler` */
if vt.Implements(vars.JsonMarshalerType) {
self.compileMarshaler(p, ir.OP_marshal, vt, vars.JsonMarshalerType)
return true
}
/* check for addressable `encoding.TextMarshaler` with pointer receiver */
if pv && pt.Implements(vars.EncodingTextMarshalerType) {
addMarshalerOp(p, ir.OP_marshal_text_p, pt, vars.EncodingTextMarshalerType)
return true
}
/* check for `encoding.TextMarshaler` */
if vt.Implements(vars.EncodingTextMarshalerType) {
self.compileMarshaler(p, ir.OP_marshal_text, vt, vars.EncodingTextMarshalerType)
return true
}
return false
}
func (self *Compiler) compileRec(p *ir.Program, sp int, vt reflect.Type, pv bool) {
pr := self.pv
if self.tryCompileMarshaler(p, vt, pv) {
return
}
/* enter the recursion, and compile the type */
self.pv = pv
self.tab[vt] = true
self.compileOps(p, sp, vt)
/* exit the recursion */
self.pv = pr
delete(self.tab, vt)
}
func (self *Compiler) compileOps(p *ir.Program, sp int, vt reflect.Type) {
switch vt.Kind() {
case reflect.Bool:
p.Add(ir.OP_bool)
case reflect.Int:
p.Add(ir.OP_int())
case reflect.Int8:
p.Add(ir.OP_i8)
case reflect.Int16:
p.Add(ir.OP_i16)
case reflect.Int32:
p.Add(ir.OP_i32)
case reflect.Int64:
p.Add(ir.OP_i64)
case reflect.Uint:
p.Add(ir.OP_uint())
case reflect.Uint8:
p.Add(ir.OP_u8)
case reflect.Uint16:
p.Add(ir.OP_u16)
case reflect.Uint32:
p.Add(ir.OP_u32)
case reflect.Uint64:
p.Add(ir.OP_u64)
case reflect.Uintptr:
p.Add(ir.OP_uintptr())
case reflect.Float32:
p.Add(ir.OP_f32)
case reflect.Float64:
p.Add(ir.OP_f64)
case reflect.String:
self.compileString(p, vt)
case reflect.Array:
self.compileArray(p, sp, vt.Elem(), vt.Len())
case reflect.Interface:
self.compileInterface(p, vt)
case reflect.Map:
self.compileMap(p, sp, vt)
case reflect.Ptr:
self.compilePtr(p, sp, vt.Elem())
case reflect.Slice:
self.compileSlice(p, sp, vt.Elem())
case reflect.Struct:
self.compileStruct(p, sp, vt)
default:
self.compileUnsupportedType(p, vt)
}
}
func (self *Compiler) compileNil(p *ir.Program, sp int, vt reflect.Type, nil_op ir.Op, fn func(*ir.Program, int, reflect.Type)) {
x := p.PC()
p.Add(ir.OP_is_nil)
fn(p, sp, vt)
e := p.PC()
p.Add(ir.OP_goto)
p.Pin(x)
p.Add(nil_op)
p.Pin(e)
}
func (self *Compiler) compilePtr(p *ir.Program, sp int, vt reflect.Type) {
self.compileNil(p, sp, vt, ir.OP_null, self.compilePtrBody)
}
func (self *Compiler) compilePtrBody(p *ir.Program, sp int, vt reflect.Type) {
p.Tag(sp)
p.Add(ir.OP_save)
p.Add(ir.OP_deref)
self.compileOne(p, sp+1, vt, true)
p.Add(ir.OP_drop)
}
func (self *Compiler) compileMap(p *ir.Program, sp int, vt reflect.Type) {
self.compileNil(p, sp, vt, ir.OP_empty_obj, self.compileMapBody)
}
func (self *Compiler) compileMapBody(p *ir.Program, sp int, vt reflect.Type) {
p.Tag(sp + 1)
p.Int(ir.OP_byte, '{')
e := p.PC()
p.Add(ir.OP_is_zero_map)
p.Add(ir.OP_save)
p.Rtt(ir.OP_map_iter, vt)
p.Add(ir.OP_save)
i := p.PC()
p.Add(ir.OP_map_check_key)
u := p.PC()
p.Add(ir.OP_map_write_key)
self.compileMapBodyKey(p, vt.Key())
p.Pin(u)
p.Int(ir.OP_byte, ':')
p.Add(ir.OP_map_value_next)
self.compileOne(p, sp+2, vt.Elem(), false)
j := p.PC()
p.Add(ir.OP_map_check_key)
p.Int(ir.OP_byte, ',')
v := p.PC()
p.Add(ir.OP_map_write_key)
self.compileMapBodyKey(p, vt.Key())
p.Pin(v)
p.Int(ir.OP_byte, ':')
p.Add(ir.OP_map_value_next)
self.compileOne(p, sp+2, vt.Elem(), false)
p.Int(ir.OP_goto, j)
p.Pin(i)
p.Pin(j)
p.Add(ir.OP_map_stop)
p.Add(ir.OP_drop_2)
p.Pin(e)
p.Int(ir.OP_byte, '}')
}
func (self *Compiler) compileMapBodyKey(p *ir.Program, vk reflect.Type) {
if !vk.Implements(vars.EncodingTextMarshalerType) {
self.compileMapBodyTextKey(p, vk)
} else {
self.compileMapBodyUtextKey(p, vk)
}
}
func (self *Compiler) compileMapBodyTextKey(p *ir.Program, vk reflect.Type) {
switch vk.Kind() {
case reflect.Invalid:
panic("map key is nil")
case reflect.Bool:
p.Key(ir.OP_bool)
case reflect.Int:
p.Key(ir.OP_int())
case reflect.Int8:
p.Key(ir.OP_i8)
case reflect.Int16:
p.Key(ir.OP_i16)
case reflect.Int32:
p.Key(ir.OP_i32)
case reflect.Int64:
p.Key(ir.OP_i64)
case reflect.Uint:
p.Key(ir.OP_uint())
case reflect.Uint8:
p.Key(ir.OP_u8)
case reflect.Uint16:
p.Key(ir.OP_u16)
case reflect.Uint32:
p.Key(ir.OP_u32)
case reflect.Uint64:
p.Key(ir.OP_u64)
case reflect.Uintptr:
p.Key(ir.OP_uintptr())
case reflect.Float32:
p.Key(ir.OP_f32)
case reflect.Float64:
p.Key(ir.OP_f64)
case reflect.String:
self.compileString(p, vk)
default:
panic(vars.Error_type(vk))
}
}
func (self *Compiler) compileMapBodyUtextKey(p *ir.Program, vk reflect.Type) {
if vk.Kind() != reflect.Ptr {
addMarshalerOp(p, ir.OP_marshal_text, vk, vars.EncodingTextMarshalerType)
} else {
self.compileMapBodyUtextPtr(p, vk)
}
}
func (self *Compiler) compileMapBodyUtextPtr(p *ir.Program, vk reflect.Type) {
i := p.PC()
p.Add(ir.OP_is_nil)
addMarshalerOp(p, ir.OP_marshal_text, vk, vars.EncodingTextMarshalerType)
j := p.PC()
p.Add(ir.OP_goto)
p.Pin(i)
p.Str(ir.OP_text, "\"\"")
p.Pin(j)
}
func (self *Compiler) compileSlice(p *ir.Program, sp int, vt reflect.Type) {
self.compileNil(p, sp, vt, ir.OP_empty_arr, self.compileSliceBody)
}
func (self *Compiler) compileSliceBody(p *ir.Program, sp int, vt reflect.Type) {
if vars.IsSimpleByte(vt) {
p.Add(ir.OP_bin)
} else {
self.compileSliceArray(p, sp, vt)
}
}
func (self *Compiler) compileSliceArray(p *ir.Program, sp int, vt reflect.Type) {
p.Tag(sp)
p.Int(ir.OP_byte, '[')
e := p.PC()
p.Add(ir.OP_is_nil)
p.Add(ir.OP_save)
p.Add(ir.OP_slice_len)
i := p.PC()
p.Rtt(ir.OP_slice_next, vt)
self.compileOne(p, sp+1, vt, true)
j := p.PC()
p.Rtt(ir.OP_slice_next, vt)
p.Int(ir.OP_byte, ',')
self.compileOne(p, sp+1, vt, true)
p.Int(ir.OP_goto, j)
p.Pin(i)
p.Pin(j)
p.Add(ir.OP_drop)
p.Pin(e)
p.Int(ir.OP_byte, ']')
}
func (self *Compiler) compileArray(p *ir.Program, sp int, vt reflect.Type, nb int) {
p.Tag(sp)
p.Int(ir.OP_byte, '[')
p.Add(ir.OP_save)
/* first item */
if nb != 0 {
self.compileOne(p, sp+1, vt, self.pv)
p.Add(ir.OP_load)
}
/* remaining items */
for i := 1; i < nb; i++ {
p.Int(ir.OP_byte, ',')
p.Int(ir.OP_index, i*int(vt.Size()))
self.compileOne(p, sp+1, vt, self.pv)
p.Add(ir.OP_load)
}
/* end of array */
p.Add(ir.OP_drop)
p.Int(ir.OP_byte, ']')
}
func (self *Compiler) compileString(p *ir.Program, vt reflect.Type) {
if vt != vars.JsonNumberType {
p.Add(ir.OP_str)
} else {
p.Add(ir.OP_number)
}
}
func (self *Compiler) compileStruct(p *ir.Program, sp int, vt reflect.Type) {
if sp >= self.opts.MaxInlineDepth || p.PC() >= vars.MAX_ILBUF || (sp > 0 && vt.NumField() >= vars.MAX_FIELDS) {
p.Vp(ir.OP_recurse, vt, self.pv)
if self.opts.RecursiveDepth > 0 {
if self.pv {
self.rec[vt] = 1
} else {
self.rec[vt] = 0
}
}
} else {
self.compileStructBody(p, sp, vt)
}
}
func (self *Compiler) compileStructBody(p *ir.Program, sp int, vt reflect.Type) {
p.Tag(sp)
p.Int(ir.OP_byte, '{')
p.Add(ir.OP_save)
p.Add(ir.OP_cond_set)
/* compile each field */
fvs := resolver.ResolveStruct(vt)
for i, fv := range fvs {
var s []int
var o resolver.Offset
/* "omitempty" for arrays */
if fv.Type.Kind() == reflect.Array {
if fv.Type.Len() == 0 && (fv.Opts&resolver.F_omitempty) != 0 {
continue
}
}
/* index to the field */
for _, o = range fv.Path {
if p.Int(ir.OP_index, int(o.Size)); o.Kind == resolver.F_deref {
s = append(s, p.PC())
p.Add(ir.OP_is_nil)
p.Add(ir.OP_deref)
}
}
/* check for "omitempty" option */
if fv.Type.Kind() != reflect.Struct && fv.Type.Kind() != reflect.Array && (fv.Opts&resolver.F_omitempty) != 0 {
s = append(s, p.PC())
self.compileStructFieldEmpty(p, fv.Type)
}
/* check for "omitzero" option */
if fv.Opts&resolver.F_omitzero != 0 {
s = append(s, p.PC())
p.VField(ir.OP_is_zero, &fvs[i])
}
/* add the comma if not the first element */
i := p.PC()
p.Add(ir.OP_cond_testc)
p.Int(ir.OP_byte, ',')
p.Pin(i)
/* compile the key and value */
ft := fv.Type
p.Str(ir.OP_text, Quote(fv.Name)+":")
/* check for "stringnize" option */
if (fv.Opts & resolver.F_stringize) == 0 {
self.compileOne(p, sp+1, ft, self.pv)
} else {
self.compileStructFieldStr(p, sp+1, ft)
}
/* patch the skipping jumps and reload the struct pointer */
p.Rel(s)
p.Add(ir.OP_load)
}
/* end of object */
p.Add(ir.OP_drop)
p.Int(ir.OP_byte, '}')
}
func (self *Compiler) compileStructFieldStr(p *ir.Program, sp int, vt reflect.Type) {
// NOTICE: according to encoding/json, Marshaler type has higher priority than string option
// see issue:
if self.tryCompileMarshaler(p, vt, self.pv) {
return
}
pc := -1
ft := vt
sv := false
/* dereference the pointer if needed */
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
/* check if it can be stringized */
switch ft.Kind() {
case reflect.Bool:
sv = true
case reflect.Int:
sv = true
case reflect.Int8:
sv = true
case reflect.Int16:
sv = true
case reflect.Int32:
sv = true
case reflect.Int64:
sv = true
case reflect.Uint:
sv = true
case reflect.Uint8:
sv = true
case reflect.Uint16:
sv = true
case reflect.Uint32:
sv = true
case reflect.Uint64:
sv = true
case reflect.Uintptr:
sv = true
case reflect.Float32:
sv = true
case reflect.Float64:
sv = true
case reflect.String:
sv = true
}
/* if it's not, ignore the "string" and follow the regular path */
if !sv {
self.compileOne(p, sp, vt, self.pv)
return
}
/* dereference the pointer */
if vt.Kind() == reflect.Ptr {
pc = p.PC()
vt = vt.Elem()
p.Add(ir.OP_is_nil)
p.Add(ir.OP_deref)
}
/* special case of a double-quoted string */
if ft != vars.JsonNumberType && ft.Kind() == reflect.String {
p.Add(ir.OP_quote)
} else {
self.compileStructFieldQuoted(p, sp, vt)
}
/* the "null" case of the pointer */
if pc != -1 {
e := p.PC()
p.Add(ir.OP_goto)
p.Pin(pc)
p.Add(ir.OP_null)
p.Pin(e)
}
}
func (self *Compiler) compileStructFieldEmpty(p *ir.Program, vt reflect.Type) {
switch vt.Kind() {
case reflect.Bool:
p.Add(ir.OP_is_zero_1)
case reflect.Int:
p.Add(ir.OP_is_zero_ints())
case reflect.Int8:
p.Add(ir.OP_is_zero_1)
case reflect.Int16:
p.Add(ir.OP_is_zero_2)
case reflect.Int32:
p.Add(ir.OP_is_zero_4)
case reflect.Int64:
p.Add(ir.OP_is_zero_8)
case reflect.Uint:
p.Add(ir.OP_is_zero_ints())
case reflect.Uint8:
p.Add(ir.OP_is_zero_1)
case reflect.Uint16:
p.Add(ir.OP_is_zero_2)
case reflect.Uint32:
p.Add(ir.OP_is_zero_4)
case reflect.Uint64:
p.Add(ir.OP_is_zero_8)
case reflect.Uintptr:
p.Add(ir.OP_is_nil)
case reflect.Float32:
p.Add(ir.OP_is_zero_4)
case reflect.Float64:
p.Add(ir.OP_is_zero_8)
case reflect.String:
p.Add(ir.OP_is_nil_p1)
case reflect.Interface:
p.Add(ir.OP_is_nil)
case reflect.Map:
p.Add(ir.OP_is_zero_map)
case reflect.Ptr:
p.Add(ir.OP_is_nil)
case reflect.Slice:
p.Add(ir.OP_is_nil_p1)
default:
panic(vars.Error_type(vt))
}
}
func (self *Compiler) compileStructFieldQuoted(p *ir.Program, sp int, vt reflect.Type) {
p.Int(ir.OP_byte, '"')
self.compileOne(p, sp, vt, self.pv)
p.Int(ir.OP_byte, '"')
}
func (self *Compiler) compileInterface(p *ir.Program, vt reflect.Type) {
/* iface and efaces are different */
if vt.NumMethod() == 0 {
p.Add(ir.OP_eface)
return
}
x := p.PC()
p.Add(ir.OP_is_nil_p1)
p.Add(ir.OP_iface)
/* the "null" value */
e := p.PC()
p.Add(ir.OP_goto)
p.Pin(x)
p.Add(ir.OP_null)
p.Pin(e)
}
func (self *Compiler) compileUnsupportedType(p *ir.Program, vt reflect.Type) {
p.Rtt(ir.OP_unsupported, vt)
}
func (self *Compiler) compileMarshaler(p *ir.Program, op ir.Op, vt reflect.Type, mt reflect.Type) {
pc := p.PC()
vk := vt.Kind()
/* direct receiver */
if vk != reflect.Ptr {
addMarshalerOp(p, op, vt, mt)
return
}
/* value receiver with a pointer type, check for nil before calling the marshaler */
p.Add(ir.OP_is_nil)
addMarshalerOp(p, op, vt, mt)
i := p.PC()
p.Add(ir.OP_goto)
p.Pin(pc)
p.Add(ir.OP_null)
p.Pin(i)
}
func addMarshalerOp(p *ir.Program, op ir.Op, vt reflect.Type, mt reflect.Type) {
if vars.UseVM {
itab := rt.GetItab(rt.IfaceType(rt.UnpackType(mt)), rt.UnpackType(vt), true)
p.Vtab(op, vt, itab)
} else {
// OPT: get itab here
p.Rtt(op, vt)
}
}
+24
View File
@@ -0,0 +1,24 @@
//go:build !race
// +build !race
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
func encodeIntoCheckRace(buf *[]byte, val interface{}, opts Options) error {
return encodeInto(buf, val, opts)
}
+54
View File
@@ -0,0 +1,54 @@
//go:build race
// +build race
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
import (
`encoding/json`
`github.com/bytedance/sonic/internal/rt`
)
func helpDetectDataRace(val interface{}) {
var out []byte
defer func() {
if v := recover(); v != nil {
// NOTICE: help user to locate where panic occurs
println("panic when encoding on: ", truncate(out))
panic(v)
}
}()
out, _ = json.Marshal(val)
}
func encodeIntoCheckRace(buf *[]byte, val interface{}, opts Options) error {
err := encodeInto(buf, val, opts)
/* put last to make the panic from sonic will always be caught at first */
helpDetectDataRace(val)
return err
}
func truncate(json []byte) string {
if len(json) <= 256 {
return rt.Mem2Str(json)
} else {
return rt.Mem2Str(json[len(json)-256:])
}
}
+318
View File
@@ -0,0 +1,318 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
import (
"bytes"
"encoding/json"
"reflect"
"runtime"
"unsafe"
"github.com/bytedance/sonic/utf8"
"github.com/bytedance/sonic/internal/encoder/alg"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/option"
)
// Options is a set of encoding options.
type Options uint64
const (
// SortMapKeys indicates that the keys of a map needs to be sorted
// before serializing into JSON.
// WARNING: This hurts performance A LOT, USE WITH CARE.
SortMapKeys Options = 1 << alg.BitSortMapKeys
// EscapeHTML indicates encoder to escape all HTML characters
// after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape).
// WARNING: This hurts performance A LOT, USE WITH CARE.
EscapeHTML Options = 1 << alg.BitEscapeHTML
// CompactMarshaler indicates that the output JSON from json.Marshaler
// is always compact and needs no validation
CompactMarshaler Options = 1 << alg.BitCompactMarshaler
// NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler
// is always escaped string and needs no quoting
NoQuoteTextMarshaler Options = 1 << alg.BitNoQuoteTextMarshaler
// NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}',
// instead of 'null'.
// NOTE: The priority of this option is lower than json tag `omitempty`.
NoNullSliceOrMap Options = 1 << alg.BitNoNullSliceOrMap
// ValidateString indicates that encoder should validate the input string
// before encoding it into JSON.
ValidateString Options = 1 << alg.BitValidateString
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
// after encoding the JSONMarshaler to JSON.
NoValidateJSONMarshaler Options = 1 << alg.BitNoValidateJSONMarshaler
// NoEncoderNewline indicates that the encoder should not add a newline after every message
NoEncoderNewline Options = 1 << alg.BitNoEncoderNewline
// CompatibleWithStd is used to be compatible with std encoder.
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
// Encode Infinity or Nan float into `null`, instead of returning an error.
EncodeNullForInfOrNan Options = 1 << alg.BitEncodeNullForInfOrNan
)
// Encoder represents a specific set of encoder configurations.
type Encoder struct {
Opts Options
prefix string
indent string
}
// Encode returns the JSON encoding of v.
func (self *Encoder) Encode(v interface{}) ([]byte, error) {
if self.indent != "" || self.prefix != "" {
return EncodeIndented(v, self.prefix, self.indent, self.Opts)
}
return Encode(v, self.Opts)
}
// SortKeys enables the SortMapKeys option.
func (self *Encoder) SortKeys() *Encoder {
self.Opts |= SortMapKeys
return self
}
// SetEscapeHTML specifies if option EscapeHTML opens
func (self *Encoder) SetEscapeHTML(f bool) {
if f {
self.Opts |= EscapeHTML
} else {
self.Opts &= ^EscapeHTML
}
}
// SetValidateString specifies if option ValidateString opens
func (self *Encoder) SetValidateString(f bool) {
if f {
self.Opts |= ValidateString
} else {
self.Opts &= ^ValidateString
}
}
// SetNoValidateJSONMarshaler specifies if option NoValidateJSONMarshaler opens
func (self *Encoder) SetNoValidateJSONMarshaler(f bool) {
if f {
self.Opts |= NoValidateJSONMarshaler
} else {
self.Opts &= ^NoValidateJSONMarshaler
}
}
// SetNoEncoderNewline specifies if option NoEncoderNewline opens
func (self *Encoder) SetNoEncoderNewline(f bool) {
if f {
self.Opts |= NoEncoderNewline
} else {
self.Opts &= ^NoEncoderNewline
}
}
// SetCompactMarshaler specifies if option CompactMarshaler opens
func (self *Encoder) SetCompactMarshaler(f bool) {
if f {
self.Opts |= CompactMarshaler
} else {
self.Opts &= ^CompactMarshaler
}
}
// SetNoQuoteTextMarshaler specifies if option NoQuoteTextMarshaler opens
func (self *Encoder) SetNoQuoteTextMarshaler(f bool) {
if f {
self.Opts |= NoQuoteTextMarshaler
} else {
self.Opts &= ^NoQuoteTextMarshaler
}
}
// SetIndent instructs the encoder to format each subsequent encoded
// value as if indented by the package-level function EncodeIndent().
// Calling SetIndent("", "") disables indentation.
func (enc *Encoder) SetIndent(prefix, indent string) {
enc.prefix = prefix
enc.indent = indent
}
// Quote returns the JSON-quoted version of s.
func Quote(s string) string {
buf := make([]byte, 0, len(s)+2)
buf = alg.Quote(buf, s, false)
return rt.Mem2Str(buf)
}
// Encode returns the JSON encoding of val, encoded with opts.
func Encode(val interface{}, opts Options) ([]byte, error) {
var ret []byte
buf := vars.NewBytes()
err := encodeIntoCheckRace(buf, val, opts)
/* check for errors */
if err != nil {
vars.FreeBytes(buf)
return nil, err
}
/* htmlescape or correct UTF-8 if opts enable */
old := buf
*buf = encodeFinish(*old, opts)
pbuf := ((*rt.GoSlice)(unsafe.Pointer(buf))).Ptr
pold := ((*rt.GoSlice)(unsafe.Pointer(old))).Ptr
/* return when allocated a new buffer */
if pbuf != pold {
vars.FreeBytes(old)
return *buf, nil
}
/* make a copy of the result */
if rt.CanSizeResue(cap(*buf)) {
ret = make([]byte, len(*buf))
copy(ret, *buf)
vars.FreeBytes(buf)
} else {
ret = *buf
}
/* return the buffer into pool */
return ret, nil
}
// EncodeInto is like Encode but uses a user-supplied buffer instead of allocating
// a new one.
func EncodeInto(buf *[]byte, val interface{}, opts Options) error {
err := encodeIntoCheckRace(buf, val, opts)
if err != nil {
return err
}
*buf = encodeFinish(*buf, opts)
return err
}
func encodeInto(buf *[]byte, val interface{}, opts Options) error {
stk := vars.NewStack()
efv := rt.UnpackEface(val)
err := encodeTypedPointer(buf, efv.Type, &efv.Value, stk, uint64(opts))
/* return the stack into pool */
if err != nil {
vars.ResetStack(stk)
}
vars.FreeStack(stk)
/* avoid GC ahead */
runtime.KeepAlive(buf)
runtime.KeepAlive(efv)
return err
}
func encodeFinish(buf []byte, opts Options) []byte {
if opts & EscapeHTML != 0 {
buf = HTMLEscape(nil, buf)
}
if (opts & ValidateString != 0) && !utf8.Validate(buf) {
buf = utf8.CorrectWith(nil, buf, `\ufffd`)
}
return buf
}
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
// so that the JSON will be safe to embed inside HTML <script> tags.
// For historical reasons, web browsers don't honor standard HTML
// escaping within <script> tags, so an alternative JSON encoding must
// be used.
func HTMLEscape(dst []byte, src []byte) []byte {
return alg.HtmlEscape(dst, src)
}
// EncodeIndented is like Encode but applies Indent to format the output.
// Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting.
func EncodeIndented(val interface{}, prefix string, indent string, opts Options) ([]byte, error) {
var err error
var buf *bytes.Buffer
/* encode into the buffer */
out := vars.NewBytes()
err = EncodeInto(out, val, opts)
/* check for errors */
if err != nil {
vars.FreeBytes(out)
return nil, err
}
/* indent the JSON */
buf = vars.NewBuffer()
err = json.Indent(buf, *out, prefix, indent)
vars.FreeBytes(out)
/* check for errors */
if err != nil {
vars.FreeBuffer(buf)
return nil, err
}
/* copy to the result buffer */
var ret []byte
if rt.CanSizeResue(cap(buf.Bytes())) {
ret = make([]byte, buf.Len())
copy(ret, buf.Bytes())
/* return the buffers into pool */
vars.FreeBuffer(buf)
} else {
ret = buf.Bytes()
}
return ret, nil
}
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
// order to reduce the first-hit latency.
//
// Opts are the compile options, for example, "option.WithCompileRecursiveDepth" is
// a compile option to set the depth of recursive compile for the nested struct type.
func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
cfg := option.DefaultCompileOptions()
for _, opt := range opts {
opt(&cfg)
}
return pretouchRec(map[reflect.Type]uint8{vt: 0}, cfg)
}
// Valid validates json and returns first non-blank character position,
// if it is only one valid json value.
// Otherwise returns invalid character position using start.
//
// Note: it does not check for the invalid UTF-8 characters.
func Valid(data []byte) (ok bool, start int) {
return alg.Valid(data)
}
+501
View File
@@ -0,0 +1,501 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ir
import (
"fmt"
"reflect"
"strconv"
"strings"
"unsafe"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/resolver"
"github.com/bytedance/sonic/internal/rt"
)
type Op uint8
const (
OP_null Op = iota + 1
OP_empty_arr
OP_empty_obj
OP_bool
OP_i8
OP_i16
OP_i32
OP_i64
OP_u8
OP_u16
OP_u32
OP_u64
OP_f32
OP_f64
OP_str
OP_bin
OP_quote
OP_number
OP_eface
OP_iface
OP_byte
OP_text
OP_deref
OP_index
OP_load
OP_save
OP_drop
OP_drop_2
OP_recurse
OP_is_nil
OP_is_nil_p1
OP_is_zero_1
OP_is_zero_2
OP_is_zero_4
OP_is_zero_8
OP_is_zero_map
OP_goto
OP_map_iter
OP_map_stop
OP_map_check_key
OP_map_write_key
OP_map_value_next
OP_slice_len
OP_slice_next
OP_marshal
OP_marshal_p
OP_marshal_text
OP_marshal_text_p
OP_cond_set
OP_cond_testc
OP_unsupported
OP_is_zero
)
const (
_INT_SIZE = 32 << (^uint(0) >> 63)
_PTR_SIZE = 32 << (^uintptr(0) >> 63)
_PTR_BYTE = unsafe.Sizeof(uintptr(0))
)
const OpSize = unsafe.Sizeof(NewInsOp(0))
var OpNames = [256]string{
OP_null: "null",
OP_empty_arr: "empty_arr",
OP_empty_obj: "empty_obj",
OP_bool: "bool",
OP_i8: "i8",
OP_i16: "i16",
OP_i32: "i32",
OP_i64: "i64",
OP_u8: "u8",
OP_u16: "u16",
OP_u32: "u32",
OP_u64: "u64",
OP_f32: "f32",
OP_f64: "f64",
OP_str: "str",
OP_bin: "bin",
OP_quote: "quote",
OP_number: "number",
OP_eface: "eface",
OP_iface: "iface",
OP_byte: "byte",
OP_text: "text",
OP_deref: "deref",
OP_index: "index",
OP_load: "load",
OP_save: "save",
OP_drop: "drop",
OP_drop_2: "drop_2",
OP_recurse: "recurse",
OP_is_nil: "is_nil",
OP_is_nil_p1: "is_nil_p1",
OP_is_zero_1: "is_zero_1",
OP_is_zero_2: "is_zero_2",
OP_is_zero_4: "is_zero_4",
OP_is_zero_8: "is_zero_8",
OP_is_zero_map: "is_zero_map",
OP_goto: "goto",
OP_map_iter: "map_iter",
OP_map_stop: "map_stop",
OP_map_check_key: "map_check_key",
OP_map_write_key: "map_write_key",
OP_map_value_next: "map_value_next",
OP_slice_len: "slice_len",
OP_slice_next: "slice_next",
OP_marshal: "marshal",
OP_marshal_p: "marshal_p",
OP_marshal_text: "marshal_text",
OP_marshal_text_p: "marshal_text_p",
OP_cond_set: "cond_set",
OP_cond_testc: "cond_testc",
OP_unsupported: "unsupported type",
}
func (self Op) String() string {
if ret := OpNames[self]; ret != "" {
return ret
} else {
return "<invalid>"
}
}
func OP_int() Op {
switch _INT_SIZE {
case 32:
return OP_i32
case 64:
return OP_i64
default:
panic("unsupported int size")
}
}
func OP_uint() Op {
switch _INT_SIZE {
case 32:
return OP_u32
case 64:
return OP_u64
default:
panic("unsupported uint size")
}
}
func OP_uintptr() Op {
switch _PTR_SIZE {
case 32:
return OP_u32
case 64:
return OP_u64
default:
panic("unsupported pointer size")
}
}
func OP_is_zero_ints() Op {
switch _INT_SIZE {
case 32:
return OP_is_zero_4
case 64:
return OP_is_zero_8
default:
panic("unsupported integer size")
}
}
type Instr struct {
o Op
u int // union {op: 8, _: 8, vi: 48}, vi maybe int or len(str)
p unsafe.Pointer // maybe GoString.Ptr, or *GoType
}
func NewInsOp(op Op) Instr {
return Instr{o: op}
}
func NewInsVi(op Op, vi int) Instr {
return Instr{o: op, u: vi}
}
func NewInsVs(op Op, vs string) Instr {
return Instr{
o: op,
u: len(vs),
p: (*rt.GoString)(unsafe.Pointer(&vs)).Ptr,
}
}
func NewInsVt(op Op, vt reflect.Type) Instr {
return Instr{
o: op,
p: unsafe.Pointer(rt.UnpackType(vt)),
}
}
type typAndTab struct {
vt *rt.GoType
itab *rt.GoItab
}
type typAndField struct {
vt reflect.Type
fv *resolver.FieldMeta
}
func NewInsVtab(op Op, vt reflect.Type, itab *rt.GoItab) Instr {
return Instr{
o: op,
p: unsafe.Pointer(&typAndTab{
vt: rt.UnpackType(vt),
itab: itab,
}),
}
}
func NewInsField(op Op, fv *resolver.FieldMeta) Instr {
return Instr{
o: op,
p: unsafe.Pointer(fv),
}
}
func NewInsVp(op Op, vt reflect.Type, pv bool) Instr {
i := 0
if pv {
i = 1
}
return Instr{
o: op,
u: i,
p: unsafe.Pointer(rt.UnpackType(vt)),
}
}
func (self Instr) Op() Op {
return Op(self.o)
}
func (self Instr) Vi() int {
return self.u
}
func (self Instr) Vf() uint8 {
return (*rt.GoType)(self.p).KindFlags
}
func (self Instr) VField() (*resolver.FieldMeta) {
return (*resolver.FieldMeta)(self.p)
}
func (self Instr) Vs() (v string) {
(*rt.GoString)(unsafe.Pointer(&v)).Ptr = self.p
(*rt.GoString)(unsafe.Pointer(&v)).Len = self.Vi()
return
}
func (self Instr) Vk() reflect.Kind {
return (*rt.GoType)(self.p).Kind()
}
func (self Instr) GoType() *rt.GoType {
return (*rt.GoType)(self.p)
}
func (self Instr) Vt() reflect.Type {
return (*rt.GoType)(self.p).Pack()
}
func (self Instr) Vr() *rt.GoType {
return (*rt.GoType)(self.p)
}
func (self Instr) Vp() (vt reflect.Type, pv bool) {
return (*rt.GoType)(self.p).Pack(), self.u == 1
}
func (self Instr) Vtab() (vt *rt.GoType, itab *rt.GoItab) {
tt := (*typAndTab)(self.p)
return tt.vt, tt.itab
}
func (self Instr) Vp2() (vt *rt.GoType, pv bool) {
return (*rt.GoType)(self.p), self.u == 1
}
func (self Instr) I64() int64 {
return int64(self.Vi())
}
func (self Instr) Byte() byte {
return byte(self.Vi())
}
func (self Instr) Vlen() int {
return int((*rt.GoType)(self.p).Size)
}
func (self Instr) isBranch() bool {
switch self.Op() {
case OP_goto:
fallthrough
case OP_is_nil:
fallthrough
case OP_is_nil_p1:
fallthrough
case OP_is_zero_1:
fallthrough
case OP_is_zero_2:
fallthrough
case OP_is_zero_4:
fallthrough
case OP_is_zero_8:
fallthrough
case OP_map_check_key:
fallthrough
case OP_map_write_key:
fallthrough
case OP_slice_next:
fallthrough
case OP_cond_testc:
return true
default:
return false
}
}
func (self Instr) Disassemble() string {
switch self.Op() {
case OP_byte:
return fmt.Sprintf("%-18s%s", self.Op().String(), strconv.QuoteRune(rune(self.Vi())))
case OP_text:
return fmt.Sprintf("%-18s%s", self.Op().String(), strconv.Quote(self.Vs()))
case OP_index:
return fmt.Sprintf("%-18s%d", self.Op().String(), self.Vi())
case OP_recurse:
fallthrough
case OP_map_iter:
return fmt.Sprintf("%-18s%s", self.Op().String(), self.Vt())
case OP_marshal:
fallthrough
case OP_marshal_p:
fallthrough
case OP_marshal_text:
fallthrough
case OP_marshal_text_p:
vt, _ := self.Vtab()
return fmt.Sprintf("%-18s%s", self.Op().String(), vt.Pack())
case OP_goto:
fallthrough
case OP_is_nil:
fallthrough
case OP_is_nil_p1:
fallthrough
case OP_is_zero_1:
fallthrough
case OP_is_zero_2:
fallthrough
case OP_is_zero_4:
fallthrough
case OP_is_zero_8:
fallthrough
case OP_is_zero_map:
fallthrough
case OP_cond_testc:
fallthrough
case OP_map_check_key:
fallthrough
case OP_map_write_key:
return fmt.Sprintf("%-18sL_%d", self.Op().String(), self.Vi())
case OP_slice_next:
return fmt.Sprintf("%-18sL_%d, %s", self.Op().String(), self.Vi(), self.Vt())
default:
return fmt.Sprintf("%#v", self)
}
}
type (
Program []Instr
)
func (self Program) PC() int {
return len(self)
}
func (self Program) Tag(n int) {
if n >= vars.MaxStack {
panic("type nesting too deep")
}
}
func (self Program) Pin(i int) {
v := &self[i]
v.u = self.PC()
}
func (self Program) Rel(v []int) {
for _, i := range v {
self.Pin(i)
}
}
func (self *Program) Add(op Op) {
*self = append(*self, NewInsOp(op))
}
func (self *Program) Key(op Op) {
*self = append(*self,
NewInsVi(OP_byte, '"'),
NewInsOp(op),
NewInsVi(OP_byte, '"'),
)
}
func (self *Program) Int(op Op, vi int) {
*self = append(*self, NewInsVi(op, vi))
}
func (self *Program) Str(op Op, vs string) {
*self = append(*self, NewInsVs(op, vs))
}
func (self *Program) Rtt(op Op, vt reflect.Type) {
*self = append(*self, NewInsVt(op, vt))
}
func (self *Program) Vp(op Op, vt reflect.Type, pv bool) {
*self = append(*self, NewInsVp(op, vt, pv))
}
func (self *Program) Vtab(op Op, vt reflect.Type, itab *rt.GoItab) {
*self = append(*self, NewInsVtab(op, vt, itab))
}
func (self *Program) VField(op Op, fv *resolver.FieldMeta) {
*self = append(*self, NewInsField(op, fv))
}
func (self Program) Disassemble() string {
nb := len(self)
tab := make([]bool, nb+1)
ret := make([]string, 0, nb+1)
/* prescan to get all the labels */
for _, ins := range self {
if ins.isBranch() {
tab[ins.Vi()] = true
}
}
/* disassemble each instruction */
for i, ins := range self {
if !tab[i] {
ret = append(ret, "\t"+ins.Disassemble())
} else {
ret = append(ret, fmt.Sprintf("L_%d:\n\t%s", i, ins.Disassemble()))
}
}
/* add the last label, if needed */
if tab[nb] {
ret = append(ret, fmt.Sprintf("L_%d:", nb))
}
/* add an "end" indicator, and join all the strings */
return strings.Join(append(ret, "\tend"), "\n")
}
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
import (
"reflect"
"unsafe"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/encoder/x86"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/option"
)
func ForceUseJit() {
x86.SetCompiler(makeEncoderX86)
pretouchType = pretouchTypeX86
encodeTypedPointer = x86.EncodeTypedPointer
vars.UseVM = false
}
func init() {
if vars.UseVM {
ForceUseVM()
} else {
ForceUseJit()
}
}
var _KeepAlive struct {
rb *[]byte
vp unsafe.Pointer
sb *vars.Stack
fv uint64
err error
frame [x86.FP_offs]byte
}
func makeEncoderX86(vt *rt.GoType, ex ...interface{}) (interface{}, error) {
pp, err := NewCompiler().Compile(vt.Pack(), ex[0].(bool))
if err != nil {
return nil, err
}
as := x86.NewAssembler(pp)
as.Name = vt.String()
return as.Load(), nil
}
func pretouchTypeX86(_vt reflect.Type, opts option.CompileOptions, v uint8) (map[reflect.Type]uint8, error) {
/* compile function */
compiler := NewCompiler().apply(opts)
/* find or compile */
vt := rt.UnpackType(_vt)
if val := vars.GetProgram(vt); val != nil {
return nil, nil
} else if _, err := vars.ComputeProgram(vt, makeEncoderX86, v == 1); err == nil {
return compiler.rec, nil
} else {
return nil, err
}
}
+24
View File
@@ -0,0 +1,24 @@
//go:build !amd64
// +build !amd64
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
func init() {
ForceUseVM()
}
+91
View File
@@ -0,0 +1,91 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoder
import (
"encoding/json"
"io"
"github.com/bytedance/sonic/internal/encoder/vars"
)
// StreamEncoder uses io.Writer as input.
type StreamEncoder struct {
w io.Writer
Encoder
}
// NewStreamEncoder adapts to encoding/json.NewDecoder API.
//
// NewStreamEncoder returns a new encoder that write to w.
func NewStreamEncoder(w io.Writer) *StreamEncoder {
return &StreamEncoder{w: w}
}
// Encode encodes interface{} as JSON to io.Writer
func (enc *StreamEncoder) Encode(val interface{}) (err error) {
out := vars.NewBytes()
/* encode into the buffer */
err = EncodeInto(out, val, enc.Opts)
if err != nil {
goto free_bytes
}
if enc.indent != "" || enc.prefix != "" {
/* indent the JSON */
buf := vars.NewBuffer()
err = json.Indent(buf, *out, enc.prefix, enc.indent)
if err != nil {
vars.FreeBuffer(buf)
goto free_bytes
}
// according to standard library, terminate each value with a newline...
if enc.Opts & NoEncoderNewline == 0 {
buf.WriteByte('\n')
}
/* copy into io.Writer */
_, err = io.Copy(enc.w, buf)
if err != nil {
vars.FreeBuffer(buf)
goto free_bytes
}
} else {
/* copy into io.Writer */
var n int
buf := *out
for len(buf) > 0 {
n, err = enc.w.Write(buf)
buf = buf[n:]
if err != nil {
goto free_bytes
}
}
// according to standard library, terminate each value with a newline...
if enc.Opts & NoEncoderNewline == 0 {
enc.w.Write([]byte{'\n'})
}
}
free_bytes:
vars.FreeBytes(out)
return err
}
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vars
import (
"unsafe"
"github.com/bytedance/sonic/internal/rt"
)
type Encoder func(
rb *[]byte,
vp unsafe.Pointer,
sb *Stack,
fv uint64,
) error
func FindOrCompile(vt *rt.GoType, pv bool, compiler func(*rt.GoType, ... interface{}) (interface{}, error)) (interface{}, error) {
if val := programCache.Get(vt); val != nil {
return val, nil
} else if ret, err := programCache.Compute(vt, compiler, pv); err == nil {
return ret, nil
} else {
return nil, err
}
}
func GetProgram(vt *rt.GoType) (interface{}) {
return programCache.Get(vt)
}
func ComputeProgram(vt *rt.GoType, compute func(*rt.GoType, ... interface{}) (interface{}, error), pv bool) (interface{}, error) {
return programCache.Compute(vt, compute, pv)
}
+42
View File
@@ -0,0 +1,42 @@
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vars
import (
"os"
"unsafe"
)
const (
MaxStack = 4096 // 4k states
StackSize = unsafe.Sizeof(Stack{})
StateSize = int64(unsafe.Sizeof(State{}))
StackLimit = MaxStack * StateSize
)
const (
MAX_ILBUF = 100000 // cutoff at 100k of IL instructions
MAX_FIELDS = 50 // cutoff at 50 fields struct
)
var (
DebugSyncGC = os.Getenv("SONIC_SYNC_GC") != ""
DebugAsyncGC = os.Getenv("SONIC_NO_ASYNC_GC") == ""
DebugCheckPtr = os.Getenv("SONIC_CHECK_POINTER") != ""
)
var UseVM = os.Getenv("SONIC_ENCODER_USE_VM") != ""
+69
View File
@@ -0,0 +1,69 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vars
import (
`encoding/json`
`fmt`
`reflect`
`strconv`
`unsafe`
`github.com/bytedance/sonic/internal/rt`
)
var ERR_too_deep = &json.UnsupportedValueError {
Str : "Value nesting too deep",
Value : reflect.ValueOf("..."),
}
var ERR_nan_or_infinite = &json.UnsupportedValueError {
Str : "NaN or ±Infinite",
Value : reflect.ValueOf("NaN or ±Infinite"),
}
func Error_type(vtype reflect.Type) error {
return &json.UnsupportedTypeError{Type: vtype}
}
func Error_number(number json.Number) error {
return &json.UnsupportedValueError {
Str : "invalid number literal: " + strconv.Quote(string(number)),
Value : reflect.ValueOf(number),
}
}
func Error_unsuppoted(typ *rt.GoType) error {
return &json.UnsupportedTypeError{Type: typ.Pack() }
}
func Error_marshaler(ret []byte, pos int) error {
return fmt.Errorf("invalid Marshaler output json syntax at %d: %q", pos, ret)
}
const (
PanicNilPointerOfNonEmptyString int = 1 + iota
)
func GoPanic(code int, val unsafe.Pointer) {
switch(code){
case PanicNilPointerOfNonEmptyString:
panic(fmt.Sprintf("val: %#v has nil pointer while its length is not zero!\nThis is a nil pointer exception (NPE) problem. There might be a data race issue. It is recommended to execute the tests related to the code with the `-race` compile flag to detect the problem.", (*rt.GoString)(val)))
default:
panic("encoder error!")
}
}
+146
View File
@@ -0,0 +1,146 @@
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vars
import (
"bytes"
"sync"
"unsafe"
"github.com/bytedance/sonic/internal/caching"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/option"
)
type State struct {
x int
f uint64
p unsafe.Pointer
q unsafe.Pointer
}
type Stack struct {
sp uintptr
sb [MaxStack]State
}
var (
bytesPool = sync.Pool{}
stackPool = sync.Pool{
New: func() interface{} {
return &Stack{}
},
}
bufferPool = sync.Pool{}
programCache = caching.CreateProgramCache()
)
func NewBytes() *[]byte {
if ret := bytesPool.Get(); ret != nil {
return ret.(*[]byte)
} else {
ret := make([]byte, 0, option.DefaultEncoderBufferSize)
return &ret
}
}
func NewStack() *Stack {
ret := stackPool.Get().(*Stack)
ret.sp = 0
return ret
}
func ResetStack(p *Stack) {
rt.MemclrNoHeapPointers(unsafe.Pointer(p), StackSize)
}
func (s *Stack) Top() *State {
return (*State)(rt.Add(unsafe.Pointer(&s.sb[0]), s.sp))
}
func (s *Stack) Cur() *State {
return (*State)(rt.Add(unsafe.Pointer(&s.sb[0]), s.sp - uintptr(StateSize)))
}
const _MaxStackSP = uintptr(MaxStack * StateSize)
func (s *Stack) Push(v State) bool {
if uintptr(s.sp) >= _MaxStackSP {
return false
}
st := s.Top()
*st = v
s.sp += uintptr(StateSize)
return true
}
func (s *Stack) Pop() State {
s.sp -= uintptr(StateSize)
st := s.Top()
ret := *st
*st = State{}
return ret
}
func (s *Stack) Load() (int, uint64, unsafe.Pointer, unsafe.Pointer) {
st := s.Cur()
return st.x, st.f, st.p, st.q
}
func (s *Stack) Save(x int, f uint64, p unsafe.Pointer, q unsafe.Pointer) bool {
return s.Push(State{x: x, f:f, p: p, q: q})
}
func (s *Stack) Drop() (int, uint64, unsafe.Pointer, unsafe.Pointer) {
st := s.Pop()
return st.x, st.f, st.p, st.q
}
func NewBuffer() *bytes.Buffer {
if ret := bufferPool.Get(); ret != nil {
return ret.(*bytes.Buffer)
} else {
return bytes.NewBuffer(make([]byte, 0, option.DefaultEncoderBufferSize))
}
}
func FreeBytes(p *[]byte) {
if rt.CanSizeResue(cap(*p)) {
(*p) = (*p)[:0]
bytesPool.Put(p)
}
}
func FreeStack(p *Stack) {
p.sp = 0
stackPool.Put(p)
}
func FreeBuffer(p *bytes.Buffer) {
if rt.CanSizeResue(cap(p.Bytes())) {
p.Reset()
bufferPool.Put(p)
}
}
var (
ArgPtrs = []bool{true, true, true, false}
LocalPtrs = []bool{}
ArgPtrs_generic = []bool{true}
LocalPtrs_generic = []bool{}
)
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vars
import (
`encoding`
`encoding/json`
`reflect`
)
var (
ByteType = reflect.TypeOf(byte(0))
JsonNumberType = reflect.TypeOf(json.Number(""))
JsonUnsupportedValueType = reflect.TypeOf(new(json.UnsupportedValueError))
)
var (
ErrorType = reflect.TypeOf((*error)(nil)).Elem()
JsonMarshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
EncodingTextMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
)
func IsSimpleByte(vt reflect.Type) bool {
if vt.Kind() != ByteType.Kind() {
return false
} else {
return !isEitherMarshaler(vt) && !isEitherMarshaler(reflect.PtrTo(vt))
}
}
func isEitherMarshaler(vt reflect.Type) bool {
return vt.Implements(JsonMarshalerType) || vt.Implements(EncodingTextMarshalerType)
}
+45
View File
@@ -0,0 +1,45 @@
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vm
import (
"unsafe"
_ "unsafe"
"github.com/bytedance/sonic/internal/encoder/alg"
"github.com/bytedance/sonic/internal/encoder/ir"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/rt"
)
func EncodeTypedPointer(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *vars.Stack, fv uint64) error {
if vt == nil {
return alg.EncodeNil(buf)
} else if pp, err := vars.FindOrCompile(vt, (fv&(1<<alg.BitPointerValue)) != 0, compiler); err != nil {
return err
} else if vt.Indirect() {
return Execute(buf, *vp, sb, fv, pp.(*ir.Program))
} else {
return Execute(buf, unsafe.Pointer(vp), sb, fv, pp.(*ir.Program))
}
}
var compiler func(*rt.GoType, ... interface{}) (interface{}, error)
func SetCompiler(c func(*rt.GoType, ... interface{}) (interface{}, error)) {
compiler = c
}
+374
View File
@@ -0,0 +1,374 @@
// Copyright 2024 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package vm
import (
"encoding"
"encoding/json"
"fmt"
"math"
"reflect"
"unsafe"
"github.com/bytedance/sonic/internal/encoder/alg"
"github.com/bytedance/sonic/internal/encoder/ir"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/rt"
)
const (
_S_cond = iota
_S_init
)
var (
_T_json_Marshaler = rt.UnpackType(vars.JsonMarshalerType)
_T_encoding_TextMarshaler = rt.UnpackType(vars.EncodingTextMarshalerType)
)
func print_instr(buf []byte, pc int, op ir.Op, ins *ir.Instr, p unsafe.Pointer) {
if len(buf) > 20 {
fmt.Println(string(buf[len(buf)-20:]))
} else {
fmt.Println(string(buf))
}
fmt.Printf("pc %04d, op %v, ins %#v, ptr: %x\n", pc, op, ins.Disassemble(), p)
}
func Execute(b *[]byte, p unsafe.Pointer, s *vars.Stack, flags uint64, prog *ir.Program) (error) {
pl := len(*prog)
if pl <= 0 {
return nil
}
var buf = *b
var x int
var q unsafe.Pointer
var f uint64
var pro = &(*prog)[0]
for pc := 0; pc < pl; {
ins := (*ir.Instr)(rt.Add(unsafe.Pointer(pro), ir.OpSize*uintptr(pc)))
pc++
op := ins.Op()
switch op {
case ir.OP_goto:
pc = ins.Vi()
continue
case ir.OP_byte:
v := ins.Byte()
buf = append(buf, v)
case ir.OP_text:
v := ins.Vs()
buf = append(buf, v...)
case ir.OP_deref:
p = *(*unsafe.Pointer)(p)
case ir.OP_index:
p = rt.Add(p, uintptr(ins.I64()))
case ir.OP_load:
// NOTICE: load CANNOT change f!
x, _, p, q = s.Load()
case ir.OP_save:
if !s.Save(x, f, p, q) {
return vars.ERR_too_deep
}
case ir.OP_drop:
x, f, p, q = s.Drop()
case ir.OP_drop_2:
s.Drop()
x, f, p, q = s.Drop()
case ir.OP_recurse:
vt, pv := ins.Vp2()
f := flags
if pv {
f |= (1 << alg.BitPointerValue)
}
*b = buf
if vt.Indirect() {
if err := EncodeTypedPointer(b, vt, (*unsafe.Pointer)(rt.NoEscape(unsafe.Pointer(&p))), s, f); err != nil {
return err
}
} else {
vp := (*unsafe.Pointer)(p)
if err := EncodeTypedPointer(b, vt, vp, s, f); err != nil {
return err
}
}
buf = *b
case ir.OP_is_nil:
if is_nil(p) {
pc = ins.Vi()
continue
}
case ir.OP_is_nil_p1:
if (*rt.GoEface)(p).Value == nil {
pc = ins.Vi()
continue
}
case ir.OP_null:
buf = append(buf, 'n', 'u', 'l', 'l')
case ir.OP_str:
v := *(*string)(p)
buf = alg.Quote(buf, v, false)
case ir.OP_bool:
if *(*bool)(p) {
buf = append(buf, 't', 'r', 'u', 'e')
} else {
buf = append(buf, 'f', 'a', 'l', 's', 'e')
}
case ir.OP_i8:
v := *(*int8)(p)
buf = alg.I64toa(buf, int64(v))
case ir.OP_i16:
v := *(*int16)(p)
buf = alg.I64toa(buf, int64(v))
case ir.OP_i32:
v := *(*int32)(p)
buf = alg.I64toa(buf, int64(v))
case ir.OP_i64:
v := *(*int64)(p)
buf = alg.I64toa(buf, int64(v))
case ir.OP_u8:
v := *(*uint8)(p)
buf = alg.U64toa(buf, uint64(v))
case ir.OP_u16:
v := *(*uint16)(p)
buf = alg.U64toa(buf, uint64(v))
case ir.OP_u32:
v := *(*uint32)(p)
buf = alg.U64toa(buf, uint64(v))
case ir.OP_u64:
v := *(*uint64)(p)
buf = alg.U64toa(buf, uint64(v))
case ir.OP_f32:
v := *(*float32)(p)
if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
if flags&(1<<alg.BitEncodeNullForInfOrNan) != 0 {
buf = append(buf, 'n', 'u', 'l', 'l')
continue
}
return vars.ERR_nan_or_infinite
}
buf = alg.F32toa(buf, v)
case ir.OP_f64:
v := *(*float64)(p)
if math.IsNaN(v) || math.IsInf(v, 0) {
if flags&(1<<alg.BitEncodeNullForInfOrNan) != 0 {
buf = append(buf, 'n', 'u', 'l', 'l')
continue
}
return vars.ERR_nan_or_infinite
}
buf = alg.F64toa(buf, v)
case ir.OP_bin:
v := *(*[]byte)(p)
buf = rt.EncodeBase64(buf, v)
case ir.OP_quote:
v := *(*string)(p)
buf = alg.Quote(buf, v, true)
case ir.OP_number:
v := *(*json.Number)(p)
if v == "" {
buf = append(buf, '0')
} else if !rt.IsValidNumber(string(v)) {
return vars.Error_number(v)
} else {
buf = append(buf, v...)
}
case ir.OP_eface:
*b = buf
if err := EncodeTypedPointer(b, *(**rt.GoType)(p), (*unsafe.Pointer)(rt.Add(p, 8)), s, flags); err != nil {
return err
}
buf = *b
case ir.OP_iface:
*b = buf
if err := EncodeTypedPointer(b, (*(**rt.GoItab)(p)).Vt, (*unsafe.Pointer)(rt.Add(p, 8)), s, flags); err != nil {
return err
}
buf = *b
case ir.OP_is_zero_map:
v := *(*unsafe.Pointer)(p)
if v == nil || rt.Maplen(v) == 0 {
pc = ins.Vi()
continue
}
case ir.OP_map_iter:
v := *(*unsafe.Pointer)(p)
vt := ins.Vr()
it, err := alg.IteratorStart(rt.MapType(vt), v, flags)
if err != nil {
return err
}
q = unsafe.Pointer(it)
case ir.OP_map_stop:
it := (*alg.MapIterator)(q)
alg.IteratorStop(it)
q = nil
case ir.OP_map_value_next:
it := (*alg.MapIterator)(q)
p = it.It.V
alg.IteratorNext(it)
case ir.OP_map_check_key:
it := (*alg.MapIterator)(q)
if it.It.K == nil {
pc = ins.Vi()
continue
}
p = it.It.K
case ir.OP_marshal_text:
vt, itab := ins.Vtab()
var it rt.GoIface
switch vt.Kind() {
case reflect.Interface :
if is_nil(p) {
buf = append(buf, 'n', 'u', 'l', 'l')
continue
}
it = rt.AssertI2I(_T_encoding_TextMarshaler, *(*rt.GoIface)(p))
case reflect.Ptr, reflect.Map : it = convT2I(p, true, itab)
default : it = convT2I(p, !vt.Indirect(), itab)
}
if err := alg.EncodeTextMarshaler(&buf, *(*encoding.TextMarshaler)(unsafe.Pointer(&it)), (flags)); err != nil {
return err
}
case ir.OP_marshal_text_p:
_, itab := ins.Vtab()
it := convT2I(p, false, itab)
if err := alg.EncodeTextMarshaler(&buf, *(*encoding.TextMarshaler)(unsafe.Pointer(&it)), (flags)); err != nil {
return err
}
case ir.OP_map_write_key:
if has_opts(flags, alg.BitSortMapKeys) {
v := *(*string)(p)
buf = alg.Quote(buf, v, false)
pc = ins.Vi()
continue
}
case ir.OP_slice_len:
v := (*rt.GoSlice)(p)
x = v.Len
p = v.Ptr
//TODO: why?
f |= 1<<_S_init
case ir.OP_slice_next:
if x == 0 {
pc = ins.Vi()
continue
}
x--
if has_opts(f, _S_init) {
f &= ^uint64(1 << _S_init)
} else {
p = rt.Add(p, uintptr(ins.Vlen()))
}
case ir.OP_cond_set:
f |= 1<<_S_cond
case ir.OP_cond_testc:
if has_opts(f, _S_cond) {
f &= ^uint64(1 << _S_cond)
pc = ins.Vi()
continue
}
case ir.OP_is_zero:
fv := ins.VField()
if alg.IsZero(p, fv) {
pc = ins.Vi()
continue
}
case ir.OP_is_zero_1:
if *(*uint8)(p) == 0 {
pc = ins.Vi()
continue
}
case ir.OP_is_zero_2:
if *(*uint16)(p) == 0 {
pc = ins.Vi()
continue
}
case ir.OP_is_zero_4:
if *(*uint32)(p) == 0 {
pc = ins.Vi()
continue
}
case ir.OP_is_zero_8:
if *(*uint64)(p) == 0 {
pc = ins.Vi()
continue
}
case ir.OP_empty_arr:
if has_opts(flags, alg.BitNoNullSliceOrMap) {
buf = append(buf, '[', ']')
} else {
buf = append(buf, 'n', 'u', 'l', 'l')
}
case ir.OP_empty_obj:
if has_opts(flags, alg.BitNoNullSliceOrMap) {
buf = append(buf, '{', '}')
} else {
buf = append(buf, 'n', 'u', 'l', 'l')
}
case ir.OP_marshal:
vt, itab := ins.Vtab()
var it rt.GoIface
switch vt.Kind() {
case reflect.Interface :
if is_nil(p) {
buf = append(buf, 'n', 'u', 'l', 'l')
continue
}
it = rt.AssertI2I(_T_json_Marshaler, *(*rt.GoIface)(p))
case reflect.Ptr, reflect.Map : it = convT2I(p, true, itab)
default : it = convT2I(p, !vt.Indirect(), itab)
}
if err := alg.EncodeJsonMarshaler(&buf, *(*json.Marshaler)(unsafe.Pointer(&it)), (flags)); err != nil {
return err
}
case ir.OP_marshal_p:
_, itab := ins.Vtab()
it := convT2I(p, false, itab)
if err := alg.EncodeJsonMarshaler(&buf, *(*json.Marshaler)(unsafe.Pointer(&it)), (flags)); err != nil {
return err
}
case ir.OP_unsupported:
return vars.Error_unsuppoted(ins.GoType())
default:
panic(fmt.Sprintf("not implement %s at %d", ins.Op().String(), pc))
}
}
*b = buf
return nil
}
func has_opts(opts uint64, bit int) bool {
return opts & (1<<bit) != 0
}
func is_nil(p unsafe.Pointer) bool {
return *(*unsafe.Pointer)(p) == nil
}
func convT2I(ptr unsafe.Pointer, deref bool, itab *rt.GoItab) (rt.GoIface) {
if deref {
ptr = *(*unsafe.Pointer)(ptr)
}
return rt.GoIface{
Itab: itab,
Value: ptr,
}
}
@@ -0,0 +1,53 @@
//go:build go1.17 && !go1.21
// +build go1.17,!go1.21
// Copyright 2023 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package x86
import (
"strconv"
"unsafe"
"github.com/bytedance/sonic/internal/jit"
"github.com/bytedance/sonic/internal/rt"
"github.com/twitchyliquid64/golang-asm/obj"
"github.com/twitchyliquid64/golang-asm/obj/x86"
)
var (
_V_writeBarrier = jit.Imm(int64(uintptr(unsafe.Pointer(&rt.RuntimeWriteBarrier))))
_F_gcWriteBarrierAX = jit.Func(rt.GcWriteBarrierAX)
)
func (self *Assembler) WritePtr(i int, ptr obj.Addr, rec obj.Addr) {
if rec.Reg == x86.REG_AX || rec.Index == x86.REG_AX {
panic("rec contains AX!")
}
self.Emit("MOVQ", _V_writeBarrier, _BX)
self.Emit("CMPL", jit.Ptr(_BX, 0), jit.Imm(0))
self.Sjmp("JE", "_no_writeBarrier" + strconv.Itoa(i) + "_{n}")
self.xsave(_DI)
self.Emit("MOVQ", ptr, _AX)
self.Emit("LEAQ", rec, _DI)
self.Emit("MOVQ", _F_gcWriteBarrierAX, _BX) // MOVQ ${fn}, AX
self.Rjmp("CALL", _BX)
self.xload(_DI)
self.Sjmp("JMP", "_end_writeBarrier" + strconv.Itoa(i) + "_{n}")
self.Link("_no_writeBarrier" + strconv.Itoa(i) + "_{n}")
self.Emit("MOVQ", ptr, rec)
self.Link("_end_writeBarrier" + strconv.Itoa(i) + "_{n}")
}
@@ -0,0 +1,52 @@
//go:build go1.21 && !go1.25
// +build go1.21,!go1.25
// Copyright 2023 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package x86
import (
"strconv"
"unsafe"
"github.com/bytedance/sonic/internal/jit"
"github.com/bytedance/sonic/internal/rt"
"github.com/twitchyliquid64/golang-asm/obj"
"github.com/twitchyliquid64/golang-asm/obj/x86"
)
var (
_V_writeBarrier = jit.Imm(int64(uintptr(unsafe.Pointer(&rt.RuntimeWriteBarrier))))
_F_gcWriteBarrier2 = jit.Func(rt.GcWriteBarrier2)
)
func (self *Assembler) WritePtr(i int, ptr obj.Addr, old obj.Addr) {
if old.Reg == x86.REG_AX || old.Index == x86.REG_AX {
panic("rec contains AX!")
}
self.Emit("MOVQ", _V_writeBarrier, _BX)
self.Emit("CMPL", jit.Ptr(_BX, 0), jit.Imm(0))
self.Sjmp("JE", "_no_writeBarrier" + strconv.Itoa(i) + "_{n}")
self.xsave(_SP_q)
self.Emit("MOVQ", _F_gcWriteBarrier2, _BX) // MOVQ ${fn}, AX
self.Rjmp("CALL", _BX)
self.Emit("MOVQ", ptr, jit.Ptr(_SP_q, 0))
self.Emit("MOVQ", old, _AX)
self.Emit("MOVQ", _AX, jit.Ptr(_SP_q, 8))
self.xload(_SP_q)
self.Link("_no_writeBarrier" + strconv.Itoa(i) + "_{n}")
self.Emit("MOVQ", ptr, old)
}
File diff suppressed because it is too large Load Diff
+66
View File
@@ -0,0 +1,66 @@
// +build go1.17,!go1.17
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package x86
import (
`os`
`strings`
`runtime`
`runtime/debug`
`github.com/bytedance/sonic/internal/jit`
)
var (
debugSyncGC = os.Getenv("SONIC_SYNC_GC") != ""
debugAsyncGC = os.Getenv("SONIC_NO_ASYNC_GC") == ""
)
var (
_Instr_End _Instr = newInsOp(_OP_null)
_F_gc = jit.Func(runtime.GC)
_F_force_gc = jit.Func(debug.FreeOSMemory)
_F_println = jit.Func(println_wrapper)
)
func println_wrapper(i int, op1 int, op2 int){
println(i, " Intrs ", op1, _OpNames[op1], "next: ", op2, _OpNames[op2])
}
func (self *_Assembler) force_gc() {
self.call_go(_F_gc)
self.call_go(_F_force_gc)
}
func (self *_Assembler) debug_instr(i int, v *_Instr) {
if debugSyncGC {
if (i+1 == len(self.p)) {
self.print_gc(i, v, &_Instr_End)
} else {
next := &(self.p[i+1])
self.print_gc(i, v, next)
name := _OpNames[next.op()]
if strings.Contains(name, "save") {
return
}
}
self.force_gc()
}
}
+201
View File
@@ -0,0 +1,201 @@
//go:build go1.17 && !go1.25
// +build go1.17,!go1.25
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package x86
import (
"fmt"
"runtime"
"strings"
"unsafe"
"github.com/bytedance/sonic/internal/encoder/ir"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/jit"
"github.com/twitchyliquid64/golang-asm/obj"
)
const _FP_debug = 128
var (
_Instr_End = ir.NewInsOp(ir.OP_is_nil)
_F_gc = jit.Func(gc)
_F_println = jit.Func(println_wrapper)
_F_print = jit.Func(print)
)
func (self *Assembler) dsave(r ...obj.Addr) {
for i, v := range r {
if i > _FP_debug/8-1 {
panic("too many registers to save")
} else {
self.Emit("MOVQ", v, jit.Ptr(_SP, _FP_fargs+_FP_saves+_FP_locals+int64(i)*8))
}
}
}
func (self *Assembler) dload(r ...obj.Addr) {
for i, v := range r {
if i > _FP_debug/8-1 {
panic("too many registers to load")
} else {
self.Emit("MOVQ", jit.Ptr(_SP, _FP_fargs+_FP_saves+_FP_locals+int64(i)*8), v)
}
}
}
func println_wrapper(i int, op1 int, op2 int) {
println(i, " Intrs ", op1, ir.OpNames[op1], "next: ", op2, ir.OpNames[op2])
}
func print(i int) {
println(i)
}
func gc() {
if !vars.DebugSyncGC {
return
}
runtime.GC()
// debug.FreeOSMemory()
}
func (self *Assembler) dcall(fn obj.Addr) {
self.Emit("MOVQ", fn, _R10) // MOVQ ${fn}, R10
self.Rjmp("CALL", _R10) // CALL R10
}
func (self *Assembler) debug_gc() {
if !vars.DebugSyncGC {
return
}
self.dsave(_REG_debug...)
self.dcall(_F_gc)
self.dload(_REG_debug...)
}
func (self *Assembler) debug_instr(i int, v *ir.Instr) {
if vars.DebugSyncGC {
if i+1 == len(self.p) {
self.print_gc(i, v, &_Instr_End)
} else {
next := &(self.p[i+1])
self.print_gc(i, v, next)
name := ir.OpNames[next.Op()]
if strings.Contains(name, "save") {
return
}
}
// self.debug_gc()
}
}
//go:noescape
//go:linkname checkptrBase runtime.checkptrBase
func checkptrBase(p unsafe.Pointer) uintptr
//go:noescape
//go:linkname findObject runtime.findObject
func findObject(p, refBase, refOff uintptr) (base uintptr, s unsafe.Pointer, objIndex uintptr)
var (
_F_checkptr = jit.Func(checkptr)
_F_printptr = jit.Func(printptr)
)
var (
_R10 = jit.Reg("R10")
)
var _REG_debug = []obj.Addr{
jit.Reg("AX"),
jit.Reg("BX"),
jit.Reg("CX"),
jit.Reg("DX"),
jit.Reg("DI"),
jit.Reg("SI"),
jit.Reg("BP"),
jit.Reg("SP"),
jit.Reg("R8"),
jit.Reg("R9"),
jit.Reg("R10"),
jit.Reg("R11"),
jit.Reg("R12"),
jit.Reg("R13"),
jit.Reg("R14"),
jit.Reg("R15"),
}
func checkptr(ptr uintptr) {
if ptr == 0 {
return
}
fmt.Printf("pointer: %x\n", ptr)
f := checkptrBase(unsafe.Pointer(uintptr(ptr)))
if f == 0 {
fmt.Printf("! unknown-based pointer: %x\n", ptr)
} else if f == 1 {
fmt.Printf("! stack pointer: %x\n", ptr)
} else {
fmt.Printf("base: %x\n", f)
}
findobj(ptr)
}
func findobj(ptr uintptr) {
base, s, objIndex := findObject(ptr, 0, 0)
if s != nil && base == 0 {
fmt.Printf("! invalid pointer: %x\n", ptr)
}
fmt.Printf("objIndex: %d\n", objIndex)
}
func (self *Assembler) check_ptr(ptr obj.Addr, lea bool) {
if !vars.DebugCheckPtr {
return
}
self.dsave(_REG_debug...)
if lea {
self.Emit("LEAQ", ptr, _R10)
} else {
self.Emit("MOVQ", ptr, _R10)
}
self.Emit("MOVQ", _R10, jit.Ptr(_SP, 0))
self.dcall(_F_checkptr)
self.dload(_REG_debug...)
}
func printptr(i int, ptr uintptr) {
fmt.Printf("[%d] ptr: %x\n", i, ptr)
}
func (self *Assembler) print_ptr(i int, ptr obj.Addr, lea bool) {
self.dsave(_REG_debug...)
if lea {
self.Emit("LEAQ", ptr, _R10)
} else {
self.Emit("MOVQ", ptr, _R10)
}
self.Emit("MOVQ", jit.Imm(int64(i)), _AX)
self.Emit("MOVQ", _R10, _BX)
self.dcall(_F_printptr)
self.dload(_REG_debug...)
}
+51
View File
@@ -0,0 +1,51 @@
/**
* Copyright 2024 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package x86
import (
"unsafe"
_ "unsafe"
"github.com/bytedance/sonic/internal/encoder/alg"
"github.com/bytedance/sonic/internal/encoder/vars"
"github.com/bytedance/sonic/internal/rt"
"github.com/bytedance/sonic/loader"
_ "github.com/cloudwego/base64x"
)
var compiler func(*rt.GoType, ... interface{}) (interface{}, error)
func SetCompiler(c func(*rt.GoType, ... interface{}) (interface{}, error)) {
compiler = c
}
func ptoenc(p loader.Function) vars.Encoder {
return *(*vars.Encoder)(unsafe.Pointer(&p))
}
func EncodeTypedPointer(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *vars.Stack, fv uint64) error {
if vt == nil {
return alg.EncodeNil(buf)
} else if fn, err := vars.FindOrCompile(vt, (fv&(1<<alg.BitPointerValue)) != 0, compiler); err != nil {
return err
} else if vt.Indirect() {
return fn.(vars.Encoder)(buf, *vp, sb, fv)
} else {
return fn.(vars.Encoder)(buf, unsafe.Pointer(vp), sb, fv)
}
}
+76
View File
@@ -0,0 +1,76 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jit
import (
"unsafe"
"github.com/twitchyliquid64/golang-asm/asm/arch"
"github.com/twitchyliquid64/golang-asm/obj"
)
var (
_AC = arch.Set("amd64")
)
func As(op string) obj.As {
if ret, ok := _AC.Instructions[op]; ok {
return ret
} else {
panic("invalid instruction: " + op)
}
}
func ImmPtr(imm unsafe.Pointer) obj.Addr {
return obj.Addr {
Type : obj.TYPE_CONST,
Offset : int64(uintptr(imm)),
}
}
func Imm(imm int64) obj.Addr {
return obj.Addr {
Type : obj.TYPE_CONST,
Offset : imm,
}
}
func Reg(reg string) obj.Addr {
if ret, ok := _AC.Register[reg]; ok {
return obj.Addr{Reg: ret, Type: obj.TYPE_REG}
} else {
panic("invalid register name: " + reg)
}
}
func Ptr(reg obj.Addr, offs int64) obj.Addr {
return obj.Addr {
Reg : reg.Reg,
Type : obj.TYPE_MEM,
Offset : offs,
}
}
func Sib(reg obj.Addr, idx obj.Addr, scale int16, offs int64) obj.Addr {
return obj.Addr {
Reg : reg.Reg,
Index : idx.Reg,
Scale : scale,
Type : obj.TYPE_MEM,
Offset : offs,
}
}
View File

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