fix(billing): validate quantity parameters and harden quota calculations
Bound user-supplied count/duration parameters at request validation, route ratio multipliers through guarded setters, and use saturating int conversions in all quota math paths.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package common
|
||||
|
||||
import "math"
|
||||
|
||||
// QuotaFromFloat converts a computed quota value to int with saturation.
|
||||
// Quota products can include user-controlled multipliers (image n, video
|
||||
// seconds, resolution ratios); an oversized product must never wrap around
|
||||
// and turn a charge into a credit. The bound is int32 because quota columns
|
||||
// (user/token/log) are 32-bit integers in the database.
|
||||
func QuotaFromFloat(value float64) int {
|
||||
if math.IsNaN(value) {
|
||||
return 0
|
||||
}
|
||||
if value >= math.MaxInt32 {
|
||||
return math.MaxInt32
|
||||
}
|
||||
if value <= math.MinInt32 {
|
||||
return math.MinInt32
|
||||
}
|
||||
return int(value)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestQuotaFromFloat guards the billing invariant that oversized quota
|
||||
// products (e.g. price multiplied by a huge user-supplied count) saturate
|
||||
// instead of wrapping into a negative charge (credit).
|
||||
func TestQuotaFromFloat(t *testing.T) {
|
||||
assert.Equal(t, 42, QuotaFromFloat(42.4))
|
||||
assert.Equal(t, -42, QuotaFromFloat(-42.4))
|
||||
// 2000 quota per call * n=18446744073686646784 overflows int64.
|
||||
assert.Equal(t, math.MaxInt32, QuotaFromFloat(2000*1.8446744073686647e19))
|
||||
assert.Equal(t, math.MinInt32, QuotaFromFloat(-2000*1.8446744073686647e19))
|
||||
assert.Equal(t, math.MaxInt32, QuotaFromFloat(math.Inf(1)))
|
||||
assert.Equal(t, math.MinInt32, QuotaFromFloat(math.Inf(-1)))
|
||||
assert.Equal(t, 0, QuotaFromFloat(math.NaN()))
|
||||
}
|
||||
Reference in New Issue
Block a user