fix(orm)!: duration encoding doesn't handle nil values properly (#15138)
Co-authored-by: marbar3778 <marbar3778@yahoo.com> Co-authored-by: Ryan Christoffersen <12519942+ryanchristo@users.noreply.github.com>
This commit is contained in:
co-authored by
marbar3778
Ryan Christoffersen
parent
9e4fbb6db1
commit
13493d3645
@@ -4,9 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/encoding/ormfield"
|
||||
|
||||
@@ -174,35 +171,3 @@ func TestCompactUInt64(t *testing.T) {
|
||||
assert.Equal(t, y, y2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimestamp(t *testing.T) {
|
||||
cdc := ormfield.TimestampCodec{}
|
||||
|
||||
// nil value
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(protoreflect.Value{}, buf))
|
||||
assert.Equal(t, 1, len(buf.Bytes()))
|
||||
val, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, !val.IsValid())
|
||||
|
||||
// no nanos
|
||||
ts := timestamppb.New(time.Date(2022, 1, 1, 12, 30, 15, 0, time.UTC))
|
||||
val = protoreflect.ValueOfMessage(ts.ProtoReflect())
|
||||
buf = &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 6, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
|
||||
// nanos
|
||||
ts = timestamppb.New(time.Date(2022, 1, 1, 12, 30, 15, 235809753, time.UTC))
|
||||
val = protoreflect.ValueOfMessage(ts.ProtoReflect())
|
||||
buf = &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 9, len(buf.Bytes()))
|
||||
val2, err = cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
}
|
||||
|
||||
@@ -1,51 +1,102 @@
|
||||
package ormfield
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
io "io"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
var (
|
||||
durationSecondsField = durationMsgType.Descriptor().Fields().ByName("seconds")
|
||||
durationNanosField = durationMsgType.Descriptor().Fields().ByName("nanos")
|
||||
const (
|
||||
DurationSecondsMin = -315576000000
|
||||
DurationSecondsMax = 315576000000
|
||||
DurationNanosMin = -999999999
|
||||
DurationNanosMax = 999999999
|
||||
)
|
||||
|
||||
func getDurationSecondsAndNanos(value protoreflect.Value) (protoreflect.Value, protoreflect.Value) {
|
||||
msg := value.Message()
|
||||
return msg.Get(durationSecondsField), msg.Get(durationNanosField)
|
||||
}
|
||||
|
||||
// DurationCodec encodes a google.protobuf.Duration value as 12 bytes using
|
||||
// Int64Codec for seconds followed by Int32Codec for nanos. This allows for
|
||||
// sorted iteration.
|
||||
type DurationCodec struct{}
|
||||
|
||||
func (d DurationCodec) Decode(r Reader) (protoreflect.Value, error) {
|
||||
seconds, err := int64Codec.Decode(r)
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
nanos, err := int32Codec.Decode(r)
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
msg := durationMsgType.New()
|
||||
msg.Set(durationSecondsField, seconds)
|
||||
msg.Set(durationNanosField, nanos)
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
}
|
||||
|
||||
func (d DurationCodec) Encode(value protoreflect.Value, w io.Writer) error {
|
||||
// nil case
|
||||
if !value.IsValid() {
|
||||
_, err := w.Write(timestampDurationNilBz)
|
||||
return err
|
||||
}
|
||||
|
||||
seconds, nanos := getDurationSecondsAndNanos(value)
|
||||
err := int64Codec.Encode(seconds, w)
|
||||
secondsInt := seconds.Int()
|
||||
if secondsInt < DurationSecondsMin || secondsInt > DurationSecondsMax {
|
||||
return fmt.Errorf("duration seconds is out of range %d, must be between %d and %d", secondsInt, DurationSecondsMin, DurationSecondsMax)
|
||||
}
|
||||
negative := secondsInt < 0
|
||||
// we subtract the min duration value to make sure secondsInt is always non-negative and starts at 0
|
||||
secondsInt -= DurationSecondsMin
|
||||
err := encodeSeconds(secondsInt, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return int32Codec.Encode(nanos, w)
|
||||
|
||||
nanosInt := nanos.Int()
|
||||
if nanosInt == 0 {
|
||||
_, err = w.Write(timestampZeroNanosBz)
|
||||
return err
|
||||
}
|
||||
|
||||
if negative {
|
||||
if nanosInt < DurationNanosMin || nanosInt > 0 {
|
||||
return fmt.Errorf("negative duration nanos is out of range %d, must be between %d and %d", nanosInt, DurationNanosMin, 0)
|
||||
}
|
||||
nanosInt = -nanosInt
|
||||
} else if nanosInt < 0 || nanosInt > DurationNanosMax {
|
||||
return fmt.Errorf("duration nanos is out of range %d, must be between %d and %d", nanosInt, 0, DurationNanosMax)
|
||||
}
|
||||
|
||||
return encodeNanos(nanosInt, w)
|
||||
}
|
||||
|
||||
func (d DurationCodec) Decode(r Reader) (protoreflect.Value, error) {
|
||||
isNil, seconds, err := decodeSeconds(r)
|
||||
if isNil || err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
|
||||
// we add the min duration value to get back the original value
|
||||
seconds += DurationSecondsMin
|
||||
|
||||
negative := seconds < 0
|
||||
|
||||
msg := durationMsgType.New()
|
||||
msg.Set(durationSecondsField, protoreflect.ValueOfInt64(seconds))
|
||||
|
||||
nanos, err := decodeNanos(r)
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
|
||||
if nanos == 0 {
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
}
|
||||
|
||||
if negative {
|
||||
nanos = -nanos
|
||||
}
|
||||
|
||||
msg.Set(durationNanosField, protoreflect.ValueOfInt32(nanos))
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
}
|
||||
|
||||
func (d DurationCodec) Compare(v1, v2 protoreflect.Value) int {
|
||||
if !v1.IsValid() {
|
||||
if !v2.IsValid() {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if !v2.IsValid() {
|
||||
return -1
|
||||
}
|
||||
|
||||
s1, n1 := getDurationSecondsAndNanos(v1)
|
||||
s2, n2 := getDurationSecondsAndNanos(v2)
|
||||
c := compareInt(s1, s2)
|
||||
@@ -61,9 +112,70 @@ func (d DurationCodec) IsOrdered() bool {
|
||||
}
|
||||
|
||||
func (d DurationCodec) FixedBufferSize() int {
|
||||
return 12
|
||||
return timestampDurationBufferSize
|
||||
}
|
||||
|
||||
func (d DurationCodec) ComputeBufferSize(protoreflect.Value) (int, error) {
|
||||
return timestampDurationBufferSize, nil
|
||||
}
|
||||
|
||||
var (
|
||||
durationSecondsField = durationMsgType.Descriptor().Fields().ByName("seconds")
|
||||
durationNanosField = durationMsgType.Descriptor().Fields().ByName("nanos")
|
||||
)
|
||||
|
||||
func getDurationSecondsAndNanos(value protoreflect.Value) (protoreflect.Value, protoreflect.Value) {
|
||||
msg := value.Message()
|
||||
return msg.Get(durationSecondsField), msg.Get(durationNanosField)
|
||||
}
|
||||
|
||||
// DurationV0Codec encodes a google.protobuf.Duration value as 12 bytes using
|
||||
// Int64Codec for seconds followed by Int32Codec for nanos. This allows for
|
||||
// sorted iteration.
|
||||
type DurationV0Codec struct{}
|
||||
|
||||
func (d DurationV0Codec) Decode(r Reader) (protoreflect.Value, error) {
|
||||
seconds, err := int64Codec.Decode(r)
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
nanos, err := int32Codec.Decode(r)
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
msg := durationMsgType.New()
|
||||
msg.Set(durationSecondsField, seconds)
|
||||
msg.Set(durationNanosField, nanos)
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
}
|
||||
|
||||
func (d DurationV0Codec) Encode(value protoreflect.Value, w io.Writer) error {
|
||||
seconds, nanos := getDurationSecondsAndNanos(value)
|
||||
err := int64Codec.Encode(seconds, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return int32Codec.Encode(nanos, w)
|
||||
}
|
||||
|
||||
func (d DurationV0Codec) Compare(v1, v2 protoreflect.Value) int {
|
||||
s1, n1 := getDurationSecondsAndNanos(v1)
|
||||
s2, n2 := getDurationSecondsAndNanos(v2)
|
||||
c := compareInt(s1, s2)
|
||||
if c != 0 {
|
||||
return c
|
||||
}
|
||||
return compareInt(n1, n2)
|
||||
}
|
||||
|
||||
func (d DurationV0Codec) IsOrdered() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d DurationV0Codec) FixedBufferSize() int {
|
||||
return 12
|
||||
}
|
||||
|
||||
func (d DurationV0Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
|
||||
return d.FixedBufferSize(), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
package ormfield_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/encoding/ormfield"
|
||||
)
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
t.Parallel()
|
||||
cdc := ormfield.DurationCodec{}
|
||||
|
||||
// nil value
|
||||
t.Run("nil value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(protoreflect.Value{}, buf))
|
||||
assert.Equal(t, 1, len(buf.Bytes()))
|
||||
val, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, !val.IsValid())
|
||||
})
|
||||
|
||||
// no nanos
|
||||
t.Run("no nanos", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dur, err := time.ParseDuration("100s")
|
||||
assert.NilError(t, err)
|
||||
durPb := durationpb.New(dur)
|
||||
val := protoreflect.ValueOfMessage(durPb.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 6, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
|
||||
t.Run("nanos", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dur, err := time.ParseDuration("3879468295ns")
|
||||
assert.NilError(t, err)
|
||||
durPb := durationpb.New(dur)
|
||||
val := protoreflect.ValueOfMessage(durPb.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 9, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
|
||||
t.Run("min value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
durPb := &durationpb.Duration{
|
||||
Seconds: -315576000000,
|
||||
Nanos: -999999999,
|
||||
}
|
||||
val := protoreflect.ValueOfMessage(durPb.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 9, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
|
||||
t.Run("max value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
durPb := &durationpb.Duration{
|
||||
Seconds: 315576000000,
|
||||
Nanos: 999999999,
|
||||
}
|
||||
val := protoreflect.ValueOfMessage(durPb.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 9, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDurationOutOfRange(t *testing.T) {
|
||||
t.Parallel()
|
||||
cdc := ormfield.DurationCodec{}
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
dur *durationpb.Duration
|
||||
expectErr string
|
||||
}{
|
||||
{
|
||||
name: "seconds too small",
|
||||
dur: &durationpb.Duration{
|
||||
Seconds: -315576000001,
|
||||
Nanos: 0,
|
||||
},
|
||||
expectErr: "seconds is out of range",
|
||||
},
|
||||
{
|
||||
name: "seconds too big",
|
||||
dur: &durationpb.Duration{
|
||||
Seconds: 315576000001,
|
||||
Nanos: 0,
|
||||
},
|
||||
expectErr: "seconds is out of range",
|
||||
},
|
||||
{
|
||||
name: "positive seconds negative nanos",
|
||||
dur: &durationpb.Duration{
|
||||
Seconds: 0,
|
||||
Nanos: -1,
|
||||
},
|
||||
expectErr: "nanos is out of range",
|
||||
},
|
||||
{
|
||||
name: "positive seconds nanos too big",
|
||||
dur: &durationpb.Duration{
|
||||
Seconds: 0,
|
||||
Nanos: 1000000000,
|
||||
},
|
||||
expectErr: "nanos is out of range",
|
||||
},
|
||||
{
|
||||
name: "negative seconds positive nanos",
|
||||
dur: &durationpb.Duration{
|
||||
Seconds: -1,
|
||||
Nanos: 1,
|
||||
},
|
||||
expectErr: "negative duration nanos is out of range",
|
||||
},
|
||||
{
|
||||
name: "negative seconds nanos too small",
|
||||
dur: &durationpb.Duration{
|
||||
Seconds: -1,
|
||||
Nanos: -1000000000,
|
||||
},
|
||||
expectErr: "negative duration nanos is out of range",
|
||||
},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
val := protoreflect.ValueOfMessage(tc.dur.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
err := cdc.Encode(val, buf)
|
||||
assert.ErrorContains(t, err, tc.expectErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -21,38 +21,33 @@ import (
|
||||
type TimestampCodec struct{}
|
||||
|
||||
const (
|
||||
timestampNilValue = 0xFF
|
||||
timestampZeroNanosValue = 0x0
|
||||
timestampSecondsMin = -62135579038
|
||||
timestampSecondsMax = 253402318799
|
||||
timestampNanosMax = 999999999
|
||||
timestampDurationNilValue = 0xFF
|
||||
timestampDurationZeroNanosValue = 0x0
|
||||
timestampDurationBufferSize = 9
|
||||
TimestampSecondsMin = -62135596800
|
||||
TimestampSecondsMax = 253402300799
|
||||
TimestampNanosMax = 999999999
|
||||
)
|
||||
|
||||
var (
|
||||
timestampNilBz = []byte{timestampNilValue}
|
||||
timestampZeroNanosBz = []byte{timestampZeroNanosValue}
|
||||
timestampDurationNilBz = []byte{timestampDurationNilValue}
|
||||
timestampZeroNanosBz = []byte{timestampDurationZeroNanosValue}
|
||||
)
|
||||
|
||||
func (t TimestampCodec) Encode(value protoreflect.Value, w io.Writer) error {
|
||||
// nil case
|
||||
if !value.IsValid() {
|
||||
_, err := w.Write(timestampNilBz)
|
||||
_, err := w.Write(timestampDurationNilBz)
|
||||
return err
|
||||
}
|
||||
|
||||
seconds, nanos := getTimestampSecondsAndNanos(value)
|
||||
secondsInt := seconds.Int()
|
||||
if secondsInt < timestampSecondsMin || secondsInt > timestampSecondsMax {
|
||||
return fmt.Errorf("seconds is out of range %d, must be between %d and %d", secondsInt, timestampSecondsMin, timestampSecondsMax)
|
||||
if secondsInt < TimestampSecondsMin || secondsInt > TimestampSecondsMax {
|
||||
return fmt.Errorf("timestamp seconds is out of range %d, must be between %d and %d", secondsInt, TimestampSecondsMin, TimestampSecondsMax)
|
||||
}
|
||||
secondsInt -= timestampSecondsMin
|
||||
var secondsBz [5]byte
|
||||
// write the seconds buffer from the end to the front
|
||||
for i := 4; i >= 0; i-- {
|
||||
secondsBz[i] = byte(secondsInt)
|
||||
secondsInt >>= 8
|
||||
}
|
||||
_, err := w.Write(secondsBz[:])
|
||||
secondsInt -= TimestampSecondsMin
|
||||
err := encodeSeconds(secondsInt, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,65 +58,104 @@ func (t TimestampCodec) Encode(value protoreflect.Value, w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if nanosInt < 0 || nanosInt > timestampNanosMax {
|
||||
return fmt.Errorf("nanos is out of range %d, must be between %d and %d", secondsInt, 0, timestampNanosMax)
|
||||
if nanosInt < 0 || nanosInt > TimestampNanosMax {
|
||||
return fmt.Errorf("timestamp nanos is out of range %d, must be between %d and %d", secondsInt, 0, TimestampNanosMax)
|
||||
}
|
||||
|
||||
return encodeNanos(nanosInt, w)
|
||||
}
|
||||
|
||||
func encodeSeconds(secondsInt int64, w io.Writer) error {
|
||||
var secondsBz [5]byte
|
||||
// write the seconds buffer from the end to the front
|
||||
for i := 4; i >= 0; i-- {
|
||||
secondsBz[i] = byte(secondsInt)
|
||||
secondsInt >>= 8
|
||||
}
|
||||
_, err := w.Write(secondsBz[:])
|
||||
return err
|
||||
}
|
||||
|
||||
func encodeNanos(nanosInt int64, w io.Writer) error {
|
||||
var nanosBz [4]byte
|
||||
for i := 3; i >= 0; i-- {
|
||||
nanosBz[i] = byte(nanosInt)
|
||||
nanosInt >>= 8
|
||||
}
|
||||
nanosBz[0] |= 0xC0
|
||||
_, err = w.Write(nanosBz[:])
|
||||
_, err := w.Write(nanosBz[:])
|
||||
return err
|
||||
}
|
||||
|
||||
func (t TimestampCodec) Decode(r Reader) (protoreflect.Value, error) {
|
||||
b0, err := r.ReadByte()
|
||||
isNil, seconds, err := decodeSeconds(r)
|
||||
if isNil || err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
|
||||
seconds += TimestampSecondsMin
|
||||
|
||||
msg := timestampMsgType.New()
|
||||
msg.Set(timestampSecondsField, protoreflect.ValueOfInt64(seconds))
|
||||
|
||||
nanos, err := decodeNanos(r)
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
}
|
||||
|
||||
if b0 == timestampNilValue {
|
||||
return protoreflect.Value{}, nil
|
||||
if nanos == 0 {
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
}
|
||||
|
||||
msg.Set(timestampNanosField, protoreflect.ValueOfInt32(nanos))
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
}
|
||||
|
||||
func decodeSeconds(r Reader) (isNil bool, seconds int64, err error) {
|
||||
b0, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
|
||||
if b0 == timestampDurationNilValue {
|
||||
return true, 0, nil
|
||||
}
|
||||
|
||||
var secondsBz [4]byte
|
||||
n, err := r.Read(secondsBz[:])
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
return false, 0, err
|
||||
}
|
||||
if n < 4 {
|
||||
return protoreflect.Value{}, io.EOF
|
||||
return false, 0, io.EOF
|
||||
}
|
||||
|
||||
seconds := int64(b0)
|
||||
seconds = int64(b0)
|
||||
for i := 0; i < 4; i++ {
|
||||
seconds <<= 8
|
||||
seconds |= int64(secondsBz[i])
|
||||
}
|
||||
seconds += timestampSecondsMin
|
||||
|
||||
msg := timestampMsgType.New()
|
||||
msg.Set(timestampSecondsField, protoreflect.ValueOfInt64(seconds))
|
||||
return false, seconds, nil
|
||||
}
|
||||
|
||||
b0, err = r.ReadByte()
|
||||
func decodeNanos(r Reader) (int32, error) {
|
||||
b0, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if b0 == timestampZeroNanosValue {
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
if b0 == timestampDurationZeroNanosValue {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var nanosBz [3]byte
|
||||
n, err = r.Read(nanosBz[:])
|
||||
n, err := r.Read(nanosBz[:])
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, err
|
||||
return 0, err
|
||||
}
|
||||
if n < 3 {
|
||||
return protoreflect.Value{}, io.EOF
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
nanos := int32(b0) & 0x3F // clear first two bits
|
||||
@@ -130,8 +164,7 @@ func (t TimestampCodec) Decode(r Reader) (protoreflect.Value, error) {
|
||||
nanos |= int32(nanosBz[i])
|
||||
}
|
||||
|
||||
msg.Set(timestampNanosField, protoreflect.ValueOfInt32(nanos))
|
||||
return protoreflect.ValueOfMessage(msg), nil
|
||||
return nanos, nil
|
||||
}
|
||||
|
||||
func (t TimestampCodec) Compare(v1, v2 protoreflect.Value) int {
|
||||
@@ -161,11 +194,11 @@ func (t TimestampCodec) IsOrdered() bool {
|
||||
}
|
||||
|
||||
func (t TimestampCodec) FixedBufferSize() int {
|
||||
return 9
|
||||
return timestampDurationBufferSize
|
||||
}
|
||||
|
||||
func (t TimestampCodec) ComputeBufferSize(protoreflect.Value) (int, error) {
|
||||
return 9, nil
|
||||
return timestampDurationBufferSize, nil
|
||||
}
|
||||
|
||||
// TimestampV0Codec encodes a google.protobuf.Timestamp value as 12 bytes using
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package ormfield_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/encoding/ormfield"
|
||||
)
|
||||
|
||||
func TestTimestamp(t *testing.T) {
|
||||
t.Parallel()
|
||||
cdc := ormfield.TimestampCodec{}
|
||||
|
||||
t.Run("nil value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(protoreflect.Value{}, buf))
|
||||
assert.Equal(t, 1, len(buf.Bytes()))
|
||||
val, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, !val.IsValid())
|
||||
})
|
||||
|
||||
t.Run("no nanos", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ts := timestamppb.New(time.Date(2022, 1, 1, 12, 30, 15, 0, time.UTC))
|
||||
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 6, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
|
||||
t.Run("nanos", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ts := timestamppb.New(time.Date(2022, 1, 1, 12, 30, 15, 235809753, time.UTC))
|
||||
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 9, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
|
||||
t.Run("min value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ts := timestamppb.New(time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 6, len(buf.Bytes()))
|
||||
assert.Assert(t, bytes.Equal(buf.Bytes(), []byte{0, 0, 0, 0, 0, 0})) // the minimum value should be all zeros
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
|
||||
t.Run("max value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ts := timestamppb.New(time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.UTC))
|
||||
val := protoreflect.ValueOfMessage(ts.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NilError(t, cdc.Encode(val, buf))
|
||||
assert.Equal(t, 9, len(buf.Bytes()))
|
||||
val2, err := cdc.Decode(buf)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 0, cdc.Compare(val, val2))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimestampOutOfRange(t *testing.T) {
|
||||
t.Parallel()
|
||||
cdc := ormfield.TimestampCodec{}
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
ts *timestamppb.Timestamp
|
||||
expectErr string
|
||||
}{
|
||||
{
|
||||
name: "before min",
|
||||
ts: timestamppb.New(time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
expectErr: "timestamp seconds is out of range",
|
||||
},
|
||||
{
|
||||
name: "after max",
|
||||
ts: timestamppb.New(time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||
expectErr: "timestamp seconds is out of range",
|
||||
},
|
||||
{
|
||||
name: "nanos too small",
|
||||
ts: ×tamppb.Timestamp{
|
||||
Seconds: 0,
|
||||
Nanos: -1,
|
||||
},
|
||||
expectErr: "timestamp nanos is out of range",
|
||||
},
|
||||
|
||||
{
|
||||
name: "nanos too big",
|
||||
ts: ×tamppb.Timestamp{
|
||||
Seconds: 0,
|
||||
Nanos: 1000000000,
|
||||
},
|
||||
expectErr: "timestamp nanos is out of range",
|
||||
},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
val := protoreflect.ValueOfMessage(tc.ts.ProtoReflect())
|
||||
buf := &bytes.Buffer{}
|
||||
err := cdc.Encode(val, buf)
|
||||
assert.ErrorContains(t, err, tc.expectErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user