diff --git a/README.md b/README.md index 3facf4a..0c844dd 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,7 @@ pricer, err = doubleclick.NewDoubleClickPricer( "vQo9-4KtlcXmPhWaYvc8asqYuiSVMiGUdZ1RLXfrK7U", // Integrity key true, // Keys are base64 helpers.Utf8, // Keys should be ingested as Utf-8 - 1000000, // Price scale Factor Micro - false, // No debug + 1000000 // Price scale Factor Micro ) ``` ##### Encrypting a clear price diff --git a/doubleclick/doubleclick_pricer.go b/doubleclick/doubleclick_pricer.go index 3f572a9..9a5feea 100644 --- a/doubleclick/doubleclick_pricer.go +++ b/doubleclick/doubleclick_pricer.go @@ -5,13 +5,9 @@ import ( "crypto/md5" "encoding/base64" "encoding/binary" - "encoding/hex" "errors" - "fmt" - "hash" - "strings" - "github.com/benjaminch/pricers/helpers" + "hash" ) var ErrWrongSize = errors.New("Encrypted price is not 38 chars") @@ -19,18 +15,17 @@ var ErrWrongSignature = errors.New("Failed to decrypt") // DoubleClickPricer implementing price encryption and decryption // Specs : https://developers.google.com/ad-exchange/rtb/response-guide/decrypt-price +// It's not thread safe so use PricersPool type DoubleClickPricer struct { - encryptionKeyRaw string - integrityKeyRaw string - encryptionKey hash.Hash - integrityKey hash.Hash - keyDecodingMode helpers.KeyDecodingMode - scaleFactor float64 - isDebugMode bool + encryptionKey hash.Hash + integrityKey hash.Hash + decoded []byte // 28 bytes + hmacBuf []byte // 28 bytes + scaleFactor float64 } // NewDoubleClickPricer returns a DoubleClickPricer struct. -// Keys are either base 64 websafe of hexa. keyDecodingMode +// Keys are either Base64Url (websafe) of hexa. keyDecodingMode // should be used to specify how keys should be decoded. // Factor the clear price will be multiplied by before encryption. // from specs, scaleFactor is 1,000,000, but you can set something else. @@ -42,147 +37,113 @@ func NewDoubleClickPricer( integrityKey string, isBase64Keys bool, keyDecodingMode helpers.KeyDecodingMode, - scaleFactor float64, - isDebugMode bool) (*DoubleClickPricer, error) { + scaleFactor float64) (*DoubleClickPricer, error) { var err error - var encryptingFun, integrityFun hash.Hash - encryptingFun, err = helpers.CreateHmac(encryptionKey, isBase64Keys, keyDecodingMode) + encryptionKeyRaw, err := helpers.RawKeyBytes(encryptionKey, isBase64Keys, keyDecodingMode) if err != nil { return nil, err } - integrityFun, err = helpers.CreateHmac(integrityKey, isBase64Keys, keyDecodingMode) + integrityKeyRaw, err := helpers.RawKeyBytes(integrityKey, isBase64Keys, keyDecodingMode) if err != nil { return nil, err } - if isDebugMode { - fmt.Println("Keys decoding mode : ", keyDecodingMode) - fmt.Println("Encryption key : ", encryptionKey) - encryptionKeyHexa, err := hex.DecodeString(encryptionKey) - if err != nil { - encryptionKeyHexa = []byte(encryptionKey) - } - fmt.Println("Encryption key (bytes) : ", []byte(encryptionKeyHexa)) - fmt.Println("Integrity key : ", integrityKey) - integrityKeyHexa, err := hex.DecodeString(integrityKey) - if err != nil { - integrityKeyHexa = []byte(integrityKey) - } - fmt.Println("Integrity key (bytes) : ", []byte(integrityKeyHexa)) - } + encryptingFun := helpers.CreateHmac(encryptionKeyRaw) + integrityFun := helpers.CreateHmac(integrityKeyRaw) + decoded := [28]byte{} + hmacBuf := [28]byte{} return &DoubleClickPricer{ - encryptionKeyRaw: encryptionKey, - integrityKeyRaw: integrityKey, - encryptionKey: encryptingFun, - integrityKey: integrityFun, - keyDecodingMode: keyDecodingMode, - scaleFactor: scaleFactor, - isDebugMode: isDebugMode}, - nil + encryptionKey: encryptingFun, + integrityKey: integrityFun, + scaleFactor: scaleFactor, + decoded: decoded[:], + hmacBuf: hmacBuf[:], + }, nil } -// Encrypt encrypts a clear price and a given seed. -func (dc *DoubleClickPricer) Encrypt(seed string, price float64) (string, error) { - var ( - iv [16]byte - encoded [8]byte - signature []byte - ) +func NewDoubleClickPricerFromRawKeys( + encryptionKeyRaw []byte, + integrityKeyRaw []byte, + scaleFactor float64) *DoubleClickPricer { + encryptingFun := helpers.CreateHmac(encryptionKeyRaw) + integrityFun := helpers.CreateHmac(integrityKeyRaw) + decoded := [28]byte{} + hmacBuf := [28]byte{} + return &DoubleClickPricer{ + encryptionKey: encryptingFun, + integrityKey: integrityFun, + scaleFactor: scaleFactor, + decoded: decoded[:], + hmacBuf: hmacBuf[:]} +} - data := helpers.ApplyScaleFactor(price, dc.scaleFactor, dc.isDebugMode) +// Encrypt encrypts a clear price and a given seed. +func (dc *DoubleClickPricer) Encrypt(seed string, price float64) string { + data := helpers.ApplyScaleFactor(price, dc.scaleFactor) // Create Initialization Vector from seed - iv = md5.Sum([]byte(seed)) - if dc.isDebugMode { - fmt.Println("Seed : ", seed) - fmt.Println("Initialization vector : ", iv) - } + iv := md5.Sum([]byte(seed)) //pad = hmac(e_key, iv), first 8 bytes - pad := helpers.HmacSum(dc.encryptionKey, iv[:], nil)[:8] - if dc.isDebugMode { - fmt.Println("// pad = hmac(e_key, iv), first 8 bytes") - fmt.Println("Pad : ", pad) - } + pad := helpers.HmacSum(dc.encryptionKey, iv[:], nil, nil)[:8] // signature = hmac(i_key, data || iv), first 4 bytes - signature = helpers.HmacSum(dc.integrityKey, data[:], iv[:])[:4] - if dc.isDebugMode { - fmt.Println("// signature = hmac(i_key, data || iv), first 4 bytes") - fmt.Println("Signature : ", signature) - } + signature := helpers.HmacSum(dc.integrityKey, data[:], iv[:], nil)[:4] // enc_data = pad data + encoded := [8]byte{} for i := range data { encoded[i] = pad[i] ^ data[i] } - if dc.isDebugMode { - fmt.Println("// enc_data = pad data") - fmt.Println("Encoded price bytes : ", encoded) - } // final_message = WebSafeBase64Encode( iv || enc_price || signature ) - return base64.RawURLEncoding.EncodeToString(append(append(iv[:], encoded[:]...), signature...)), nil + return base64.RawURLEncoding.EncodeToString(append(append(iv[:], encoded[:]...), signature...)) } // Decrypt decrypts an encrypted price. func (dc *DoubleClickPricer) Decrypt(encryptedPrice string) (float64, error) { - var err error - var errPrice float64 + priceInMicros, err := dc.DecryptRaw([]byte(encryptedPrice)) + price := float64(priceInMicros) / dc.scaleFactor + return price, err +} +// DecryptRaw decrypts an encrypted price. +// It returns the price as integer in micros without applying a scaleFactor +func (dc *DoubleClickPricer) DecryptRaw(encryptedPrice []byte) (uint64, error) { // Decode base64 url // Just to be safe remove padding if it was added by mistake - encryptedPrice = strings.TrimRight(encryptedPrice, "=") + encryptedPrice = bytes.TrimRight(encryptedPrice, "=") if len(encryptedPrice) != 38 { - return errPrice, ErrWrongSize + return 0, ErrWrongSize } - decoded, err := base64.RawURLEncoding.DecodeString(encryptedPrice) + _, err := base64.RawURLEncoding.Decode(dc.decoded, encryptedPrice) if err != nil { - return errPrice, err - } - - if dc.isDebugMode { - fmt.Println("Encrypted price : ", encryptedPrice) - fmt.Println("Base64 decoded price : ", decoded) + return 0, err } // Get elements - var ( - iv []byte - p []byte - signature []byte - priceMicro [8]byte - ) - - iv = decoded[0:16] - p = decoded[16:24] - signature = decoded[24:28] + iv := dc.decoded[0:16] + p := binary.BigEndian.Uint64(dc.decoded[16:24]) + signature := binary.BigEndian.Uint32(dc.decoded[24:28]) // pad = hmac(e_key, iv) - pad := helpers.HmacSum(dc.encryptionKey, iv, nil)[:8] - - if dc.isDebugMode { - fmt.Println("IV : ", hex.EncodeToString(iv)) - fmt.Println("Encoded price : ", hex.EncodeToString(p)) - fmt.Println("Signature : ", hex.EncodeToString(signature)) - fmt.Println("Pad : ", hex.EncodeToString(pad)) - } + padBytes := helpers.HmacSum(dc.encryptionKey, iv, nil, dc.hmacBuf)[:8] + pad := binary.BigEndian.Uint64(padBytes) // priceMicro = p pad - for i := range p { - priceMicro[i] = pad[i] ^ p[i] - } + priceInMicros := pad ^ p + priceMicro := [8]byte{} + binary.BigEndian.PutUint64(priceMicro[:], priceInMicros) // conf_sig = hmac(i_key, data || iv) - confirmationSignature := helpers.HmacSum(dc.integrityKey, priceMicro[:], iv)[:4] + confirmationSignatureBytes := helpers.HmacSum(dc.integrityKey, priceMicro[:], iv, dc.hmacBuf)[:4] + confirmationSignature := binary.BigEndian.Uint32(confirmationSignatureBytes) // success = (conf_sig == sig) - if !bytes.Equal(confirmationSignature, signature) { - return errPrice, ErrWrongSignature + if confirmationSignature != signature { + return 0, ErrWrongSignature } - price := float64(binary.BigEndian.Uint64(priceMicro[:])) / dc.scaleFactor - - return price, nil + return priceInMicros, nil } diff --git a/doubleclick/doubleclick_pricer_test.go b/doubleclick/doubleclick_pricer_test.go index 4471a4b..5905676 100644 --- a/doubleclick/doubleclick_pricer_test.go +++ b/doubleclick/doubleclick_pricer_test.go @@ -1,6 +1,7 @@ package doubleclick import ( + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -8,8 +9,19 @@ import ( "github.com/benjaminch/pricers/helpers" ) -func buildNewDoubleClickPricer(encryptionKey string, integrityKey string, isBase64Keys bool, keyDecodingMode helpers.KeyDecodingMode, scaleFactor float64, isDebugMode bool) (*DoubleClickPricer, error) { - return NewDoubleClickPricer(encryptionKey, integrityKey, isBase64Keys, keyDecodingMode, scaleFactor, isDebugMode) +var encryptionKeyRaw, _ = helpers.RawKeyBytes("652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", false, helpers.Hexa) +var integrityKeyRaw, _ = helpers.RawKeyBytes("bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", false, helpers.Hexa) + +// Create a pricer with: +// - HEX keys +// - Price scale factor as micro +func buildPricer() *DoubleClickPricer { + return buildPricerWithScale(1000000) +} + +func buildPricerWithScale(scaleFactor float64) *DoubleClickPricer { + pricer := NewDoubleClickPricerFromRawKeys(encryptionKeyRaw, integrityKeyRaw, scaleFactor) + return pricer } type priceTestCase struct { @@ -23,21 +35,10 @@ func newPriceTestCase(encrypted string, clear float64, scaleFactor float64) pric } func TestDecryptEmpty(t *testing.T) { - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "6356770B3C111C07F778AFD69F16643E9110090FD4C479D91181EED2523788F1", - "3588BF6D387E8AEAD4EEC66798255369AF47BFD48B056E8934CEFEF3609C469E", - false, // Keys are not base64 - helpers.Utf8, - 1000000, - false, - ) + pricer := buildPricer() - assert.Nil(t, err, "Error creating new Pricer : ", err) // Execute: - var result float64 - result, err = pricer.Decrypt("") + result, err := pricer.Decrypt("") // Verify: assert.Equal(t, err, ErrWrongSize) assert.Equal(t, float64(0), result) @@ -50,14 +51,12 @@ func TestDecryptGoogleOfficialExamples(t *testing.T) { // Setup: var pricer *DoubleClickPricer var err error - pricer, err = buildNewDoubleClickPricer( + pricer, err = NewDoubleClickPricer( "ZS-DraBUUVeht_sMDgn1nnM3My_nq9TrEESbjubDkTU", "vQo9-4KtlcXmPhWaYvc8asqYuiSVMiGUdZ1RLXfrK7U", - true, // Keys are base64 + true, helpers.Utf8, - 1000000, - false, - ) + 1000000) assert.Nil(t, err, "Error creating new Pricer : ", err) @@ -84,24 +83,8 @@ func TestDecryptGoogleOfficialExamples(t *testing.T) { } func TestDecryptWithHexaKeys(t *testing.T) { - // Create a pricer with: - // - HEX keys - // - Price scale factor as micro - // - No debug mode - // Setup: - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", - "bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", - false, // Keys are not base64 - helpers.Hexa, - 1000000, - false, - ) - - assert.Nil(t, err, "Error creating new Pricer : ", err) + pricer := buildPricer() // Encrypted prices we will try to decrypt var pricesTestCase = []priceTestCase{ @@ -129,19 +112,16 @@ func TestDecryptWithUtf8Keys(t *testing.T) { // Create a pricer with: // - UTF-8 keys // - Price scale factor as micro - // - No debug mode // Setup: var pricer *DoubleClickPricer var err error - pricer, err = buildNewDoubleClickPricer( + pricer, err = NewDoubleClickPricer( "6356770B3C111C07F778AFD69F16643E9110090FD4C479D91181EED2523788F1", "3588BF6D387E8AEAD4EEC66798255369AF47BFD48B056E8934CEFEF3609C469E", - false, // Keys are not base64 - helpers.Utf8, - 1000000, false, - ) + helpers.Utf8, + 1000000) assert.Nil(t, err, "Error creating new Pricer : ", err) @@ -177,26 +157,10 @@ func TestDecryptWithScaleFactor(t *testing.T) { } for _, priceTestCase := range pricesTestCase { - // Create a pricer with: - // - HEX keys - // - Price scale factor as micro - // - No debug mode - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", - "bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", - false, // Keys are not base64 - helpers.Hexa, - priceTestCase.scaleFactor, - false, - ) - - assert.Nil(t, err, "Error creating new Pricer : ", err) + pricer := buildPricerWithScale(priceTestCase.scaleFactor) // Execute: - var result float64 - result, err = pricer.Decrypt(priceTestCase.encrypted) + result, err := pricer.Decrypt(priceTestCase.encrypted) // Verify: assert.Nil(t, err, "Decryption failed. Error : %s", err) @@ -204,29 +168,9 @@ func TestDecryptWithScaleFactor(t *testing.T) { } } -func TestDecryptWithDebug(t *testing.T) { - // TODO: To be implemented -} - func TestEncryptWithHexaKeys(t *testing.T) { - // Create a pricer with: - // - HEX keys - // - Price scale factor as micro - // - No debug mode - // Setup: - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", - "bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", - false, // Keys are not base64 - helpers.Hexa, - 1000000, - false, - ) - - assert.Nil(t, err, "Error creating new Pricer : ", err) + pricer := buildPricer() // Clear prices we will try to encrypt var pricesTestCase = []priceTestCase{ @@ -240,12 +184,9 @@ func TestEncryptWithHexaKeys(t *testing.T) { for _, price := range pricesTestCase { // Execute: - var result string - var err error - result, err = pricer.Encrypt("", price.clear) + result := pricer.Encrypt("", price.clear) // Verify: - assert.Nil(t, err, "Encryption failed. Error : %s", err) assert.Equal(t, result, price.encrypted, "Encryption failed. Should be : %s but was : %s", price.encrypted, result) } } @@ -255,7 +196,6 @@ func TestEncryptWithUtf8Keys(t *testing.T) { // Create a pricer with: // - UTF-8 keys // - Price scale factor as micro - // - No debug mode var pricer *DoubleClickPricer var err error pricer, err = buildNewDoubleClickPricer( @@ -311,56 +251,18 @@ func TestEncryptWithScaleFactor(t *testing.T) { } for _, priceTestCase := range pricesTestCase { - // Create a pricer with: - // - HEX keys - // - Price scale factor as micro - // - No debug mode - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", - "bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", - false, // Keys are not base64 - helpers.Hexa, - priceTestCase.scaleFactor, - false, - ) - - assert.Nil(t, err, "Error creating new Pricer : ", err) + pricer := buildPricerWithScale(priceTestCase.scaleFactor) // Execute: - var result string - result, err = pricer.Encrypt("", priceTestCase.clear) + result := pricer.Encrypt("", priceTestCase.clear) - // Verify: - assert.Nil(t, err, "Encryption failed. Error : %s", err) assert.Equal(t, result, priceTestCase.encrypted, "Encryption failed. Should be : %s but was : %s (scale factor: %f)", priceTestCase.encrypted, result, priceTestCase.scaleFactor) } } -func TestEncryptWithDebug(t *testing.T) { - // TODO : To be implemented -} - func TestEncryptDecryptWithHexaKeys(t *testing.T) { - // Create a pricer with: - // - HEX keys - // - Price scale factor as micro - // - No debug mode - // Setup: - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", - "bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", - false, // Keys are not base64 - helpers.Hexa, - 1000000, - false, - ) - - assert.Nil(t, err, "Error creating new Pricer : ", err) + pricer := buildPricer() // Clear prices to encrypt var pricesTestCase = []priceTestCase{ @@ -379,12 +281,11 @@ func TestEncryptDecryptWithHexaKeys(t *testing.T) { var err error // Encrypt - encrypted, err = pricer.Encrypt("", price.clear) - assert.Nil(t, err, "Encryption failed. Error : %s", err) + encrypted = pricer.Encrypt("", price.clear) // Decrypt decrypted, err = pricer.Decrypt(encrypted) - assert.Nil(t, err, "EncryDecryptionption failed. Error : %s", err) + assert.Nil(t, err, "Decryption failed. Error : %s", err) // Verify: // Assert that the decrypted price is the one with encrypted in a first place @@ -396,19 +297,16 @@ func TestEncryptDecryptWithUtf8Keys(t *testing.T) { // Create a pricer with: // - UTF-8 keys // - Price scale factor as micro - // - No debug mode // Setup: var pricer *DoubleClickPricer var err error - pricer, err = buildNewDoubleClickPricer( + pricer, err = NewDoubleClickPricer( "6356770B3C111C07F778AFD69F16643E9110090FD4C479D91181EED2523788F1", "3588BF6D387E8AEAD4EEC66798255369AF47BFD48B056E8934CEFEF3609C469E", - false, // Keys are not base64 - helpers.Utf8, - 1000000, false, - ) + helpers.Utf8, + 1000000) assert.Nil(t, err, "Error creating new Pricer : ", err) @@ -429,12 +327,11 @@ func TestEncryptDecryptWithUtf8Keys(t *testing.T) { var err error // Encrypt - encrypted, err = pricer.Encrypt("", price.clear) - assert.Nil(t, err, "Encryption failed. Error : %s", err) + encrypted = pricer.Encrypt("", price.clear) // Decrypt decrypted, err = pricer.Decrypt(encrypted) - assert.Nil(t, err, "EncryDecryptionption failed. Error : %s", err) + assert.Nil(t, err, "Decryption failed. Error : %s", err) // Verify: // Assert that the decrypted price is the one with encrypted in a first place @@ -457,22 +354,7 @@ func TestEncryptDecryptWithSeed(t *testing.T) { newPriceTestCase("", 1000, 1000000), } - // Create a pricer with: - // - HEX keys - // - Price scale factor as micro - // - No debug mode - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", - "bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", - false, // Keys are not base64 - helpers.Hexa, - 1000000, - false, - ) - - assert.Nil(t, err, "Error creating new Pricer : ", err) + pricer := buildPricer() var encryptedPrices []string @@ -485,12 +367,11 @@ func TestEncryptDecryptWithSeed(t *testing.T) { var err error // Encrypt - encrypted, err = pricer.Encrypt(seed, price.clear) - assert.Nil(t, err, "Encryption failed. Error : %s", err) + encrypted = pricer.Encrypt(seed, price.clear) // Decrypt decrypted, err = pricer.Decrypt(encrypted) - assert.Nil(t, err, "EncryDecryptionption failed. Error : %s", err) + assert.Nil(t, err, "Decryption failed. Error : %s", err) // Verify: // Assert that the decrypted price is the one with encrypted in a first place @@ -526,22 +407,7 @@ func TestEncryptDecryptWithScaleFactor(t *testing.T) { for _, scaleFactor := range scaleFactorsToTest { - // Create a pricer with: - // - HEX keys - // - Price scale factor as micro - // - No debug mode - var pricer *DoubleClickPricer - var err error - pricer, err = buildNewDoubleClickPricer( - "652f83ada0545157a1b7fb0c0e09f59e7337332fe7abd4eb10449b8ee6c39135", - "bd0a3dfb82ad95c5e63e159a62f73c6aca98ba2495322194759d512d77eb2bb5", - false, // Keys are not base64 - helpers.Hexa, - scaleFactor, - false, - ) - - assert.Nil(t, err, "Error creating new Pricer : ", err) + pricer := buildPricerWithScale(scaleFactor) for _, price := range pricesTestCase { // Execute: @@ -550,12 +416,11 @@ func TestEncryptDecryptWithScaleFactor(t *testing.T) { var err error // Encrypt - encrypted, err = pricer.Encrypt("", price.clear) - assert.Nil(t, err, "Encryption failed. Error : %s", err) + encrypted = pricer.Encrypt("", price.clear) // Decrypt decrypted, err = pricer.Decrypt(encrypted) - assert.Nil(t, err, "EncryDecryptionption failed. Error : %s", err) + assert.Nil(t, err, "Decryption failed. Error : %s", err) // Verify: // Assert that the decrypted price is the one with encrypted in a first place @@ -563,3 +428,56 @@ func TestEncryptDecryptWithScaleFactor(t *testing.T) { } } } + +func TestDecryptAlloc(t *testing.T) { + pricer := buildPricer() + encryptedPrice := "anCGGFJApcfB6ZGc6mindhpTrYXHY4ONo7lXpg" + // We can use testing.AllocsPerRun() but it gives only mallocs + // warmup + _, _ = pricer.Decrypt(encryptedPrice) + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) + var memstats runtime.MemStats + runtime.ReadMemStats(&memstats) + mallocs := 0 - memstats.Mallocs + allocBytes := 0 - memstats.Alloc + + // Run the function the specified number of times + _, _ = pricer.Decrypt(encryptedPrice) + + // Read the final statistics + runtime.ReadMemStats(&memstats) + mallocs += memstats.Mallocs + allocBytes += memstats.Alloc + + assert.Equal(t, uint64(2), mallocs) + assert.Equal(t, uint64(64), allocBytes) +} + +func TestDecryptRawAlloc(t *testing.T) { + pricer := buildPricer() + encryptedPrice := "anCGGFJApcfB6ZGc6mindhpTrYXHY4ONo7lXpg" + encryptedPriceBytes := []byte(encryptedPrice) // don't inline + mallocs := testing.AllocsPerRun(1, func() { + _, _ = pricer.DecryptRaw(encryptedPriceBytes) + }) + assert.Equal(t, float64(1), mallocs) +} + +// BenchmarkDecrypt-8 1831339 598.6 ns/op +func BenchmarkDecrypt(b *testing.B) { + pricer := buildPricer() + encryptedPrice := "anCGGFJApcfB6ZGc6mindhpTrYXHY4ONo7lXpg" + for i := 0; i < b.N; i++ { + _, _ = pricer.Decrypt(encryptedPrice) + } +} + +// BenchmarkDecryptRaw-8 2003535 556.7 ns/op +func BenchmarkDecryptRaw(b *testing.B) { + pricer := buildPricer() + encryptedPrice := "anCGGFJApcfB6ZGc6mindhpTrYXHY4ONo7lXpg" + encryptedPriceBytes := []byte(encryptedPrice) // don't inline + for i := 0; i < b.N; i++ { + _, _ = pricer.DecryptRaw(encryptedPriceBytes) + } +} diff --git a/doubleclick/pricers_pool.go b/doubleclick/pricers_pool.go new file mode 100644 index 0000000..ba05724 --- /dev/null +++ b/doubleclick/pricers_pool.go @@ -0,0 +1,33 @@ +package doubleclick + +import "sync" + +type PricersPool struct { + pool *sync.Pool + encryptionKeyRaw, integrityKeyRaw []byte + scaleFactor float64 +} + +func NewPricersPool(encryptionKeyRaw, integrityKeyRaw []byte, scaleFactor float64) *PricersPool { + return &PricersPool{ + pool: &sync.Pool{}, + encryptionKeyRaw: encryptionKeyRaw, + integrityKeyRaw: integrityKeyRaw, + scaleFactor: scaleFactor, + } +} + +func (pl *PricersPool) AcquirePricer() *DoubleClickPricer { + var pricer *DoubleClickPricer + oldPricer := pl.pool.Get() + if oldPricer != nil { + pricer = oldPricer.(*DoubleClickPricer) + } else { + pricer = NewDoubleClickPricerFromRawKeys(pl.encryptionKeyRaw, pl.integrityKeyRaw, pl.scaleFactor) + } + return pricer +} + +func (pl *PricersPool) ReleasePricer(pricer *DoubleClickPricer) { + pl.pool.Put(pricer) +} diff --git a/doubleclick/pricers_pool_test.go b/doubleclick/pricers_pool_test.go new file mode 100644 index 0000000..014e5d8 --- /dev/null +++ b/doubleclick/pricers_pool_test.go @@ -0,0 +1,17 @@ +package doubleclick + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewPricersPool(t *testing.T) { + pp := NewPricersPool(encryptionKeyRaw, integrityKeyRaw, 1000000) + pricer := pp.AcquirePricer() + defer pp.ReleasePricer(pricer) + encryptedPrice := "anCGGFJApcfB6ZGc6mindhpTrYXHY4ONo7lXpg" + encryptedPriceBytes := []byte(encryptedPrice) // don't inline + priceInMicros, err := pricer.DecryptRaw(encryptedPriceBytes) + assert.Equal(t, uint64(1354000), priceInMicros) + assert.Equal(t, nil, err) +} diff --git a/go.mod b/go.mod index eabcec5..d509bc4 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module github.com/benjaminch/pricers go 1.12 -require ( - github.com/benjaminch/openrtb-pricers v0.2.0 - github.com/stretchr/testify v1.4.0 -) +require github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 3263b4a..acb88a4 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,11 @@ -github.com/benjaminch/openrtb-pricers v0.2.0 h1:rEoZbSsa47sprVLE6qjSXsq9ROUGAAsKjFOV0gtzUH0= -github.com/benjaminch/openrtb-pricers v0.2.0/go.mod h1:/I+cVRYTUI3TkNxO3bvIzC7E6NcEzsDsdNxZt6J6RVI= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/helpers/helpers.go b/helpers/helpers.go index ff64388..7b60d05 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -7,7 +7,6 @@ import ( "encoding/binary" "encoding/hex" "errors" - "fmt" "hash" "strings" ) @@ -50,8 +49,11 @@ func ParseKeyDecodingMode(input string) (KeyDecodingMode, error) { return parsed, err } -// CreateHmac : Returns Hash from input string. -func CreateHmac(key string, isBase64 bool, mode KeyDecodingMode) (hash.Hash, error) { +func CreateHmac(keyRaw []byte) hash.Hash { + return hmac.New(sha1.New, keyRaw) +} + +func RawKeyBytes(key string, isBase64 bool, mode KeyDecodingMode) ([]byte, error) { var err error var b64DecodedKey []byte var k []byte @@ -73,29 +75,24 @@ func CreateHmac(key string, isBase64 bool, mode KeyDecodingMode) (hash.Hash, err if err != nil { return nil, err } - - return hmac.New(sha1.New, k), nil + return k, nil } // HmacSum : Returns Hmac sum bytes. -func HmacSum(hmac hash.Hash, buf, buf2 []byte) []byte { +func HmacSum(hmac hash.Hash, buf, buf2, hmacBuf []byte) []byte { hmac.Reset() hmac.Write(buf) if buf2 != nil { hmac.Write(buf2) } - return hmac.Sum(nil) + return hmac.Sum(hmacBuf[:0]) } // ApplyScaleFactor : Applies a scale factor to a given price. // Scaled price will be represented on 8 bytes. -func ApplyScaleFactor(price float64, scaleFactor float64, isDebugMode bool) [8]byte { +func ApplyScaleFactor(price float64, scaleFactor float64) [8]byte { scaledPrice := [8]byte{} binary.BigEndian.PutUint64(scaledPrice[:], uint64(price*scaleFactor)) - if isDebugMode { - fmt.Printf("Micro price bytes: %v", scaledPrice) - } - return scaledPrice }