Skip to content

Commit 1750000

Browse files
author
Christopher Swenson
authored
Use base32 instead of base64 for identifiers (#21)
Base64 in Go 1.22 requires that the list of characters in the encoding be unique. This is not possible if we are using it to generate Go identifiers. So, we switch to base32. This is not a breaking change as this only affects the generated code, and not the wire format.
1 parent c1a7c12 commit 1750000

File tree

4 files changed

+89
-73
lines changed

4 files changed

+89
-73
lines changed

.github/workflows/test.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ jobs:
3636
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
3737
with:
3838
go-version: ${{ needs.get-go-version.outputs.go-version }}
39-
- run: go test -v ./codec
39+
- run: go test -v ./codec
40+
- run: go test -tags codecgen.exec -v ./codec

codec/doc.go

Lines changed: 72 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,10 @@ Rich Feature Set includes:
6161
- Drop-in replacement for encoding/json. `json:` key in struct tag supported.
6262
- Provides a RPC Server and Client Codec for net/rpc communication protocol.
6363
- Handle unique idiosyncrasies of codecs e.g.
64-
- For messagepack, configure how ambiguities in handling raw bytes are resolved
65-
- For messagepack, provide rpc server/client codec to support
66-
msgpack-rpc protocol defined at:
67-
https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
68-
64+
- For messagepack, configure how ambiguities in handling raw bytes are resolved
65+
- For messagepack, provide rpc server/client codec to support
66+
msgpack-rpc protocol defined at:
67+
https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
6968
7069
## Extension Support
7170
@@ -75,19 +74,20 @@ custom types.
7574
There are no restrictions on what the custom type can be. Some examples:
7675
7776
```go
78-
type BisSet []int
79-
type BitSet64 uint64
80-
type UUID string
81-
type MyStructWithUnexportedFields struct { a int; b bool; c []int; }
82-
type GifImage struct { ... }
77+
78+
type BisSet []int
79+
type BitSet64 uint64
80+
type UUID string
81+
type MyStructWithUnexportedFields struct { a int; b bool; c []int; }
82+
type GifImage struct { ... }
83+
8384
```
8485
8586
As an illustration, MyStructWithUnexportedFields would normally be encoded
8687
as an empty map because it has no exported fields, while UUID would be
8788
encoded as a string. However, with extension support, you can encode any of
8889
these however you like.
8990
90-
9191
## Custom Encoding and Decoding
9292
9393
This package maintains symmetry in the encoding and decoding halfs. We
@@ -108,13 +108,11 @@ Consequently, if a type only defines one-half of the symmetry (e.g. it
108108
implements UnmarshalJSON() but not MarshalJSON() ), then that type doesn't
109109
satisfy the check and we will continue walking down the decision tree.
110110
111-
112111
## RPC
113112
114113
RPC Client and Server Codecs are implemented, so the codecs can be used with
115114
the standard net/rpc package.
116115
117-
118116
## Usage
119117
120118
The Handle is SAFE for concurrent READ, but NOT SAFE for concurrent
@@ -135,85 +133,93 @@ Consequently, the usage model is basically:
135133
Sample usage model:
136134
137135
```go
138-
// create and configure Handle
139-
var (
140-
mh codec.MsgpackHandle
141-
)
142-
143-
mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
144-
145-
// configure extensions
146-
// e.g. for msgpack, define functions and enable Time support for tag 1
147-
mh.SetExt(reflect.TypeOf(time.Time{}), 1, myExt)
148-
149-
// create and use decoder/encoder
150-
var (
151-
r io.Reader
152-
w io.Writer
153-
b []byte
154-
h = &mh
155-
)
156-
157-
dec = codec.NewDecoder(r, h)
158-
dec = codec.NewDecoderBytes(b, h)
159-
err = dec.Decode(&v)
160-
161-
enc = codec.NewEncoder(w, h)
162-
enc = codec.NewEncoderBytes(&b, h)
163-
err = enc.Encode(v)
164-
165-
//RPC Server
166-
go func() {
167-
for {
168-
conn, err := listener.Accept()
169-
rpcCodec := codec.GoRpc.ServerCodec(conn, h)
170-
//OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h)
171-
rpc.ServeCodec(rpcCodec)
172-
}
173-
}()
174-
175-
//RPC Communication (client side)
176-
conn, err = net.Dial("tcp", "localhost:5555")
177-
rpcCodec := codec.GoRpc.ClientCodec(conn, h)
178-
//OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h)
179-
client := rpc.NewClientWithCodec(rpcCodec)
180-
```
181136
137+
// create and configure Handle
138+
var (
139+
mh codec.MsgpackHandle
140+
)
141+
142+
mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
143+
144+
// configure extensions
145+
// e.g. for msgpack, define functions and enable Time support for tag 1
146+
mh.SetExt(reflect.TypeOf(time.Time{}), 1, myExt)
147+
148+
// create and use decoder/encoder
149+
var (
150+
r io.Reader
151+
w io.Writer
152+
b []byte
153+
h = &mh
154+
)
155+
156+
dec = codec.NewDecoder(r, h)
157+
dec = codec.NewDecoderBytes(b, h)
158+
err = dec.Decode(&v)
159+
160+
enc = codec.NewEncoder(w, h)
161+
enc = codec.NewEncoderBytes(&b, h)
162+
err = enc.Encode(v)
163+
164+
//RPC Server
165+
go func() {
166+
for {
167+
conn, err := listener.Accept()
168+
rpcCodec := codec.GoRpc.ServerCodec(conn, h)
169+
//OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h)
170+
rpc.ServeCodec(rpcCodec)
171+
}
172+
}()
173+
174+
//RPC Communication (client side)
175+
conn, err = net.Dial("tcp", "localhost:5555")
176+
rpcCodec := codec.GoRpc.ClientCodec(conn, h)
177+
//OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h)
178+
client := rpc.NewClientWithCodec(rpcCodec)
179+
180+
```
182181
183182
## Running Tests
184183
185184
To run tests, use the following:
186185
187186
```
188-
go test
187+
188+
go test
189+
189190
```
190191
191192
To run the full suite of tests, use the following:
192193
193194
```
194-
go test -tags alltests -run Suite
195+
196+
go test -tags alltests -run Suite
197+
195198
```
196199
197200
You can run the tag 'safe' to run tests or build in safe mode. e.g.
198201
199202
```
200-
go test -tags safe -run Json
201-
go test -tags "alltests safe" -run Suite
203+
204+
go test -tags safe -run Json
205+
go test -tags "alltests safe" -run Suite
206+
202207
```
203208
204209
## Running Benchmarks
205210
206211
```
207-
cd codec/bench
208-
./bench.sh -d
209-
./bench.sh -c
210-
./bench.sh -s
211-
go test -bench . -benchmem -benchtime 1s
212+
213+
cd codec/bench
214+
./bench.sh -d
215+
./bench.sh -c
216+
./bench.sh -s
217+
go test -bench . -benchmem -benchtime 1s
218+
212219
```
213220
214221
Please see http://github.com/hashicorp/go-codec-bench .
215222
216-
217223
## Caveats
218224
219225
Struct fields matching the following are ignored during encoding and

codec/gen.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
package codec
88

99
import (
10-
"encoding/base64"
10+
"encoding/base32"
1111
"errors"
1212
"fmt"
1313
"io"
@@ -132,7 +132,8 @@ var (
132132
errGenAllTypesSamePkg = errors.New("All types must be in the same package")
133133
errGenExpectArrayOrMap = errors.New("unexpected type. Expecting array/map/slice")
134134

135-
genBase64enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789__")
135+
// base64 requires 64 unique characters in Go 1.22+, which is not possible for Go identifiers.
136+
genBase32enc = base32.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
136137
genQNameRegex = regexp.MustCompile(`[A-Za-z_.]+`)
137138
)
138139

@@ -1829,7 +1830,7 @@ func genMethodNameT(t reflect.Type, tRef reflect.Type) (n string) {
18291830
} else {
18301831
// best way to get the package name inclusive
18311832
// return ptrPfx + strings.Replace(tstr, ".", "_", 1000)
1832-
// return ptrPfx + genBase64enc.EncodeToString([]byte(tstr))
1833+
// return ptrPfx + genBase32enc.EncodeToString([]byte(tstr))
18331834
if t.Name() != "" && genQNameRegex.MatchString(tstr) {
18341835
return ptrPfx + strings.Replace(tstr, ".", "_", 1000)
18351836
} else {
@@ -1840,12 +1841,12 @@ func genMethodNameT(t reflect.Type, tRef reflect.Type) (n string) {
18401841
}
18411842
}
18421843

1843-
// genCustomNameForType base64encodes the t.String() value in such a way
1844+
// genCustomNameForType base32encodes the t.String() value in such a way
18441845
// that it can be used within a function name.
18451846
func genCustomTypeName(tstr string) string {
1846-
len2 := genBase64enc.EncodedLen(len(tstr))
1847+
len2 := genBase32enc.EncodedLen(len(tstr))
18471848
bufx := make([]byte, len2)
1848-
genBase64enc.Encode(bufx, []byte(tstr))
1849+
genBase32enc.Encode(bufx, []byte(tstr))
18491850
for i := len2 - 1; i >= 0; i-- {
18501851
if bufx[i] == '=' {
18511852
len2--

codec/gen_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//go:build codecgen.exec
2+
3+
package codec
4+
5+
import "testing"
6+
7+
// TestDontPanic checks that the code compiles with this tag.
8+
func TestDontPanic(t *testing.T) {}

0 commit comments

Comments
 (0)