Starting the server

First, just the WorkerPool structure and explaining the channels and waitgroup
This commit is contained in:
David Vennik
2022-04-28 11:20:15 +03:00
parent e1f134b711
commit 1abeab533f
12 changed files with 1767 additions and 0 deletions

111
README.md
View File

@@ -45,6 +45,12 @@
- [Enabling logging in the tests](#enabling-logging-in-the-tests)
- [The Go tool recursive descent notation](#the-go-tool-recursive-descent-notation)
- [Running the tests with logging and recursive descent](#running-the-tests-with-logging-and-recursive-descent)
- [Actually testing the Encoder and Decoder](#actually-testing-the-encoder-and-decoder)
- [Step 6 Creating a Server](#step-6-creating-a-server)
- [The Logger](#the-logger)
- [Implementing the worker pool](#implementing-the-worker-pool)
- [About Channels](#about-channels)
- [About Waitgroups](#about-waitgroups)
## Teaching Golang via building a Human Readable Binary Transcription Encoding Framework
@@ -1695,3 +1701,108 @@ In some cases, it makes sense to test multiple failure modes, but for the sake o
Writing good tests is a bit of a black art, and the task gets more and more complicated the more complex the algorithms.
----
### [Step 6](steps/step6) Creating a Server
Just to clarify an important distinction, and another aspect of writing modular code, we are not writing an *executable* that you can spawn from the commandline or within scripts or Dockerfiles. We are writing an *in process* service that can be started by any Go application, the actual application that does this will be done later.
The reason why we separate the two things is because for tests, we want to spawn a server and also in parallel run a client to query it.
Because our service uses gRPC/Protobuf for its messages, for the reason that it is binary, and supported by almost every major langage, we will put the service inside `pkg/grpc/server`.
#### The Logger
I will be repetitive about this, because I want to reinforce the point that debugging by log printing is *not* "unprofessional" or any insults that certain kinds of "programmers" would have you believe. Ignore these fools, they have amnesia about their own learning process and want to be elite forever and not have competition.
In the olden days, when code was single threaded and largely procedural, ok, you didn't really need to do this so much because you could just step through the code line by line, set breakpoints, and immediately know where a failure was because the debugger would tell you.
This can probably be said to be true even of languages with heavy threading systems like C++/Boost and Rust/Tokio in that you can rely on the debugger to tell you where your errors are.
But this simply is not true of Go multithreaded applications.
The service we are about to explain how to build runs, by default in the binary service daemon we will show you how to build later, 8 concurrent threads to process requests, which run in parallel with a watcher thread that waits for shutdown, using process signals to ensure a clean shutdown when that signal occurs.
This is just a simple application, and has 9 concurrent threads operating. A much more usual situation with writing servers and applications in Go is that there could be 10 or 20 different pieces of code running concurrently with potentially thousands of concurrent threads, and the timing of their interactions can be crucial to the functionality of the application.
Concurrent programming just cannot be properly debugged only with a debugger, unless that debugger is recording the state of EVERYTHING. Because that is also not practical for performance reasons, log prints let you record application state only when you actually need it to be logged.
The standard logging library doesn't account for the production versus debug mode logging either, and makes subsystems being enabled explicitly without others, there needs to be better tools, and I have written some such tools but we will not cover this here. For now, just to make sure you know why you should use this little source code location configuration on the standard logger when you are learning, at least.
The only practical way to debug such an application is with logging, with decent precision timestamps so you can correctly play back a post mortem in slow motion to see what actually went wrong, and where.
Put this file in `pkg/grpc/server/log.go` first:
```go
package server
import (
logg "log"
"os"
)
var log = logg.New(os.Stderr, "b32 ", logg.Llongfile)
```
It is not generally told this way in the Go community, but this was essential to my own progress as a Go programmer, and I think that if it works this way for me, it probably will help at least a substantial number of other developers starting out on their journey who may not have the luxury of the amount of time I was able to finagle in my learning process as an intern, during which time my living conditions were abominable.
You can gloss over this, if you want to, but you will come back to it if you intend to make any real progress in this business.
This little file makes sure that you can put log prints in anywhere in your code, and when they are printed out, easily jump into the codebase exactly where the problem comes up.
#### Implementing the worker pool
Continuing, as always, with steps that build upon the previous steps and not leaving you with code that would not compile due to undefined symbols, the very first thing you need to put in the package is the `Transcriber`, which is the name we will give to our worker pool.
So, create a new file `pkg/grpc/server/workerpool.go` and into it we will first put the `Transcriber` data structure, which manages the worker pool:
```go
package server
import (
"github.com/quanterall/kitchensink/pkg/based32"
"github.com/quanterall/kitchensink/pkg/proto"
"go.uber.org/atomic"
"sync"
)
// Transcriber is a multithreaded worker pool for performing transcription
// encode and decode requests.
type Transcriber struct {
stop chan struct{}
encode []chan *proto.EncodeRequest
decode []chan *proto.DecodeRequest
encodeRes []chan proto.EncodeRes
decodeRes []chan proto.DecodeRes
encCallCount, decCallCount *atomic.Uint32
workers uint32
wait sync.WaitGroup
}
```
There is a few explanations that need to be made to start with.
#### About Channels
First of all, channels themselves, which have the type `chan` as a prefix.
Channels are technically known as an Atomic FIFO Queue. What this means is that their operations are atomic, meaning either they happen, or they don't, and no other code sees an in-between state, so they can be safely used concurrently between two or more goroutines.
They are FIFO, meaning First In First Out, in that what comes out, is what was put in first.
The queues can be zero length, or can have multiple buffers, which makes them function more like a queue than a simple signalling or message passing mechanism. Generally one only adds buffers to them to account for latency in processing or to maintain ongoing processing without blocking on the process when it is in continuous operation.
`stop` is often colloquially referred to as a "quit channel" or a "breaker". It is a breaker because with all `chan` types in Go, when you `close(channelName)` them, any time you read from them with `<-channelName` you will get `nil` forever, until the end of the program.
We use the type `struct {}` which is an empty struct, because this is the smallest possible type of data it can be, it is zero bytes! So such a channel can only send simple signals, namely "something", which is actually nothing, it can be used like a momentary switch such as the power button on a computer, or the trigger on a gun, it simply sends a signal to do something to whatever it is connected to.
So, such channels have to be single purpose. If you need to send only one signal to stop, you close the channel, if you want to use the channel to send multiple triggers, you send an empty struct value to it. You *could* distinguish between the two and recognise the `nil` separately in a the "trigger" channel mode, but it's just not worth it to combine the two and having a separate "quit" channel to "trigger" channels makes for easier reading, as every channel receive has to distinguish between `struct {}{}` and `nil` every time, which is a waste of time.
For each API call we have a pair of channels created in this structure, one for the request, and one for the response. It is possible to embed the response in the requests as well, so the return channel is sent in the request, but this costs more in initiation and makes more sense for a lower frequency request service than the one we are making. The channel is already initialised before any requests are made, so the responses will return immediately and do not have to be initialised by the caller.
The last point, is that each of the 4 channels we have for our two API methods call and response are slices. This allows us to define how many threads we will warm up when starting up the service to handle the expected workload. The reason for making this pre-allocated is again, response latency. This may not be so important in some types of applications, but in all cases, the result of pre-allocation is better performance and a lack of unexpected unbounded resource allocation, commonly known as a "resource leak" which can extend to not just variables but also channels and goroutines, both of which have a small but nonzero cost in allocation time and memory utilisation until they are freed by the garbage collector.
#### About Waitgroups
The last element of the struct is a `sync.Waitgroup`. This is an atomic counter which can be used to keep track of the number of threads that are running, and allows you to write code that holds things open until all of the wait group are `Done` when shutting down.

19
steps/step6/go.mod Normal file
View File

@@ -0,0 +1,19 @@
module github.com/quanterall/kitchensink/steps/step6
go 1.18
require (
github.com/quanterall/kitchensink v0.0.0-20220426120150-4a59688929c1
google.golang.org/grpc v1.45.0
google.golang.org/protobuf v1.28.0
lukechampine.com/blake3 v1.1.7
)
require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
)

130
steps/step6/go.sum Normal file
View File

@@ -0,0 +1,130 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quanterall/kitchensink v0.0.0-20220426120150-4a59688929c1 h1:zdE1Rgh1yB18EeB+UnhMojpKo9WqZOHTBYQ6qxyPDhc=
github.com/quanterall/kitchensink v0.0.0-20220426120150-4a59688929c1/go.mod h1:X2eXt4Ny529q5AZIaKeX7p6tFdr4OxUKJCcRYQ86RGs=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=

View File

@@ -0,0 +1,322 @@
// Package based32 provides a simplified variant of the standard
// Bech32 human readable binary codec
//
// This codec simplifies the padding algorithm compared to the Bech32 standard
// BIP 0173 by performing all of the check validation with the decoded bits
// instead of separating the pads of each segment.
//
// The format will be entirely created by the use of the standard library
// base32, which may or may not result in the same thing (we are teaching Go
// here, not cryptocurrency, and the extra rules used by the Bech32 standard
// complicate this tutorial unnecessarily - and, Go Uber Alles :)
package based32
import (
"encoding/base32"
"github.com/quanterall/kitchensink"
"github.com/quanterall/kitchensink/pkg/proto"
"lukechampine.com/blake3"
"strings"
)
// charset is the set of characters used in the data section of bech32 strings.
// Note that this is ordered, such that for a given charset[i], i is the binary
// value of the character.
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
// Codec provides the encoder/decoder implementation created by makeCodec.
//
// This variable is sometimes called a "Singleton" in other languages, and in Go
// it is a thing that should be avoided unless the value is not a constant and
// an initialization process is required.
//
// Variable declarations like this are executed before init() functions and are
// for cases such as this, as the import of this package means the programmer
// intends to use this codec, usually, as otherwise they would be creating a new
// implementation from the struct type or for the interface.
//
// In general, an init() function is better avoided, and singletons also, better
// avoided, unless it makes sense in the context of the package as this is this
// initialization adds to startup delay for an application, so consider
// carefully before using these or init().
var Codec = makeCodec(
"Base32Check",
charset,
"QNTRL",
)
func getCheckLen(length int) (checkLen int) {
// In order to provide a minimum of 1 byte of check to the output, while
// avoiding the encoder adding padding characters (default is '=') the
// length of the encoded bytes must be rounded to the nearest multiple of 5,
// adding 5 if it is already a multiple of 5 (5 bytes is 40 bits which
// encodes as 8 base32 characters).
//
// The first byte of the encoded data contains the check length, as this
// formula varies depending on the length of the data, so it needs to be
// encoded into the format in the beginning as it can't go at the end. So
// the check length is one byte less than this formula indicates.
//
// This is a significant divergence from the methods used for these encoders
// because in this tutorial we are not only aiming to produce human readable
// transcription codes for just transaction hashes (usually 256bit/32 byte)
// and addresses (usually 160bit/20byte) but a general formula that could
// encode any binary data length, but presumably it would be likely no more
// than 512 bits of data for a double length hash, since such a code would
// take at least a couple of minutes to correctly transcribe.
//
// Though a Go programmer may never do a lot of this kind of algorithm
// design, it is here especially for those who are inclined towards this
// kind of low level encoding, which is part of any data encoding for wire,
// storage, for graphic and audio encoding formats, and things like writing
// GUIs.
//
// The following formula ensures that there is at least 1 check byte, up to
// 4
//
// we add two to the length before modulus, as there must be 1 byte for
// check length and 1 byte of check
lengthMod := (2 + length) % 5
// The modulus is subtracted from 5 to produce the complement required to
// make the correct number of bytes of total data, plus 1 to account for the
// minimum length of 1.
checkLen = 5 - lengthMod + 1
return checkLen
}
// getCutPoint is made into a function because it is needed more than once.
func getCutPoint(length, checkLen int) int {
return length - checkLen - 1
}
// makeCodec generates our custom codec as above, into the exported Codec
// variable
//
// Here we demonstrate the use of closures. In this case, it is an
// initialization, but it can also be used in dynamic generation code, or to use
// the 'builder' pattern to construct larger algorithms out of small modular
// parts.
func makeCodec(
name string,
cs string,
hrp string,
) (cdc *codec.Codec) {
// Create the codec.Codec struct and put its pointer in the return variable.
cdc = &codec.Codec{
Name: name,
Charset: cs,
HRP: hrp,
}
// We need to create the check creation functions first
cdc.MakeCheck = func(input []byte, checkLen int) (output []byte) {
// We use the Blake3 256 bit hash because it is nearly as fast as CRC32
// but less complicated to use due to the 32 bit integer conversions to
// bytes required to use the CRC32 algorithm.
checkArray := blake3.Sum256(input)
// This truncates the blake3 hash to the prescribed check length
return checkArray[:checkLen]
}
// Create a base32.Encoding from the provided charset.
enc := base32.NewEncoding(cdc.Charset)
cdc.Encoder = func(input []byte) (output string, err error) {
if len(input) < 1 {
// Unfortunately there is a minor bug in the Go protobuf/grpc
// generator that does not set the type of the errors to Error,
// which is an alias of int32. Thus here we have to cast it to int32
// to retrieve the map entry containing the error name.
//
// You can see the error in ../proto/based32.pb.go which is what is
// generated by protoc-gen-go.
err = proto.Error_ZERO_LENGTH
return
}
// The check length depends on the modulus of the length of the data is
// order to avoid padding.
checkLen := getCheckLen(len(input))
// The output is longer than the input, so we create a new buffer.
outputBytes := make([]byte, len(input)+checkLen+1)
// Add the check length byte to the front
outputBytes[0] = byte(checkLen)
// Then copy the input bytes for beginning segment.
copy(outputBytes[1:len(input)+1], input)
// Then copy the check to the end of the input.
copy(outputBytes[len(input)+1:], cdc.MakeCheck(input, checkLen))
// Create the encoding for the output.
outputString := enc.EncodeToString(outputBytes)
// We can omit the first character of the encoding because the length
// prefix never uses the first 5 bits of the first byte, and add it back
// for the decoder later.
trimmedString := outputString[1:]
// Prefix the output with the Human Readable Part and append the
// encoded string version of the provided bytes.
output = cdc.HRP + trimmedString
return
}
cdc.Check = func(input []byte) (err error) {
// We must do this check or the next statement will cause a bounds check
// panic. Note that zero length and nil slices are different, but have
// the same effect in this case, so both must be checked.
switch {
case len(input) < 1:
err = proto.Error_ZERO_LENGTH
return
case input == nil:
err = proto.Error_NIL_SLICE
return
}
// The check length is encoded into the first byte in order to ensure
// the data is cut correctly to perform the integrity check.
checkLen := int(input[0])
// Ensure there is at enough bytes in the input to run a check on
if len(input) < checkLen+1 {
err = proto.Error_CHECK_TOO_SHORT
return
}
// Find the index to cut the input to find the checksum value. We need
// this same value twice so it must be made into a variable.
cutPoint := getCutPoint(len(input), checkLen)
// Here is an example of a multiple assignment and more use of the
// slicing operator.
payload, checksum := input[1:cutPoint], string(input[cutPoint:])
// A checksum is checked in all cases by taking the data received, and
// applying the checksum generation function, and then comparing the
// checksum to the one attached to the received data with checksum
// present.
//
// Note: The casting to string above and here. This makes a copy to the
// immutable string, which is not optimal for large byte slices, but for
// this short check value, it is a cheap operation on the stack, and an
// illustration of the interchangeability of []byte and string, with the
// distinction of the availability of a comparison operator for the
// string that isn't present for []byte, so for such cases this
// conversion is a shortcut method to compare byte slices.
computedChecksum := string(cdc.MakeCheck(payload, checkLen))
// Here we assign to the return variable the result of the comparison.
// by doing this instead of using an if and returns, the meaning of the
// comparison is more clear by the use of the return value's name.
valid := checksum != computedChecksum
if !valid {
err = proto.Error_CHECK_FAILED
}
return
}
cdc.Decoder = func(input string) (output []byte, err error) {
// Other than for human identification, the HRP is also a validity
// check, so if the string prefix is wrong, the entire value is wrong
// and won't decode as it is expected.
if !strings.HasPrefix(input, cdc.HRP) {
log.Printf("Provided string has incorrect human readable part:"+
"found '%s' expected '%s'", input[:len(cdc.HRP)], cdc.HRP,
)
err = proto.Error_INCORRECT_HUMAN_READABLE_PART
return
}
// Cut the HRP off the beginning to get the content, add the initial
// zeroed 5 bits with a 'q' character.
//
// Be aware the input string will be copied to create the []byte
// version. Also, because the input bytes are always zero for the first
// 5 most significant bits, we must re-add the zero at the front (q)
// before feeding it to the decoder.
input = "q" + input[len(cdc.HRP):]
// The length of the base32 string refers to 5 bits per slice index
// position, so the correct size of the output bytes, which are 8 bytes
// per slice index position, is found with the following simple integer
// math calculation.
//
// This allocation needs to be made first as the base32 Decode function
// does not do this allocation automatically and it would be wasteful to
// not compute it precisely, when the calculation is so simple.
//
// If this allocation is omitted, the decoder will panic due to bounds
// check error. A nil slice is equivalent to a zero length slice and
// gives a bounds check error, but in fact, the slice has no data at
// all. Yes, the panic message is lies:
//
// panic: runtime error: index out of range [4] with length 0
//
// If this assignment isn't made, by default, output is nil, not
// []byte{} so this panic message is deceptive.
data := make([]byte, len(input)*5/8)
var writtenBytes int
writtenBytes, err = enc.Decode(data, []byte(input))
if err != nil {
log.Println(err)
return
}
// The first byte signifies the length of the check at the end
checkLen := int(data[0])
if writtenBytes < checkLen+1 {
err = proto.Error_CHECK_TOO_SHORT
return
}
// Assigning the result of the check here as if true the resulting
// decoded bytes still need to be trimmed of the check value (keeping
// things cleanly separated between the check and decode function.
err = cdc.Check(data)
// There is no point in doing any more if the check fails, as per the
// contract specified in the interface definition codecer.Codecer
if err != nil {
return
}
// Slice off the check length prefix, and the check bytes to return the
// valid input bytes.
output = data[1:getCutPoint(len(data)+1, checkLen)]
// If we got to here, the decode was successful.
return
}
// We return the value explicitly to be nice to readers as the function is
// not a short and simple one.
return cdc
}

View File

@@ -0,0 +1,183 @@
package based32
import (
"encoding/binary"
"encoding/hex"
"fmt"
"lukechampine.com/blake3"
"math/rand"
"testing"
)
const (
seed = 1234567890
numKeys = 32
)
func TestCodec(t *testing.T) {
// Generate 10 pseudorandom 64 bit values. We do this here rather than
// pre-generating this separately as ultimately it is the same thing, the
// same seed produces the same series of pseudorandom values, and the hashes
// of these values are deterministic.
rand.Seed(seed)
seeds := make([]uint64, numKeys)
for i := range seeds {
seeds[i] = rand.Uint64()
}
// Convert the uint64 values to 8 byte long slices for the hash function.
seedBytes := make([][]byte, numKeys)
for i := range seedBytes {
seedBytes[i] = make([]byte, 8)
binary.LittleEndian.PutUint64(seedBytes[i], seeds[i])
}
// Generate hashes from the seeds
hashedSeeds := make([][]byte, numKeys)
// Uncomment lines relating to this variable to regenerate expected data
// that will log to console during test run.
generated := "\nexpected := []string{\n"
for i := range hashedSeeds {
hashed := blake3.Sum256(seedBytes[i])
hashedSeeds[i] = hashed[:]
generated += fmt.Sprintf("\t\"%x\",\n", hashedSeeds[i])
}
generated += "}\n"
t.Log(generated)
expected := []string{
"7bf4667ea06fe57687a7c0c8aae869db103745a3d8c5dce5bf2fc6206d3b97e4",
"84c0ee2f49bfb26f48a78c9d048bb309a006db4c7991ebe4dd6dc3f2cc1067cd",
"206a953c4ba4f79ffe3d3a452214f19cb63e2895866cc27c7cf6a4ec8fe5a7a6",
"35d64c401829c621624fe9d4f134c24ae909ecf4f07ec4540ffd58911f427d03",
"573d6989a2c2994447b4669ae6931f12e73c8744e9f65451918a1f3d8cd39aa1",
"2b08aea58cc1d680de0e7acadc027ebe601f923ff9d5536c6f73e2559a1b6b14",
"bcc3256005da59b06f69b4c1cc62c89af041f8cd5ad79b81351fbfbbaf2cc60f",
"42a0f7b9aef1cdc0b3f2a1fd0fb547fb76e5eb50f4f5a6646ee8929fdfef5db7",
"50e1cb9f5f8d5325e18298faeeea7fd93d83e3bd518299e7150c1f548c11ddc8",
"22a70a74ccfd61a47576150f968039cfeb33143ec549dfeb6c95afc8a6d3d75a",
"cd1b21bd745e122f0db1f5ca4e4cbe0bace8439112d519e5b9c0a44a2648a61a",
"9ec4bd670b053e722d9d5bc3c2aca4a1d64858ef53c9b58a9222081dc1eeb017",
"d85713c898f2fc95d282b58a0ea475c1b1726f8be44d2f17c3c675ce688e1563",
"875baee7e9a372fe3bad1fbf0e2e4119038c1ed53302758fc5164b012e927766",
"7de94ca668463db890478d8ba3bbed35eb7666ac0b8b4e2cb808c1cbb754576f",
"159469150dc41ebd2c2bfafb84aef221769699013b70068444296169dc9e93be",
"a90a104ea470df61d337c51589b520454acbd05ef5bbe7d2a8285043a222bec9",
"a835de5206f6dbef6a2cb3da66ffb99a19bfa4e005208ffdb316ce880132297e",
"f6a09e8f41231febd1b25c52cb73ea438ac803db77d5549db4e15a32e804de9f",
"074c59cce7783042cc6941c849206582ecc43028d1576d00e02d95b1e669bf7a",
"203c3566724c229b570f33be994cd6094e1a64f3df552f1390b4c2adc7e36d6d",
"efec32d52a17ed75ad5a486ba621e0f47f61e4e60557129fce728a1bb63208fd",
"9cc2962fc62fe40f6197a4fb81356717fd57b4c988641bca3a9d45efde893894",
"2adf211300632bb5f650202bf128ba9187ec2c6c738431dc396d93b8f62bd590",
"0782aade40d0ae7a293bfb67016466682d858b5226eaaa8df2f2104fa6c408c3",
"d011ad5550f3f03caa469fa233f553721e6af84f1341d256cefe052d85397637",
"83deb64f5c134d108e8b99c8a196b8d04228acfc810c33711d975400fa731508",
"d9a4b19142d015fd541f50f18f41b7e9738a30c59a3e914b4d4d1556c75786f2",
"3e05940b76735ea114db8b037dece53090765510c9c4e55a0be18cb8aef754fa",
"41f43119041dd1f3a250f54768ce904808cd0d7bb7b37697803ed2940c39a555",
"a2c2d7cb980c2b57c8fdfae55cf4c6040eaf8163b21072877e5e57349388d59c",
"02155c589e5bd89ce806a33c1841fe1e157171222701d515263acd0254208a39",
}
for i := range hashedSeeds {
if expected[i] != hex.EncodeToString(hashedSeeds[i]) {
t.Log("failed", i, "expected", expected[1], "found", hashedSeeds)
t.FailNow()
}
}
encodedStr := []string{
"QNTRLfalgen75ph72a585lqv32hgd8d3qd6950vvth89huhuvgrd8wt7ftet",
"QNTRLwzvpm30fxlmym6g57xf6pytkvy6qpkmf3uer6lym4ku8ukvzpnckqs6",
"QNTRLssx49fufwj008l785ay2gs57xwtv03gjkrxesnu0nm2fmy0uhmerj80",
"QNTRL56avnzqrq5uvgtzfl5afuf5cf9wjz0v7nc8a3z5pl743yglgjs6nypp",
"QNTRLetn66vf5tpfj3z8k3nf4e5nrufww0y8gn5lv4z3jx9p70vwrzchx7pf",
"QNTRLg4s3t493nqadqx7peav4hqz06lxq8uj8lua25mvdae7y4v6rd43fmcv",
"QNTRLw7vxftqqhd9nvr0dx6vrnrzezd0qs0ce4dd0xupx50mlwa09nr8hhvc",
"QNTRL3p2paae4mcums9n72sl6ra4glahde0t2r60tfnydm5f987lalykzuqe",
"QNTRL4gwrjult7x4xf0ps2v04mh20lvnmqlrh4gc9x08z5xp74yva49qf29d",
"QNTRLc32wzn5en7krfr4wc2sl95q8887kvc58mz5nhltdj26ljxy33yxs7px",
"QNTRLtx3kgdaw30pytcdk86u5njvhc96e6zrjyfd2x09h8q2gj3xfznp4l5y",
"QNTRLw0vf0t8pvznuu3dn4du8s4v5jsavjzcaafundv2jg3qs8wpa6czu2kz",
"QNTRLnv9wy7gnre0e9wjs26c5r4ywhqmzun030jy6tchc0r8tnng3e6u5pg6",
"QNTRLkr4hth8ax3h9l3m450m7r3wgyvs8rq765esyav0c5tykqfwr2u7zmfp",
"QNTRLe77jn9xdprrmwysg7xchgama567kanx4s9ckn3vhqyvrjam6x9h7qmx",
"QNTRLg2eg6g4phzpa0fv90a0hp9w7gshd95eqyahqp5ygs5kz6wun6fmukle",
"QNTRLw5s5yzw53cd7cwnxlz3tzd4ypz54j7stm6mhe7j4q59qsazy2lfqq35",
"QNTRLj5rthjjqmmdhmm29jea5ehlhxdpn0ayuqzjprlakvtvazqpxt0zdxtv",
"QNTRLhm2p850gy33l673kfw99jmnafpc4jqrmdma24yakns45vhg0sfcsrrp",
"QNTRLcr5ckwvuaurqskvd9qusjfqvkpwe3ps9rg4wmgquqketvvex8jy6alw",
"QNTRLgsrcdtxwfxz9x6hpuemax2v6cy5uxny70042tcnjz6v9tw8udkk6rkl",
"QNTRL0h7cvk49gt76addtfyxhf3pur687c0yucz4wy5leeeg5xakxgywasu3",
"QNTRLjwv9930cch7grmpj7j0hqf4vutl64a5exyxgx7282w5tm7738hap8u7",
"QNTRL54d7ggnqp3jhd0k2qszhufgh2gc0mpvd3ecgvwu89ke8w8kcv3ksyma",
"QNTRLcrc92k7grg2u73f80akwqtyve5zmpvt2gnw425d7tepqnaz7jehh549",
"QNTRLtgprt242relq092g606yvl42depu6hcfuf5r5jkemlq2tv989mr0wng",
"QNTRLwpaadj0tsf56yyw3wvu3gvkhrgyy29vljqscvm3rkt4gq86wv2upf6r",
"QNTRLnv6fvv3gtgptl25rag0rr6pkl5h8z3sckdray2tf4x324k82a58c5kf",
"QNTRL5lqt9qtwee4agg5mw9sxl0vu5cfqaj4zryufe26p0scew9w9cgcnqj8",
"QNTRLeqlgvgeqswaruaz2r65w6xwjpyq3ngd0wmmxa5hsqld998rns3l4gfz",
"QNTRL23v947tnqxzk47glhaw2h85cczqatupvwepqu580e09wdyn3r2eeltr",
"QNTRLvpp2hzcneda388gq63ncxzplc0p2ut3ygnsr4g4ycav6qj5yz9g9lv8",
}
encoded := "\nencodedStr := []string{\n"
// Convert hashes to our base32 encoding format
for i := range hashedSeeds {
// Note that we are slicing off a number of bytes at the end according
// to the sequence number to get different check byte lengths from a
// uniform original data. As such, this will be accounted for in the
// check by truncating the same amount in the check (times two, for the
// hex encoding of the string).
encode, err := Codec.Encode(hashedSeeds[i][:len(hashedSeeds[i])-i%5])
if err != nil {
t.Fatal(err)
}
if encode != encodedStr[i] {
t.Fatalf(
"Decode failed, expected item %d '%s' got '%s'",
i, encodedStr[i], encode,
)
}
encoded += "\t\"" + encode + "\",\n"
}
encoded += "}\n"
t.Log(encoded)
// Next, decode the encodedStr above, which should be the output of the
// original generated seeds, with the index mod 5 truncations performed on
// each as was done to generate them.
for i := range encodedStr {
res, err := Codec.Decode(encodedStr[i])
if err != nil {
t.Fatalf("error: '%v'", err)
}
elen := len(expected[i])
etrimlen := 2 * (i % 5)
expectedHex := expected[i][:elen-etrimlen]
resHex := fmt.Sprintf("%x", res)
if resHex != expectedHex {
t.Fatalf(
"got: '%s' expected: '%s'",
resHex,
expectedHex,
)
}
}
}

View File

@@ -0,0 +1,8 @@
package based32
import (
logg "log"
"os"
)
var log = logg.New(os.Stderr, "based32 ", logg.Llongfile)

View File

@@ -0,0 +1,73 @@
// Package codecer is the interface definition for a Human Readable Binary
// Transcription Codec
//
// Interface definitions should be placed in separate packages to
// implementations so there is no risk of a circular dependency, which is not
// permitted in Go, because this kind of automated interpretation of programmer
// intent is the most expensive thing (time, processing, memory) that compilers
// do.
package codecer
// Codecer is the externally usable interface which provides a check for
// complete implementation as well as illustrating the use of interfaces in Go.
//
// It is an odd name but the idiom for interfaces is to describe it as a <thing
// it does>er - so if the interface is for a print function, it could be called
// Printer, if it finds an average, it could be called Averager, and in this
// case, the interface encodes and decodes, thus 'codec' and the noun forming
// suffix -er. Encoder is useless without a Decoder so neither name really makes
// sense for the interface, and Translator implies linguistic restructuring.
//
// It is helpful to those who must work with your code after or with you to give
// meaningful names, and it is idiomatic in Go programming to make meaningful
// names, so don't be afraid to spend a little time when writing Go code with a
// thesaurus and dictionary. *Especially* if english is not your first language.
// Your colleagues will thank you and the inheritors of your code will be
// grateful that you spent the time.
//
// It may seem somewhat redundant in light of type definition, in the root of
// the repository, which exposes the exact same Encode and Decode functions, but
// the purpose of adding this is that this interface can be implemented without
// using the concrete Codec type above, should the programmer have a need to do
// so.
//
// The implementation only needs to implement these two functions and then
// whatever structure ties it together can be passed around without needing to
// know anything about its internal representations or implementation details.
//
// The purpose of interfaces in Go is exactly to eliminate dependencies on any
// concrete data types so the implementations can be changed without changing
// the consuming code.
//
// We are adding this interface in addition to the use of a struct and closure
// pattern mainly as illustration but also to make sure the student is aware of
// the implicit implementation recognition, the way to make the compile time
// check of implementation, and as an exercise for later, the student can create
// their own implementation by importing this package and use the provided
// implementation, in parallel with their own, or without it, which they can
// implement with an entirely separate and different data structure (which will
// be a struct, most likely, though it can be a slice of interface and be even
// subordinate to another structured variable like a slice of interface, or a
// map of interfaces. Then they can drop this interface in place of the built in
// one and see that they don't have to change the calling code.
//
// Note: though it is not officially recognised as idiomatic, it is the opinion
// of the author of this tutorial that the return values of interface function
// signatures should be named, as it makes no sense to force the developer to
// have to read through the implementation that *idiomatically* should accompany
// an interface, as by idiom, interface should be avoided unless there is more
// than one implementation.
type Codecer interface {
// Encode takes an arbitrary length byte input and returns the output as
// defined for the codec.
Encode(input []byte) (output string, err error)
// Decode takes an encoded string and returns if the encoding is valid and
// the value passes any check function defined for the type.
//
// If the check fails or the input is too short to have a check, false and
// nil is returned. This is the contract for this method that
// implementations should uphold.
Decode(input string) (output []byte, err error)
}

View File

@@ -0,0 +1,494 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.6.1
// source: pkg/proto/based32.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Error int32
const (
Error_ZERO_LENGTH Error = 0
Error_CHECK_FAILED Error = 1
Error_NIL_SLICE Error = 2
Error_CHECK_TOO_SHORT Error = 3
Error_INCORRECT_HUMAN_READABLE_PART Error = 4
)
// Enum value maps for Error.
var (
Error_name = map[int32]string{
0: "ZERO_LENGTH",
1: "CHECK_FAILED",
2: "NIL_SLICE",
3: "CHECK_TOO_SHORT",
4: "INCORRECT_HUMAN_READABLE_PART",
}
Error_value = map[string]int32{
"ZERO_LENGTH": 0,
"CHECK_FAILED": 1,
"NIL_SLICE": 2,
"CHECK_TOO_SHORT": 3,
"INCORRECT_HUMAN_READABLE_PART": 4,
}
)
func (x Error) Enum() *Error {
p := new(Error)
*p = x
return p
}
func (x Error) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Error) Descriptor() protoreflect.EnumDescriptor {
return file_pkg_proto_based32_proto_enumTypes[0].Descriptor()
}
func (Error) Type() protoreflect.EnumType {
return &file_pkg_proto_based32_proto_enumTypes[0]
}
func (x Error) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Error.Descriptor instead.
func (Error) EnumDescriptor() ([]byte, []int) {
return file_pkg_proto_based32_proto_rawDescGZIP(), []int{0}
}
type EncodeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Data []byte `protobuf:"bytes,1,opt,name=Data,proto3" json:"Data,omitempty"`
}
func (x *EncodeRequest) Reset() {
*x = EncodeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_proto_based32_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EncodeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EncodeRequest) ProtoMessage() {}
func (x *EncodeRequest) ProtoReflect() protoreflect.Message {
mi := &file_pkg_proto_based32_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EncodeRequest.ProtoReflect.Descriptor instead.
func (*EncodeRequest) Descriptor() ([]byte, []int) {
return file_pkg_proto_based32_proto_rawDescGZIP(), []int{0}
}
func (x *EncodeRequest) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type EncodeResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Encoded:
// *EncodeResponse_EncodedString
// *EncodeResponse_Error
Encoded isEncodeResponse_Encoded `protobuf_oneof:"Encoded"`
}
func (x *EncodeResponse) Reset() {
*x = EncodeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_proto_based32_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EncodeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EncodeResponse) ProtoMessage() {}
func (x *EncodeResponse) ProtoReflect() protoreflect.Message {
mi := &file_pkg_proto_based32_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EncodeResponse.ProtoReflect.Descriptor instead.
func (*EncodeResponse) Descriptor() ([]byte, []int) {
return file_pkg_proto_based32_proto_rawDescGZIP(), []int{1}
}
func (m *EncodeResponse) GetEncoded() isEncodeResponse_Encoded {
if m != nil {
return m.Encoded
}
return nil
}
func (x *EncodeResponse) GetEncodedString() string {
if x, ok := x.GetEncoded().(*EncodeResponse_EncodedString); ok {
return x.EncodedString
}
return ""
}
func (x *EncodeResponse) GetError() Error {
if x, ok := x.GetEncoded().(*EncodeResponse_Error); ok {
return x.Error
}
return Error_ZERO_LENGTH
}
type isEncodeResponse_Encoded interface {
isEncodeResponse_Encoded()
}
type EncodeResponse_EncodedString struct {
EncodedString string `protobuf:"bytes,1,opt,name=EncodedString,proto3,oneof"`
}
type EncodeResponse_Error struct {
Error Error `protobuf:"varint,2,opt,name=Error,proto3,enum=codec.Error,oneof"`
}
func (*EncodeResponse_EncodedString) isEncodeResponse_Encoded() {}
func (*EncodeResponse_Error) isEncodeResponse_Encoded() {}
type DecodeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
EncodedString string `protobuf:"bytes,1,opt,name=EncodedString,proto3" json:"EncodedString,omitempty"`
}
func (x *DecodeRequest) Reset() {
*x = DecodeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_proto_based32_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DecodeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DecodeRequest) ProtoMessage() {}
func (x *DecodeRequest) ProtoReflect() protoreflect.Message {
mi := &file_pkg_proto_based32_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DecodeRequest.ProtoReflect.Descriptor instead.
func (*DecodeRequest) Descriptor() ([]byte, []int) {
return file_pkg_proto_based32_proto_rawDescGZIP(), []int{2}
}
func (x *DecodeRequest) GetEncodedString() string {
if x != nil {
return x.EncodedString
}
return ""
}
type DecodeResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Decoded:
// *DecodeResponse_Data
// *DecodeResponse_Error
Decoded isDecodeResponse_Decoded `protobuf_oneof:"Decoded"`
}
func (x *DecodeResponse) Reset() {
*x = DecodeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_proto_based32_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DecodeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DecodeResponse) ProtoMessage() {}
func (x *DecodeResponse) ProtoReflect() protoreflect.Message {
mi := &file_pkg_proto_based32_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DecodeResponse.ProtoReflect.Descriptor instead.
func (*DecodeResponse) Descriptor() ([]byte, []int) {
return file_pkg_proto_based32_proto_rawDescGZIP(), []int{3}
}
func (m *DecodeResponse) GetDecoded() isDecodeResponse_Decoded {
if m != nil {
return m.Decoded
}
return nil
}
func (x *DecodeResponse) GetData() []byte {
if x, ok := x.GetDecoded().(*DecodeResponse_Data); ok {
return x.Data
}
return nil
}
func (x *DecodeResponse) GetError() Error {
if x, ok := x.GetDecoded().(*DecodeResponse_Error); ok {
return x.Error
}
return Error_ZERO_LENGTH
}
type isDecodeResponse_Decoded interface {
isDecodeResponse_Decoded()
}
type DecodeResponse_Data struct {
Data []byte `protobuf:"bytes,1,opt,name=Data,proto3,oneof"`
}
type DecodeResponse_Error struct {
Error Error `protobuf:"varint,2,opt,name=Error,proto3,enum=codec.Error,oneof"`
}
func (*DecodeResponse_Data) isDecodeResponse_Decoded() {}
func (*DecodeResponse_Error) isDecodeResponse_Decoded() {}
var File_pkg_proto_based32_proto protoreflect.FileDescriptor
var file_pkg_proto_based32_proto_rawDesc = []byte{
0x0a, 0x17, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x73, 0x65,
0x64, 0x33, 0x32, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63,
0x22, 0x23, 0x0a, 0x0d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x04, 0x44, 0x61, 0x74, 0x61, 0x22, 0x69, 0x0a, 0x0e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0d, 0x45, 0x6e, 0x63, 0x6f, 0x64,
0x65, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
0x52, 0x0d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12,
0x24, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05,
0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64,
0x22, 0x35, 0x0a, 0x0d, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x24, 0x0a, 0x0d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65,
0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x57, 0x0a, 0x0e, 0x44, 0x65, 0x63, 0x6f, 0x64,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x04, 0x44, 0x61, 0x74,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12,
0x24, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05,
0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64,
0x2a, 0x71, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x0f, 0x0a, 0x0b, 0x5a, 0x45, 0x52,
0x4f, 0x5f, 0x4c, 0x45, 0x4e, 0x47, 0x54, 0x48, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x48,
0x45, 0x43, 0x4b, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09,
0x4e, 0x49, 0x4c, 0x5f, 0x53, 0x4c, 0x49, 0x43, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x43,
0x48, 0x45, 0x43, 0x4b, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x48, 0x4f, 0x52, 0x54, 0x10, 0x03,
0x12, 0x21, 0x0a, 0x1d, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x48, 0x55,
0x4d, 0x41, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x50, 0x41, 0x52,
0x54, 0x10, 0x04, 0x32, 0x83, 0x01, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x06, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x2e,
0x63, 0x6f, 0x64, 0x65, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f,
0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x39,
0x0a, 0x06, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x63,
0x2e, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15,
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x2e, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x61,
0x6c, 0x6c, 0x2f, 0x6b, 0x69, 0x74, 0x63, 0x68, 0x65, 0x6e, 0x73, 0x69, 0x6e, 0x6b, 0x2f, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_pkg_proto_based32_proto_rawDescOnce sync.Once
file_pkg_proto_based32_proto_rawDescData = file_pkg_proto_based32_proto_rawDesc
)
func file_pkg_proto_based32_proto_rawDescGZIP() []byte {
file_pkg_proto_based32_proto_rawDescOnce.Do(func() {
file_pkg_proto_based32_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_proto_based32_proto_rawDescData)
})
return file_pkg_proto_based32_proto_rawDescData
}
var file_pkg_proto_based32_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_pkg_proto_based32_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_pkg_proto_based32_proto_goTypes = []interface{}{
(Error)(0), // 0: codec.Error
(*EncodeRequest)(nil), // 1: codec.EncodeRequest
(*EncodeResponse)(nil), // 2: codec.EncodeResponse
(*DecodeRequest)(nil), // 3: codec.DecodeRequest
(*DecodeResponse)(nil), // 4: codec.DecodeResponse
}
var file_pkg_proto_based32_proto_depIdxs = []int32{
0, // 0: codec.EncodeResponse.Error:type_name -> codec.Error
0, // 1: codec.DecodeResponse.Error:type_name -> codec.Error
1, // 2: codec.Transcriber.Encode:input_type -> codec.EncodeRequest
3, // 3: codec.Transcriber.Decode:input_type -> codec.DecodeRequest
2, // 4: codec.Transcriber.Encode:output_type -> codec.EncodeResponse
4, // 5: codec.Transcriber.Decode:output_type -> codec.DecodeResponse
4, // [4:6] is the sub-list for method output_type
2, // [2:4] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_pkg_proto_based32_proto_init() }
func file_pkg_proto_based32_proto_init() {
if File_pkg_proto_based32_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_pkg_proto_based32_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EncodeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_proto_based32_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EncodeResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_proto_based32_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DecodeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_proto_based32_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DecodeResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_pkg_proto_based32_proto_msgTypes[1].OneofWrappers = []interface{}{
(*EncodeResponse_EncodedString)(nil),
(*EncodeResponse_Error)(nil),
}
file_pkg_proto_based32_proto_msgTypes[3].OneofWrappers = []interface{}{
(*DecodeResponse_Data)(nil),
(*DecodeResponse_Error)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pkg_proto_based32_proto_rawDesc,
NumEnums: 1,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_pkg_proto_based32_proto_goTypes,
DependencyIndexes: file_pkg_proto_based32_proto_depIdxs,
EnumInfos: file_pkg_proto_based32_proto_enumTypes,
MessageInfos: file_pkg_proto_based32_proto_msgTypes,
}.Build()
File_pkg_proto_based32_proto = out.File
file_pkg_proto_based32_proto_rawDesc = nil
file_pkg_proto_based32_proto_goTypes = nil
file_pkg_proto_based32_proto_depIdxs = nil
}

View File

@@ -0,0 +1,38 @@
syntax = "proto3";
package proto;
option go_package = "github.com/quanterall/kitchensink/service/proto";
service Transcriber {
rpc Encode(stream EncodeRequest) returns (stream EncodeResponse);
rpc Decode(stream DecodeRequest) returns (stream DecodeResponse);
}
message EncodeRequest {
bytes Data = 1;
}
message EncodeResponse {
oneof Encoded {
string EncodedString = 1;
Error Error = 2;
}
}
message DecodeRequest{
string EncodedString = 1;
}
message DecodeResponse {
oneof Decoded {
bytes Data = 1;
Error Error = 2;
}
}
enum Error {
ZERO_LENGTH = 0;
CHECK_FAILED = 1;
NIL_SLICE = 2;
CHECK_TOO_SHORT = 3;
INCORRECT_HUMAN_READABLE_PART = 4;
}

View File

@@ -0,0 +1,201 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// TranscriberClient is the client API for Transcriber service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TranscriberClient interface {
Encode(ctx context.Context, opts ...grpc.CallOption) (Transcriber_EncodeClient, error)
Decode(ctx context.Context, opts ...grpc.CallOption) (Transcriber_DecodeClient, error)
}
type transcriberClient struct {
cc grpc.ClientConnInterface
}
func NewTranscriberClient(cc grpc.ClientConnInterface) TranscriberClient {
return &transcriberClient{cc}
}
func (c *transcriberClient) Encode(ctx context.Context, opts ...grpc.CallOption) (Transcriber_EncodeClient, error) {
stream, err := c.cc.NewStream(ctx, &Transcriber_ServiceDesc.Streams[0], "/codec.Transcriber/Encode", opts...)
if err != nil {
return nil, err
}
x := &transcriberEncodeClient{stream}
return x, nil
}
type Transcriber_EncodeClient interface {
Send(*EncodeRequest) error
Recv() (*EncodeResponse, error)
grpc.ClientStream
}
type transcriberEncodeClient struct {
grpc.ClientStream
}
func (x *transcriberEncodeClient) Send(m *EncodeRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *transcriberEncodeClient) Recv() (*EncodeResponse, error) {
m := new(EncodeResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *transcriberClient) Decode(ctx context.Context, opts ...grpc.CallOption) (Transcriber_DecodeClient, error) {
stream, err := c.cc.NewStream(ctx, &Transcriber_ServiceDesc.Streams[1], "/codec.Transcriber/Decode", opts...)
if err != nil {
return nil, err
}
x := &transcriberDecodeClient{stream}
return x, nil
}
type Transcriber_DecodeClient interface {
Send(*DecodeRequest) error
Recv() (*DecodeResponse, error)
grpc.ClientStream
}
type transcriberDecodeClient struct {
grpc.ClientStream
}
func (x *transcriberDecodeClient) Send(m *DecodeRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *transcriberDecodeClient) Recv() (*DecodeResponse, error) {
m := new(DecodeResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// TranscriberServer is the server API for Transcriber service.
// All implementations must embed UnimplementedTranscriberServer
// for forward compatibility
type TranscriberServer interface {
Encode(Transcriber_EncodeServer) error
Decode(Transcriber_DecodeServer) error
mustEmbedUnimplementedTranscriberServer()
}
// UnimplementedTranscriberServer must be embedded to have forward compatible implementations.
type UnimplementedTranscriberServer struct {
}
func (UnimplementedTranscriberServer) Encode(Transcriber_EncodeServer) error {
return status.Errorf(codes.Unimplemented, "method Encode not implemented")
}
func (UnimplementedTranscriberServer) Decode(Transcriber_DecodeServer) error {
return status.Errorf(codes.Unimplemented, "method Decode not implemented")
}
func (UnimplementedTranscriberServer) mustEmbedUnimplementedTranscriberServer() {}
// UnsafeTranscriberServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TranscriberServer will
// result in compilation errors.
type UnsafeTranscriberServer interface {
mustEmbedUnimplementedTranscriberServer()
}
func RegisterTranscriberServer(s grpc.ServiceRegistrar, srv TranscriberServer) {
s.RegisterService(&Transcriber_ServiceDesc, srv)
}
func _Transcriber_Encode_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(TranscriberServer).Encode(&transcriberEncodeServer{stream})
}
type Transcriber_EncodeServer interface {
Send(*EncodeResponse) error
Recv() (*EncodeRequest, error)
grpc.ServerStream
}
type transcriberEncodeServer struct {
grpc.ServerStream
}
func (x *transcriberEncodeServer) Send(m *EncodeResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *transcriberEncodeServer) Recv() (*EncodeRequest, error) {
m := new(EncodeRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Transcriber_Decode_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(TranscriberServer).Decode(&transcriberDecodeServer{stream})
}
type Transcriber_DecodeServer interface {
Send(*DecodeResponse) error
Recv() (*DecodeRequest, error)
grpc.ServerStream
}
type transcriberDecodeServer struct {
grpc.ServerStream
}
func (x *transcriberDecodeServer) Send(m *DecodeResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *transcriberDecodeServer) Recv() (*DecodeRequest, error) {
m := new(DecodeRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Transcriber_ServiceDesc is the grpc.ServiceDesc for Transcriber service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Transcriber_ServiceDesc = grpc.ServiceDesc{
ServiceName: "codec.Transcriber",
HandlerType: (*TranscriberServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Encode",
Handler: _Transcriber_Encode_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "Decode",
Handler: _Transcriber_Decode_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "pkg/proto/based32.proto",
}

View File

@@ -0,0 +1,86 @@
// Package proto is the protocol buffers specification and generated code
// package for based32
//
// The extra `error.go` file provides helpers and missing elements from the
// generated code that make programming the protocol simpler.
package proto
// The following line generates the protocol, it assumes that `protoc` is in the
// path. This directive is run when `go generate` is run in the current package,
// or if a wildcard was used ( go generate ./... ).
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./based32.proto
// Error implements the Error interface which allows this error to automatically
// generate from the error code.
//
// Fixes a bug in the generated code, which not
// only lacks the Error method it uses int32 for the error string map when it
// should be using the defined Error type. No easy way to report the bug in the
// code.
//
// With this method implemented, one can simply return the error map code
// protos.Error_ERROR_NAME_HERE and logs print this upper case snake case which
// means it can be written to be informative in the proto file and concise in
// usage, and with this tiny additional helper, very easy to return, and print.
func (x Error) Error() string {
return Error_name[int32(x)]
}
// EncodeRes makes a more convenient return type for the results
type EncodeRes struct {
String string
Error error
}
// DecodeRes makes a more convenient return type for the results
type DecodeRes struct {
Bytes []byte
Error error
}
// CreateEncodeResponse is a helper to turn a proto.EncodeRes into an
// EncodeResponse to be returned to a gRPC client.
func CreateEncodeResponse(res EncodeRes) (response *EncodeResponse) {
// First, create the response structure.
response = &EncodeResponse{}
// Because the protobuf struct is essentially a Variant, a structure that
// does not exist in Go, there is an implicit contract that if there is an
// error, there is no return value. This is not implicit in Go's tuple
// returns.
//
// Thus, if there is an error, we return that, otherwise, the value in the
// response.
if res.Error != nil {
response.Encoded = &EncodeResponse_Error{
Error(Error_value[res.Error.Error()]),
}
} else {
response.Encoded =
&EncodeResponse_EncodedString{
res.String,
}
}
return
}
// CreateDecodeResponse is a helper to turn a proto.DecodeRes into an
// DecodeResponse to be returned to a gRPC client.
func CreateDecodeResponse(res DecodeRes) (response *DecodeResponse) {
// First, create the response structure.
response = &DecodeResponse{}
// Return an error if there is an error, otherwise return the response data.
if res.Error != nil {
response.Decoded = &DecodeResponse_Error{
Error(Error_value[res.Error.Error()]),
}
} else {
response.Decoded = &DecodeResponse_Data{res.Bytes}
}
return
}

102
steps/step6/types.go Normal file
View File

@@ -0,0 +1,102 @@
package codec
import (
"github.com/quanterall/kitchensink/pkg/codecer"
)
// Codec is the collection of elements that creates a Human Readable Binary
// Transcription Codec
//
// This is an example of the use of a structure definition to encapsulate and
// logically connect together all of the elements of an implementation, while
// also permitting this to be used by external code without further
// dependencies, either through this type, or via the interface defined further
// down.
//
// It is not "official" idiom, but it's the opinion of the author of this
// tutorial that return values given in type specifications like this helps the
// users of the library understand what the return values actually are.
// Otherwise, the programmer is forced to read the whole function just to spot
// the names and, even worse, comments explaining what the values are, which are
// often neglected during debugging, and turn into lies!
type Codec struct {
// Name is the human readable name given to this encoder
Name string
// HRP is the Human Readable Prefix to be appended in front of the encoding
// to disambiguate it from another encoding or as a network or protocol
// identifier. This can be empty, but more usually this will be used to
// disambiguate versus other similarly encoded values, such as used on a
// different cryptocurrency network, or between main and test networks.
HRP string
// Charset is the set of characters that the encoder uses. This should match
// the output encoder, 32 for using base32, 64 for base64, etc.
//
// For arbitrary bases, see the following function in the standard library:
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/strconv/itoa.go;l=25
// This function can render up to base36, but by default uses 0-9a-z in its
// representation, which would either need to be string substituted for
// non-performance-critical uses or the function above forked to provide a
// direct encoding to the intended characters used for the encoding, using
// this charset string as the key. The sequence matters, each character
// represents the cipher for a given value to be found at a given place in
// the encoded number.
Charset string
// Encode takes an arbitrary length byte input and returns the output as
// defined for the codec
Encoder func(input []byte) (output string, err error)
// Decode takes an encoded string and returns if the encoding is valid and
// the value passes any check function defined for the type.
Decoder func(input string) (output []byte, err error)
// AddCheck is used by Encode to add extra bytes for the checksum to ensure
// correct input so user does not send to a wrong address by mistake, for
// example.
MakeCheck func(input []byte, checkLen int) (output []byte)
// Check returns whether the check is valid
Check func(input []byte) (err error)
}
// The following implementations are here to ensure this type implements the
// interface. In this tutorial/example we are creating a kind of generic
// implementation through the use of closures loaded into a struct.
//
// Normally a developer would use either one, or the other, a struct with
// closures, OR an interface with arbitrary variable with implementations for
// the created type.
//
// In order to illustrate both interfaces and the use of closures with a struct
// in this way we combine the two things by invoking the closures in a
// predefined pair of methods that satisfy the interface.
//
// In fact, there is no real reason why this design could not be standard idiom,
// since satisfies most of the requirements of idiom for both interfaces
// (minimal) and hot-reloadable interfaces (allowing creation of registerable
// compile time plugins such as used in database drivers with structs, and the
// end user can then either use interfaces or the provided struct, and both
// options are open.
// This ensures the interface is satisfied for codecer.Codecer and is removed in
// the generated binary because the underscore indicates the value is discarded.
var _ codecer.Codecer = &Codec{}
// Encode implements the codecer.Codecer.Encode by calling the provided
// function, and allows the concrete Codec type to always satisfy the interface,
// while allowing it to be implemented entirely differently.
//
// Note: short functions like this can be one-liners according to gofmt.
func (c *Codec) Encode(input []byte) (string, error) { return c.Encoder(input) }
// Decode implements the codecer.Codecer.Decode by calling the provided
// function, and allows the concrete Codec type to always satisfy the interface,
// while allowing it to be implemented entirely differently.
//
// Note: this also can be a one liner. Since we name the return values in the
// type definition and interface, omitting them here makes the line short enough
// to be a one liner.
func (c *Codec) Decode(input string) ([]byte, error) { return c.Decoder(input) }