325 lines
9.6 KiB
Go
325 lines
9.6 KiB
Go
package ormfield
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
)
|
|
|
|
const (
|
|
DurationSecondsMin int64 = -315576000000
|
|
DurationSecondsMax int64 = 315576000000
|
|
DurationNanosMin = -999999999
|
|
DurationNanosMax = 999999999
|
|
)
|
|
|
|
// DurationCodec encodes google.protobuf.Duration values with the following
|
|
// encoding:
|
|
// - nil is encoded as []byte{0xFF}
|
|
// - seconds (which can range from -315,576,000,000 to +315,576,000,000) is encoded as 5 fixed bytes
|
|
// - nanos (which can range from 0 to 999,999,999 or -999,999,999 to 0 if seconds is negative) are encoded such
|
|
// that 999,999,999 is always added to nanos. This ensures that the encoded nanos are always >= 0. Additionally,
|
|
// by adding 999,999,999 to both positive and negative nanos, we guarantee that the lexicographical order is
|
|
// preserved when comparing the encoded values of two Durations:
|
|
// - []byte{0xBB, 0x9A, 0xC9, 0xFF} for zero nanos
|
|
// - 4 fixed bytes with the bit mask 0x80 applied to the first byte, with negative nanos scaled so that -999,999,999
|
|
// is encoded as 0 and -1 is encoded as 999,999,998
|
|
//
|
|
// When iterating over timestamp indexes, nil values will always be ordered last.
|
|
//
|
|
// Values for seconds and nanos outside the ranges specified by google.protobuf.Duration will be rejected.
|
|
type DurationCodec struct{}
|
|
|
|
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)
|
|
secondsInt := seconds.Int()
|
|
nanosInt := nanos.Int()
|
|
|
|
if err := validateDurationRanges(secondsInt, nanosInt); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// we subtract the min duration value to make sure nanosInt is always non-negative and starts at 0.
|
|
nanosInt -= DurationNanosMin
|
|
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 seconds duration value to get back the original value
|
|
seconds += DurationSecondsMin
|
|
|
|
msg := durationMsgType.New()
|
|
msg.Set(durationSecondsField, protoreflect.ValueOfInt64(seconds))
|
|
|
|
nanos, err := decodeNanos(r)
|
|
if err != nil {
|
|
return protoreflect.Value{}, err
|
|
}
|
|
// we add the min nanos duration value to get back the original value
|
|
nanos += DurationNanosMin
|
|
|
|
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)
|
|
if c != 0 {
|
|
return c
|
|
}
|
|
|
|
return compareInt(n1, n2)
|
|
}
|
|
|
|
func (d DurationCodec) IsOrdered() bool {
|
|
return true
|
|
}
|
|
|
|
func (d DurationCodec) FixedBufferSize() int {
|
|
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)
|
|
}
|
|
|
|
// validateDurationRanges checks whether seconds and nanoseconds are in valid ranges
|
|
// for a protobuf Duration type. It ensures that seconds are within the allowed range
|
|
// and, if seconds are zero or negative, verifies that nanoseconds are also within
|
|
// the valid range. For negative seconds, nanoseconds must be non-positive.
|
|
// Parameters:
|
|
// - seconds: The number of seconds component of the duration.
|
|
// - nanos: The number of nanoseconds component of the duration.
|
|
//
|
|
// Returns:
|
|
// - error: An error indicating if the duration components are out of range.
|
|
func validateDurationRanges(seconds, nanos int64) error {
|
|
if seconds < DurationSecondsMin || seconds > DurationSecondsMax {
|
|
return fmt.Errorf("duration seconds is out of range %d, must be between %d and %d", seconds, DurationSecondsMin, DurationSecondsMax)
|
|
}
|
|
|
|
if seconds == 0 {
|
|
if nanos < DurationNanosMin || nanos > DurationNanosMax {
|
|
return fmt.Errorf("duration nanos is out of range %d, must be between %d and %d", nanos, DurationNanosMin, DurationNanosMax)
|
|
}
|
|
} else if seconds < 0 {
|
|
if nanos < DurationNanosMin || nanos > 0 {
|
|
return fmt.Errorf("negative duration nanos is out of range %d, must be between %d and %d", nanos, DurationNanosMin, 0)
|
|
}
|
|
} else if nanos < 0 || nanos > DurationNanosMax {
|
|
return fmt.Errorf("duration nanos is out of range %d, must be between %d and %d", nanos, 0, DurationNanosMax)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// DurationV1Codec encodes google.protobuf.Duration values with the following
|
|
// encoding:
|
|
// - nil is encoded as []byte{0xFF}
|
|
// - seconds (which can range from -315,576,000,000 to +315,576,000,000) is encoded as 5 fixed bytes
|
|
// - nanos (which can range from 0 to 999,999,999 or -999,999,999 to 0 if seconds is negative) is encoded as:
|
|
// - []byte{0x0} for zero nanos
|
|
// - 4 fixed bytes with the bit mask 0xC0 applied to the first byte, with negative nanos scaled so that -999,999,999
|
|
// is encoded as 1 and -1 is encoded as 999,999,999
|
|
//
|
|
// When iterating over timestamp indexes, nil values will always be ordered last.
|
|
//
|
|
// Values for seconds and nanos outside the ranges specified by google.protobuf.Duration will be rejected.
|
|
type DurationV1Codec struct{}
|
|
|
|
func (d DurationV1Codec) Encode(value protoreflect.Value, w io.Writer) error {
|
|
// nil case
|
|
if !value.IsValid() {
|
|
_, err := w.Write(timestampDurationNilBz)
|
|
return err
|
|
}
|
|
|
|
seconds, nanos := getDurationSecondsAndNanos(value)
|
|
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
|
|
}
|
|
|
|
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 = DurationNanosMax + nanosInt + 1
|
|
} 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 encodeNanosV1(nanosInt, w)
|
|
}
|
|
|
|
func (d DurationV1Codec) 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 := decodeNanosV1(r)
|
|
if err != nil {
|
|
return protoreflect.Value{}, err
|
|
}
|
|
|
|
if nanos == 0 {
|
|
return protoreflect.ValueOfMessage(msg), nil
|
|
}
|
|
|
|
if negative {
|
|
nanos = nanos - DurationNanosMax - 1
|
|
}
|
|
|
|
msg.Set(durationNanosField, protoreflect.ValueOfInt32(nanos))
|
|
return protoreflect.ValueOfMessage(msg), nil
|
|
}
|
|
|
|
func (d DurationV1Codec) 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)
|
|
if c != 0 {
|
|
return c
|
|
}
|
|
|
|
return compareInt(n1, n2)
|
|
}
|
|
|
|
func (d DurationV1Codec) IsOrdered() bool {
|
|
return true
|
|
}
|
|
|
|
func (d DurationV1Codec) FixedBufferSize() int {
|
|
return timestampDurationBufferSize
|
|
}
|
|
|
|
func (d DurationV1Codec) ComputeBufferSize(protoreflect.Value) (int, error) {
|
|
return timestampDurationBufferSize, nil
|
|
}
|