proto.gno
4.90 Kb · 161 lines
1package proto
2
3import (
4 "encoding/binary"
5 "time"
6
7 "gno.land/p/nt/ufmt/v0"
8)
9
10type WireType int
11
12const (
13 VARINT WireType = 0 // int32, int64, uint32, uint64, sint32, sint64, bool, enum
14 FIXED64 WireType = 1 // fixed64, sfixed64, double
15 LEN WireType = 2 // string, bytes, embedded messages, packed repeated fields
16 FIXED32 WireType = 5 // fixed32, sfixed32, float
17)
18
19// AppendVarint appends a varint field.
20func AppendVarint(buf []byte, fieldNum int, v uint64) []byte {
21 if v == 0 {
22 return buf
23 }
24 buf = AppendTag(buf, fieldNum, VARINT)
25 return binary.AppendUvarint(buf, v)
26}
27
28// MarshalAny returns the wire encoding of a google.protobuf.Any message
29// holding (typeURL, value). Used to match the cdc.MarshalInterface format
30// produced by the Cosmos SDK proto codec when storing or verifying
31// interface-typed messages (e.g. ClientState in the upgrade store).
32func MarshalAny(typeURL string, value []byte) []byte {
33 var bz []byte
34 bz = AppendLengthDelimited(bz, 1, []byte(typeURL))
35 bz = AppendLengthDelimited(bz, 2, value)
36 return bz
37}
38
39// AppendPackedVarintInt32 appends a packed repeated int32 varint field.
40// Negative values are sign-extended to 64 bits to match proto3 wire format.
41// Returns buf unchanged when values is empty (proto3 default).
42func AppendPackedVarintInt32(buf []byte, fieldNum int, values []int32) []byte {
43 if len(values) == 0 {
44 return buf
45 }
46 var packed []byte
47 for _, v := range values {
48 packed = binary.AppendUvarint(packed, uint64(int64(v)))
49 }
50 buf = AppendTag(buf, fieldNum, LEN)
51 buf = binary.AppendUvarint(buf, uint64(len(packed)))
52 return append(buf, packed...)
53}
54
55// AppendFixed64 appends a fixed 64-bit field.
56func AppendFixed64(buf []byte, fieldNum int, v uint64) []byte {
57 if v == 0 {
58 return buf
59 }
60 buf = AppendTag(buf, fieldNum, FIXED64)
61 var b [8]byte
62 binary.LittleEndian.PutUint64(b[:], v)
63 return append(buf, b[:]...)
64}
65
66// AppendLengthDelimited appends a length-delimited field. Returns buf
67// unchanged when bz is empty (proto3 default for optional fields).
68func AppendLengthDelimited(buf []byte, fieldNum int, bz []byte) []byte {
69 if len(bz) == 0 {
70 return buf
71 }
72 buf = AppendTag(buf, fieldNum, LEN)
73 buf = binary.AppendUvarint(buf, uint64(len(bz)))
74 return append(buf, bz...)
75}
76
77// AppendAlwaysLengthDelimited appends a length-delimited field, including
78// when bz is empty (emits a length-0 value). Required to match the wire
79// format produced by gogoproto for fields marked
80// `[(gogoproto.nullable) = false]`, which always serialize even when their
81// underlying message has no non-zero fields.
82func AppendAlwaysLengthDelimited(buf []byte, fieldNum int, bz []byte) []byte {
83 buf = AppendTag(buf, fieldNum, LEN)
84 buf = binary.AppendUvarint(buf, uint64(len(bz)))
85 return append(buf, bz...)
86}
87
88// TimeMarshal returns a proto marshalled time.
89func TimeMarshal(t time.Time) []byte {
90 var (
91 buf []byte
92 seconds = t.Unix()
93 nanos = int32(t.Nanosecond())
94 )
95 // Field 1: seconds (int64 - varint)
96 buf = AppendVarint(buf, 1, uint64(seconds))
97 // Field 2: nanos (int32 - varint)
98 buf = AppendVarint(buf, 2, uint64(nanos))
99 return buf
100}
101
102// AppendTime appends a google.protobuf.Timestamp field.
103func AppendTime(buf []byte, fieldNum int, t time.Time) []byte {
104 return AppendLengthDelimited(buf, fieldNum, TimeMarshal(t))
105}
106
107// DurationMarshal returns the inner proto encoding of a
108// google.protobuf.Duration (without the length-delimited field wrapper).
109func DurationMarshal(d time.Duration) []byte {
110 var buf []byte
111 seconds := d / time.Second
112 nanos := int32(d % time.Second)
113 if nanos < 0 {
114 nanos += int32(time.Second)
115 }
116 buf = AppendVarint(buf, 1, uint64(seconds))
117 buf = AppendVarint(buf, 2, uint64(nanos))
118 return buf
119}
120
121// AppendDuration appends a google.protobuf.Duration field.
122func AppendDuration(buf []byte, fieldNum int, d time.Duration) []byte {
123 return AppendLengthDelimited(buf, fieldNum, DurationMarshal(d))
124}
125
126// AppendTag appends a protobuf tag (field number and wire type)
127func AppendTag(buf []byte, fieldNum int, wireType WireType) []byte {
128 tag := (fieldNum << 3) | int(wireType)
129 return binary.AppendUvarint(buf, uint64(tag))
130}
131
132// DecodeVarint reads a varint from the byte slice and returns the value and new position
133func DecodeVarint(buf []byte, pos int) (uint64, int, error) {
134 if pos > len(buf) {
135 return 0, pos, ufmt.Errorf("buffer underflow while reading varint")
136 }
137 v, n := binary.Uvarint(buf[pos:])
138 if n == 0 {
139 return 0, pos, ufmt.Errorf("buffer underflow while reading varint")
140 }
141 if n < 0 {
142 return 0, pos + (-n), ufmt.Errorf("varint overflows uint64")
143 }
144 return v, pos + n, nil
145}
146
147// DecodeString reads a string (length-prefixed) from the byte slice
148func DecodeString(buf []byte, pos int) (string, int, error) {
149 length, newPos, err := DecodeVarint(buf, pos)
150 if err != nil {
151 return "", newPos, err
152 }
153 pos = newPos
154
155 if length > uint64(len(buf)-pos) {
156 return "", pos, ufmt.Errorf("buffer underflow while reading string")
157 }
158
159 end := pos + int(length)
160 return string(buf[pos:end]), end, nil
161}