snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
332
internal/client/track_signature_bench_test.go
Normal file
332
internal/client/track_signature_bench_test.go
Normal file
@@ -0,0 +1,332 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cristalhq/jwt/v5"
|
||||
sjson "github.com/segmentio/encoding/json"
|
||||
)
|
||||
|
||||
func generateKeys(n int) []string {
|
||||
keys := make([]string, n)
|
||||
for i := range keys {
|
||||
keys[i] = fmt.Sprintf("item_%05d", i)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// HMAC signature: create.
|
||||
func createTrackSignature(secret, channel string, keys []string, userID string, iat, expiry int64) string {
|
||||
keysHash := sha256.Sum256([]byte(strings.Join(keys, "\x00")))
|
||||
// Inner payload uses NUL separators; outer signature stays ':'-separated.
|
||||
payload := fmt.Sprintf("%d\x00%d\x00%s\x00%s\x00%x", iat, expiry, userID, channel, keysHash)
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write([]byte(payload))
|
||||
return fmt.Sprintf("%d:%d:%x", iat, expiry, mac.Sum(nil))
|
||||
}
|
||||
|
||||
// JWT: claims struct that includes keys.
|
||||
type trackClaims struct {
|
||||
jwt.RegisteredClaims
|
||||
Channel string `json:"channel"`
|
||||
Keys []string `json:"keys"`
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_HMAC_Create_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now().Unix()
|
||||
expiry := now + 3600
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
createTrackSignature(secret, channel, keys, userID, now, expiry)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_HMAC_Create_Reuse_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now().Unix()
|
||||
expiry := now + 3600
|
||||
|
||||
// Pre-compute keys hash.
|
||||
keysHash := sha256.Sum256([]byte(strings.Join(keys, "\x00")))
|
||||
|
||||
// Reuse HMAC hasher and buffers.
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
payload := make([]byte, 0, 256)
|
||||
result := make([]byte, 0, 128)
|
||||
hexBuf := make([]byte, hex.EncodedLen(sha256.Size))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mac.Reset()
|
||||
// Inner payload uses NUL separators; outer signature stays ':'-separated.
|
||||
payload = payload[:0]
|
||||
payload = strconv.AppendInt(payload, now, 10)
|
||||
payload = append(payload, 0)
|
||||
payload = strconv.AppendInt(payload, expiry, 10)
|
||||
payload = append(payload, 0)
|
||||
payload = append(payload, userID...)
|
||||
payload = append(payload, 0)
|
||||
payload = append(payload, channel...)
|
||||
payload = append(payload, 0)
|
||||
hex.Encode(hexBuf, keysHash[:])
|
||||
payload = append(payload, hexBuf...)
|
||||
mac.Write(payload)
|
||||
|
||||
result = result[:0]
|
||||
result = strconv.AppendInt(result, now, 10)
|
||||
result = append(result, ':')
|
||||
result = strconv.AppendInt(result, expiry, 10)
|
||||
result = append(result, ':')
|
||||
hex.Encode(hexBuf, mac.Sum(nil))
|
||||
result = append(result, hexBuf...)
|
||||
_ = string(result)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_HMAC_Verify_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now().Unix()
|
||||
expiry := now + 3600
|
||||
sig := createTrackSignature(secret, channel, keys, userID, now, expiry)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if !verifyTrackSignature(secret, channel, sig, keys, userID) {
|
||||
b.Fatal("verification failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_HMAC_Verify_Reuse_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now().Unix()
|
||||
expiry := now + 3600
|
||||
sig := createTrackSignature(secret, channel, keys, userID, now, expiry)
|
||||
|
||||
// Pre-compute keys hash.
|
||||
keysHash := sha256.Sum256([]byte(strings.Join(keys, "\x00")))
|
||||
|
||||
// Reuse HMAC hasher and buffers.
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
payload := make([]byte, 0, 256)
|
||||
hexBuf := make([]byte, hex.EncodedLen(sha256.Size))
|
||||
expectedHex := make([]byte, hex.EncodedLen(sha256.Size))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Parse iat:expiry:hmac from signature.
|
||||
first := strings.IndexByte(sig, ':')
|
||||
rest := sig[first+1:]
|
||||
second := strings.IndexByte(rest, ':')
|
||||
iatStr := sig[:first]
|
||||
expiryStr := rest[:second]
|
||||
hmacHex := rest[second+1:]
|
||||
|
||||
mac.Reset()
|
||||
// Inner payload uses NUL separators; outer signature parsed with ':' above.
|
||||
payload = payload[:0]
|
||||
payload = append(payload, iatStr...)
|
||||
payload = append(payload, 0)
|
||||
payload = append(payload, expiryStr...)
|
||||
payload = append(payload, 0)
|
||||
payload = append(payload, userID...)
|
||||
payload = append(payload, 0)
|
||||
payload = append(payload, channel...)
|
||||
payload = append(payload, 0)
|
||||
hex.Encode(hexBuf, keysHash[:])
|
||||
payload = append(payload, hexBuf...)
|
||||
mac.Write(payload)
|
||||
hex.Encode(expectedHex, mac.Sum(nil))
|
||||
|
||||
if !hmac.Equal([]byte(hmacHex), expectedHex) {
|
||||
b.Fatal("verification failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_JWT_Create_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now()
|
||||
expiry := now.Add(time.Hour)
|
||||
|
||||
signer, err := jwt.NewSignerHS(jwt.HS256, []byte(secret))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
builder := jwt.NewBuilder(signer)
|
||||
|
||||
claims := &trackClaims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: userID,
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(expiry),
|
||||
},
|
||||
Channel: channel,
|
||||
Keys: keys,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := builder.Build(claims)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_JWT_Create_Segmentio_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now()
|
||||
expiry := now.Add(time.Hour)
|
||||
|
||||
signer, err := jwt.NewSignerHS(jwt.HS256, []byte(secret))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
builder := jwt.NewBuilder(signer)
|
||||
|
||||
claims := &trackClaims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: userID,
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(expiry),
|
||||
},
|
||||
Channel: channel,
|
||||
Keys: keys,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
rawClaims, err := sjson.Marshal(claims)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, err = builder.Build(rawClaims)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_JWT_Verify_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now()
|
||||
expiry := now.Add(time.Hour)
|
||||
|
||||
signer, err := jwt.NewSignerHS(jwt.HS256, []byte(secret))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
builder := jwt.NewBuilder(signer)
|
||||
|
||||
claims := &trackClaims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: userID,
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(expiry),
|
||||
},
|
||||
Channel: channel,
|
||||
Keys: keys,
|
||||
}
|
||||
|
||||
token, err := builder.Build(claims)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
tokenBytes := token.Bytes()
|
||||
|
||||
verifier, err := jwt.NewVerifierHS(jwt.HS256, []byte(secret))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parsedToken, err := jwt.Parse(tokenBytes, verifier)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var parsed trackClaims
|
||||
if err := parsedToken.DecodeClaims(&parsed); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrackSignature_JWT_Verify_Segmentio_10kKeys(b *testing.B) {
|
||||
keys := generateKeys(10_000)
|
||||
secret := "bench-secret-key"
|
||||
channel := "post_votes:feed1"
|
||||
userID := "user123"
|
||||
now := time.Now()
|
||||
expiry := now.Add(time.Hour)
|
||||
|
||||
signer, err := jwt.NewSignerHS(jwt.HS256, []byte(secret))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
builder := jwt.NewBuilder(signer)
|
||||
|
||||
claims := &trackClaims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Subject: userID,
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(expiry),
|
||||
},
|
||||
Channel: channel,
|
||||
Keys: keys,
|
||||
}
|
||||
|
||||
token, err := builder.Build(claims)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
tokenBytes := token.Bytes()
|
||||
|
||||
verifier, err := jwt.NewVerifierHS(jwt.HS256, []byte(secret))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parsedToken, err := jwt.Parse(tokenBytes, verifier)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var parsed trackClaims
|
||||
if _, err := sjson.Parse(parsedToken.Claims(), &parsed, sjson.ZeroCopy); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user