diff --git a/fix_int.go b/fix_int.go index 6bd968003..63f52512c 100644 --- a/fix_int.go +++ b/fix_int.go @@ -17,6 +17,7 @@ package quickfix import ( "errors" + "math" "strconv" ) @@ -27,10 +28,20 @@ const ( // ASCII numbers 0-9. ascii0 = 48 ascii9 = 57 + + // maxSafeDigits is the most digits that cannot overflow an int: 18 when + // int is 64 bits, 9 when it is 32. + maxSafeDigits = strconv.IntSize/64*9 + 9 ) // atoi is similar to the function in strconv, but is tuned for ints appearing in FIX field types. func atoi(d []byte) (int, error) { + // Checking the length here rather than in parseUInt keeps parseUInt small + // enough to inline. + if len(d) > maxSafeDigits { + return parseLongInt(d) + } + if d[0] == asciiMinus { n, err := parseUInt(d[1:]) return (-1) * n, err @@ -58,6 +69,33 @@ func parseUInt(d []byte) (n int, err error) { return } +// parseLongInt is atoi for values long enough to overflow an int, which it +// reports as an error. +func parseLongInt(d []byte) (n int, err error) { + neg := d[0] == asciiMinus + if neg { + d = d[1:] + } + + for _, dec := range d { + if dec < ascii0 || dec > ascii9 { + err = errors.New("invalid format") + return + } + + digit := int(dec) - ascii0 + if n > (math.MaxInt-digit)/10 { + return 0, errors.New("value out of range") + } + n = n*10 + digit + } + + if neg { + n = -n + } + return +} + // FIXInt is a FIX Int Value, implements FieldValue. type FIXInt int diff --git a/fix_int_test.go b/fix_int_test.go index caa9b7840..81062b2c4 100644 --- a/fix_int_test.go +++ b/fix_int_test.go @@ -16,6 +16,8 @@ package quickfix import ( + "math" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -37,6 +39,22 @@ func TestFIXInt_Read(t *testing.T) { assert.NotNil(t, err, "Unexpected error") } +func TestFIXInt_ReadRange(t *testing.T) { + var field FIXInt + assert.Nil(t, field.Read([]byte(strconv.Itoa(math.MaxInt)))) + assert.Equal(t, math.MaxInt, int(field)) + + assert.Nil(t, field.Read([]byte(strconv.Itoa(-math.MaxInt)))) + assert.Equal(t, -math.MaxInt, int(field)) + + assert.Nil(t, field.Read([]byte("0000000000000000000000015"))) + assert.Equal(t, 15, int(field)) + + for _, v := range []string{"9223372036854775808", "-9223372036854775809", "99999999999999999999"} { + assert.NotNil(t, field.Read([]byte(v)), v) + } +} + func TestFIXInt_Int(t *testing.T) { f := FIXInt(4) assert.Equal(t, 4, f.Int()) diff --git a/parser.go b/parser.go index 38ac19ddc..4a00ef771 100644 --- a/parser.go +++ b/parser.go @@ -19,6 +19,7 @@ import ( "bytes" "errors" "io" + "math" "time" ) @@ -132,7 +133,7 @@ func (p *parser) jumpLength() (int, error) { return length, err } - if length <= 0 { + if length <= 0 || length > math.MaxInt-offset { return length, errors.New("Invalid length") } diff --git a/parser_test.go b/parser_test.go index 34bca808d..0957f2ed9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -59,6 +59,19 @@ func (s *ParserSuite) TestOverflowLength() { s.NotNil(err) } +func (s *ParserSuite) TestLengthPastMaxInt() { + for _, length := range []string{ + "9223372036854775807", + "9223372036854775808", + "119999999999999999999999999999999999999999999999999999999999970", + } { + s.parser = new(parser) + s.reader = strings.NewReader("8=FIX.4.29=" + length + "") + _, err := s.ReadMessage() + s.NotNil(err, length) + } +} + func (s *ParserSuite) TestJumpLength() { stream := "8=FIXT.1.19=11135=D34=449=TW52=20140511-23:10:3456=ISLD11=ID21=340=154=155=INTC60=20140511-23:10:3410=2348=FIXT.1.19=9535=D34=549=TW52=20140511-23:10:3456=ISLD11=ID21=340=154=155=INTC60=20140511-23:10:3410=198"