Adding custom layer to packet from capture fails - go

I am trying to implement my own decoding layer ontop of TCP, so far it only works when I create a packet without any Eth/IP/TCP header and set its layer to my custom layer manually. The data of the custom protocol is inside an ordinary TCP payload.
How do I decode only the payload of the TCP layer as another layer?
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
)
var (
pcapFile string = "capt.pcap"
handle *pcap.Handle
err error
)
type CustomLayer struct {
SomeByte byte
AnotherByte byte
restOfData []byte
}
var CustomLayerType = gopacket.RegisterLayerType(
2001,
gopacket.LayerTypeMetadata{
"CustomLayerType",
gopacket.DecodeFunc(decodeCustomLayer),
},
)
func (l CustomLayer) LayerType() gopacket.LayerType {
return CustomLayerType
}
func (l CustomLayer) LayerContents() []byte {
return []byte{l.SomeByte, l.AnotherByte}
}
func (l CustomLayer) LayerPayload() []byte {
return l.restOfData
}
func decodeCustomLayer(data []byte, p gopacket.PacketBuilder) error {
p.AddLayer(&CustomLayer{data[0], data[1], data[2:]})
// nil means this is the last layer. No more decoding
return nil
}
func main() {
handle, err = pcap.OpenOffline(pcapFile)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
fmt.Println("TCP layer detected.")
tcp, _ := tcpLayer.(*layers.TCP)
fmt.Println("Sequence number: ", tcp.Seq)
customLayer := packet.Layer(CustomLayerType)
if customLayer != nil { // always nil
customLayerContent, _ := customLayer.(*CustomLayer)
// Now we can access the elements of the custom struct
fmt.Println("Payload: ", customLayerContent.LayerPayload())
fmt.Println("SomeByte element:", customLayerContent.SomeByte)
fmt.Println("AnotherByte element:", customLayerContent.AnotherByte)
}
}
fmt.Println()
}
}
Most of the code is from this great post by devdungeon.

As no one responded I am going to answer it myself now.
Basically we have 3 options to handle this:
Create an extended TCP layer that handles our additional bytes and override default one by setting layers.LinkTypeMetadata[layers.LinkTypeTCP] to our extended version. Have a look at this example.
Create a new packet from the TCP payload using gopacket.NewPacket setting firstLayerDecoder to CustomLayerType and decode it normally.
As you mostly don't need an actual layer but instead a filled CustomLayer struct simply write a DecodeBytesToCustomStruct function where you pass TCP payload. This way we can even return multiple structs from one packets payload which wouldn't be possible otherwise.
Omit all CustomLayer code from above.
type CustomStruct struct {
SomeByte byte
AnotherByte byte
restOfData []byte
}
func (customStruct *CustomStruct) DecodeStructFromBytes(data []byte) error {
customStruct.SomeByte = data[0]
customStruct.AnotherByte = data[1]
customStruct.restOfData = data[2:]
return nil
}
In your main.go
for packet := range packetSource.Packets() {
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
if tcp.Payload != nil && len(tcpLayer.Payload) > 0 {
customStruct := CustomStruct{}
customStruct.DecodeStructFromBytes(tcp.Payload)
fmt.Println("SomeByte element:", customStruct.SomeByte)
}
}
}
tcp.Payload is the same as packet.ApplicationLayer().Payload()

Related

Why is data being pushed into the channel but never read from the receiver goroutine?

I am building a daemon and I have two services that will be sending data to and from each other. Service A is what produces the data and service B a is Data Buffer service or like a queue. So from the main.go file, service B is instantiated and started. The Start() method will perform the buffer() function as a goroutine because this function waits for data to be passed onto a channel and I don't want the main process to halt waiting for buffer to complete. Then Service A is instantiated and started. It is then also "registered" with Service B.
I created a method called RegisterWithBufferService for Service A that creates two new channels. It will store those channels as it's own attributes and also provide them to Service B.
func (s *ServiceA) RegisterWithBufferService(bufService *data.DataBuffer) error {
newIncomingChan := make(chan *data.DataFrame, 1)
newOutgoingChan := make(chan []byte, 1)
s.IncomingBuffChan = newIncomingChan
s.OutgoingDataChannels = append(s.OutgoingDataChannels, newOutgoingChan)
bufService.DataProviders[s.ServiceName()] = data.DataProviderInfo{
IncomingChan: newOutgoingChan, //our outGoing channel is their incoming
OutgoingChan: newIncomingChan, // our incoming channel is their outgoing
}
s.DataBufferService = bufService
bufService.NewProvider <- s.ServiceName() //The DataBuffer service listens for new services and creates a new goroutine for buffering
s.Logger.Info().Msg("Registeration completed.")
return nil
}
Buffer essentially listens for incoming data from Service A, decodes it using Decode() and then adds it to a slice called buf. If the slice is greater in length than bufferPeriod then it will send the first item in the slice in the Outgoing channel back to Service A.
func (b* DataBuffer) buffer(bufferPeriod int) {
for {
select {
case newProvider := <- b.NewProvider:
b.wg.Add(1)
/*
newProvider is a string
DataProviders is a map the value it returns is a struct containing the Incoming and
Outgoing channels for this service
*/
p := b.DataProviders[newProvider]
go func(prov string, in chan []byte, out chan *DataFrame) {
defer b.wg.Done()
var buf []*DataFrame
for {
select {
case rawData := <-in:
tmp := Decode(rawData) //custom decoding function. Returns a *DataFrame
buf = append(buf, tmp)
if len(buf) < bufferPeriod {
b.Logger.Info().Msg("Sending decoded data out.")
out <- buf[0]
buf = buf[1:] //pop
}
case <- b.Quit:
return
}
}
}(newProvider, p.IncomingChan, p.OutgoingChan)
}
case <- b.Quit:
return
}
}
Now Service A has a method called record that will periodically push data to all the channels in it's OutgoingDataChannels attribute.
func (s *ServiceA) record() error {
...
if atomic.LoadInt32(&s.Listeners) != 0 {
s.Logger.Info().Msg("Sending raw data to data buffer")
for _, outChan := range s.OutgoingDataChannels {
outChan <- dataBytes // the receiver (Service B) is already listening and this doesn't hang
}
s.Logger.Info().Msg("Raw data sent and received") // The logger will output this so I know it's not hanging
}
}
The problem is that Service A seems to push the data successfully using record but Service B never goes into the case rawData := <-in: case in the buffer sub-goroutine. Is this because I have nested goroutines? Incase it's not clear, when Service B is started, it calls buffer but because it would hang otherwise, I made the call to buffer a goroutine. So then when Service A calls RegisterWithBufferService, the buffer goroutine creates a goroutine to listen for new data from Service B and push it back to Service A once the buffer is filled. I hope I explained it clearly.
EDIT 1
I've made a minimal, reproducible example.
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
var (
defaultBufferingPeriod int = 3
DefaultPollingInterval int64 = 10
)
type DataObject struct{
Data string
}
type DataProvider interface {
RegisterWithBufferService(*DataBuffer) error
ServiceName() string
}
type DataProviderInfo struct{
IncomingChan chan *DataObject
OutgoingChan chan *DataObject
}
type DataBuffer struct{
Running int32 //used atomically
DataProviders map[string]DataProviderInfo
Quit chan struct{}
NewProvider chan string
wg sync.WaitGroup
}
func NewDataBuffer() *DataBuffer{
var (
wg sync.WaitGroup
)
return &DataBuffer{
DataProviders: make(map[string]DataProviderInfo),
Quit: make(chan struct{}),
NewProvider: make(chan string),
wg: wg,
}
}
func (b *DataBuffer) Start() error {
if ok := atomic.CompareAndSwapInt32(&b.Running, 0, 1); !ok {
return fmt.Errorf("Could not start Data Buffer Service.")
}
go b.buffer(defaultBufferingPeriod)
return nil
}
func (b *DataBuffer) Stop() error {
if ok := atomic.CompareAndSwapInt32(&b.Running, 1, 0); !ok {
return fmt.Errorf("Could not stop Data Buffer Service.")
}
for _, p := range b.DataProviders {
close(p.IncomingChan)
close(p.OutgoingChan)
}
close(b.Quit)
b.wg.Wait()
return nil
}
// buffer creates goroutines for each incoming, outgoing data pair and decodes the incoming bytes into outgoing DataFrames
func (b *DataBuffer) buffer(bufferPeriod int) {
for {
select {
case newProvider := <- b.NewProvider:
fmt.Println("Received new Data provider.")
if _, ok := b.DataProviders[newProvider]; ok {
b.wg.Add(1)
p := b.DataProviders[newProvider]
go func(prov string, in chan *DataObject, out chan *DataObject) {
defer b.wg.Done()
var (
buf []*DataObject
)
fmt.Printf("Waiting for data from: %s\n", prov)
for {
select {
case inData := <-in:
fmt.Printf("Received data from: %s\n", prov)
buf = append(buf, inData)
if len(buf) > bufferPeriod {
fmt.Printf("Queue is filled, sending data back to %s\n", prov)
out <- buf[0]
fmt.Println("Data Sent")
buf = buf[1:] //pop
}
case <- b.Quit:
return
}
}
}(newProvider, p.IncomingChan, p.OutgoingChan)
}
case <- b.Quit:
return
}
}
}
type ServiceA struct{
Active int32 // atomic
Stopping int32 // atomic
Recording int32 // atomic
Listeners int32 // atomic
name string
QuitChan chan struct{}
IncomingBuffChan chan *DataObject
OutgoingBuffChans []chan *DataObject
DataBufferService *DataBuffer
}
// A compile time check to ensure ServiceA fully implements the DataProvider interface
var _ DataProvider = (*ServiceA)(nil)
func NewServiceA() (*ServiceA, error) {
var newSliceOutChans []chan *DataObject
return &ServiceA{
QuitChan: make(chan struct{}),
OutgoingBuffChans: newSliceOutChans,
name: "SERVICEA",
}, nil
}
// Start starts the service. Returns an error if any issues occur
func (s *ServiceA) Start() error {
atomic.StoreInt32(&s.Active, 1)
return nil
}
// Stop stops the service. Returns an error if any issues occur
func (s *ServiceA) Stop() error {
atomic.StoreInt32(&s.Stopping, 1)
close(s.QuitChan)
return nil
}
func (s *ServiceA) StartRecording(pol_int int64) error {
if ok := atomic.CompareAndSwapInt32(&s.Recording, 0, 1); !ok {
return fmt.Errorf("Could not start recording. Data recording already started")
}
ticker := time.NewTicker(time.Duration(pol_int) * time.Second)
go func() {
for {
select {
case <-ticker.C:
fmt.Println("Time to record...")
err := s.record()
if err != nil {
return
}
case <-s.QuitChan:
ticker.Stop()
return
}
}
}()
return nil
}
func (s *ServiceA) record() error {
current_time := time.Now()
ct := fmt.Sprintf("%02d-%02d-%d", current_time.Day(), current_time.Month(), current_time.Year())
dataObject := &DataObject{
Data: ct,
}
if atomic.LoadInt32(&s.Listeners) != 0 {
fmt.Println("Sending data to Data buffer...")
for _, outChan := range s.OutgoingBuffChans {
outChan <- dataObject // the receivers should already be listening
}
fmt.Println("Data sent.")
}
return nil
}
// RegisterWithBufferService satisfies the DataProvider interface. It provides the bufService with new incoming and outgoing channels along with a polling interval
func (s ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
if _, ok := bufService.DataProviders[s.ServiceName()]; ok {
return fmt.Errorf("%v data provider already registered with Data Buffer.", s.ServiceName())
}
newIncomingChan := make(chan *DataObject, 1)
newOutgoingChan := make(chan *DataObject, 1)
s.IncomingBuffChan = newIncomingChan
s.OutgoingBuffChans = append(s.OutgoingBuffChans, newOutgoingChan)
bufService.DataProviders[s.ServiceName()] = DataProviderInfo{
IncomingChan: newOutgoingChan, //our outGoing channel is their incoming
OutgoingChan: newIncomingChan, // our incoming channel is their outgoing
}
s.DataBufferService = bufService
bufService.NewProvider <- s.ServiceName() //The DataBuffer service listens for new services and creates a new goroutine for buffering
return nil
}
// ServiceName satisfies the DataProvider interface. It returns the name of the service.
func (s ServiceA) ServiceName() string {
return s.name
}
func main() {
var BufferedServices []DataProvider
fmt.Println("Instantiating and Starting Data Buffer Service...")
bufService := NewDataBuffer()
err := bufService.Start()
if err != nil {
panic(fmt.Sprintf("%v", err))
}
defer bufService.Stop()
fmt.Println("Data Buffer Service successfully started.")
fmt.Println("Instantiating and Starting Service A...")
serviceA, err := NewServiceA()
if err != nil {
panic(fmt.Sprintf("%v", err))
}
BufferedServices = append(BufferedServices, *serviceA)
err = serviceA.Start()
if err != nil {
panic(fmt.Sprintf("%v", err))
}
defer serviceA.Stop()
fmt.Println("Service A successfully started.")
fmt.Println("Registering services with Data Buffer...")
for _, s := range BufferedServices {
_ = s.RegisterWithBufferService(bufService) // ignoring error msgs for base case
}
fmt.Println("Registration complete.")
fmt.Println("Beginning recording...")
_ = atomic.AddInt32(&serviceA.Listeners, 1)
err = serviceA.StartRecording(DefaultPollingInterval)
if err != nil {
panic(fmt.Sprintf("%v", err))
}
for {
select {
case RTD := <-serviceA.IncomingBuffChan:
fmt.Println(RTD)
case <-serviceA.QuitChan:
atomic.StoreInt32(&serviceA.Listeners, 0)
bufService.Quit<-struct{}{}
}
}
}
Running on Go 1.17. When running the example, it should print the following every 10 seconds:
Time to record...
Sending data to Data buffer...
Data sent.
But then Data buffer never goes into the inData := <-in case.
To diagnose this I changed fmt.Println("Sending data to Data buffer...") to fmt.Println("Sending data to Data buffer...", s.OutgoingBuffChans) and the output was:
Time to record...
Sending data to Data buffer... []
So you are not actually sending the data to any channels. The reason for this is:
func (s ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
As the receiver is not a pointer when you do the s.OutgoingBuffChans = append(s.OutgoingBuffChans, newOutgoingChan) you are changing s.OutgoingBuffChans in a copy of the ServiceA which is discarded when the function exits. To fix this change:
func (s ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
to
func (s *ServiceA) RegisterWithBufferService(bufService *DataBuffer) error {
and
BufferedServices = append(BufferedServices, *serviceA)
to
BufferedServices = append(BufferedServices, serviceA)
The amended version outputs:
Time to record...
Sending data to Data buffer... [0xc0000d8060]
Data sent.
Received data from: SERVICEA
Time to record...
Sending data to Data buffer... [0xc0000d8060]
Data sent.
Received data from: SERVICEA
So this resolves the reported issue (I would not be suprised if there are other issues but hopefully this points you in the right direction). I did notice that the code you originally posted does use a pointer receiver so that might have suffered from another issue (but its difficult to comment on code fragments in a case like this).

Semantics - Passing outer interface type value to inner interface variable

Below is the prototype code for 3 layer API(with some design limitations):
// Sample program demonstrating interface composition.
package main
import (
"errors"
"fmt"
"io"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
// =============================================================================
// Data is the structure of the data we are copying.
type Data struct {
Line string
}
// =============================================================================
// Puller declares behavior for pulling data.
type Puller interface {
Pull(d *Data) error
}
// Storer declares behavior for storing data.
type Storer interface {
Store(d *Data) error
}
// PullStorer declares behavior for both pulling and storing.
type PullStorer interface {
Puller
Storer
}
// =============================================================================
// Xenia is a system we need to pull data from.
type Xenia struct {
Host string
Timeout time.Duration
}
// Pull knows how to pull data out of Xenia.
func (*Xenia) Pull(d *Data) error {
switch rand.Intn(10) {
case 1, 9:
return io.EOF
case 5:
return errors.New("Error reading data from Xenia")
default:
d.Line = "Data"
fmt.Println("In:", d.Line)
return nil
}
}
// Pillar is a system we need to store data into.
type Pillar struct {
Host string
Timeout time.Duration
}
// Store knows how to store data into Pillar.
func (*Pillar) Store(d *Data) error {
fmt.Println("Out:", d.Line)
return nil
}
// =============================================================================
// System wraps Xenia and Pillar together into a single system.
type System struct {
Xenia
Pillar
}
// =============================================================================
// pull knows how to pull bulks of data from any Puller.
func pull(p Puller, data []Data) (int, error) {
for i := range data {
if err := p.Pull(&data[i]); err != nil {
return i, err
}
}
return len(data), nil
}
// store knows how to store bulks of data from any Storer.
func store(s Storer, data []Data) (int, error) {
for i := range data {
if err := s.Store(&data[i]); err != nil {
return i, err
}
}
return len(data), nil
}
// Copy knows how to pull and store data from any System.
func Copy(ps PullStorer, batch int) error {
data := make([]Data, batch)
for {
i, err := pull(ps, data)
if i > 0 {
if _, err := store(ps, data[:i]); err != nil {
return err
}
}
if err != nil {
return err
}
}
}
// =============================================================================
func main() {
sys := System{
Xenia: Xenia{
Host: "localhost:8000",
Timeout: time.Second,
},
Pillar: Pillar{
Host: "localhost:9000",
Timeout: time.Second,
},
}
if err := Copy(&sys, 3); err != io.EOF {
fmt.Println(err)
}
}
My understanding is,
If you pass u, the interface contains type of u, and a pointer to a copy of u. If you pass &u, the interface contains the type of &u and the address of u.
So, below is my understanding for variables p, s & ps, from above code:
But, I would like to confirm,
1) On pull(ps, data), does first element of p contain type *System or Xenia type, as first element?
2) On store(ps, data[:i]), does first element of s contain type *System or Pillar type?
The type is *System.
You can confirm this by adding simple print statement, e.g. fmt.Printf("%T\n", p)

Golang Parsing of ProtoBuf

I'm new to Golang and I am trying to write an home automation framework in Golang, using the Micro framework and Protobuf framework.
I am currently having a hard time trying to implement a simple registry type service.
An example of the problem I am having is that I have the following, I want to be able to get a list of devices, provided a client does a GET request to http://localhost:8080/view/devices
I have the following protobuf definition:
syntax = "proto3";
service DRegistry {
rpc View(ViewRequest) returns (DeviceRegistry) {}
}
message DeviceRegistry {
repeated Device devices = 1;
}
message ViewRequest {
string Alias = 1;
}
message Device {
string Alias = 1;
string HWAddress = 2;
string WakeUpMethod = 3;
repeated string BoundServices = 4;
}
Then in my service defination I have the following:
package main
import (
"log"
micro "github.com/micro/go-micro"
proto "github.com/srizzling/gotham/proto/device"
"golang.org/x/net/context"
)
// DRegistry stands for Device Registry and is how devices register to Gotham.
type DRegistry struct{}
var devices map[string]proto.Device
func (g *DRegistry) View(ctx context.Context, req *proto.ViewRequest, rsp *proto.DeviceRegistry) error {
filter := req.Alias
devices, err := filterDevices(filter)
rsp.Devices = devices
}
func filterDevices(filter string) (*[]proto.Device, error) {
// Currently only supports listing a single service for now
// TODO: expand filter to be more consise
filteredDevices := make([]proto.Device, 0, len(devices))
for _, e := range devices {
for _, f := range e.BoundServices {
if f == filter {
filteredDevices = append(filteredDevices, e)
}
}
}
return &filteredDevices, nil
}
func main() {
service := micro.NewService(
micro.Name("DRegistry"),
)
proto.RegisterDRegistryHandler(service.Server(), new(DRegistry))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
The problem I am having is that my IDE (Visual Studio Code) is complianing that I cannot use devices (type *[]device.Device) as type []*device.Device in assignment which is confusing.
TLDR: How do I assign a collection of proto.Devices to the proto.DeviceRegistry?
func filterDevices(filter string) ([]*proto.Device, error) {
// Currently only supports listing a single service for now
// TODO: expand filter to be more consise
filteredDevices := make([]*proto.Device, 0, len(devices))
for _, e := range devices {
for _, f := range e.BoundServices {
if f == filter {
filteredDevices = append(filteredDevices, &e)
}
}
}
return filteredDevices, nil
}
There is a difference between a slice of pointers ([]*) and a pointer to a slice (*[]). You are returning a pointer to slice, whereas what you want is a slice of pointers. We can solve this by:
Updating your filterDevices signature to return a slice of pointers
Updating your make call to make a slice of pointers
Taking the address of e in your call to append (we want a slice of pointers to devices)
Not returning the address of the slice

Golang RPC encode custom function

I am trying to use github.com/dullgiulio/pingo and send my custom struct
type LuaPlugin struct {
Name string
List []PluginTable
}
type PluginTable struct {
Name string
F lua.LGFunction
}
// LoadPlugins walks over the plugin directory loading all exported plugins
func LoadPlugins() {
//
p := pingo.NewPlugin("tcp", "plugins/test")
// Actually start the plugin
p.Start()
// Remember to stop the plugin when done using it
defer p.Stop()
gob.Register(&LuaPlugin{})
gob.Register(&PluginTable{})
var resp *LuaPlugin
// Call a function from the object we created previously
if err := p.Call("MyPlugin.SayHello", "Go developer", &resp); err != nil {
log.Print(err)
} else {
log.Print(resp.List[0])
}
}
However I am always getting nil for the F field of ym struct. This is what I am sending on the client
// Create an object to be exported
type MyPlugin struct{}
// Exported method, with a RPC signature
func (p *MyPlugin) SayHello(name string, msg *util.LuaPlugin) error {
//
//
*msg = util.LuaPlugin{
Name: "test",
List: []util.PluginTable{
{
Name: "hey",
F: func(L *lua.LState) int {
log.Println(L.ToString(2))
return 0
},
},
},
}
return nil
}
Is it not possible to send custom data types over RPC?
I'm not familiar with the library however, you could try converting the struct to a byte slice before transport. Late reply, might help others....
Simple conversion: returns a struct as bytes
func StructToBytes(s interface{}) (converted []byte, err error) {
var buff bytes.Buffer
encoder := gob.NewEncoder(&buff)
if err = encoder.Encode(s); err != nil {
return
}
converted = buff.Bytes()
return
}
Decoder: returns a wrapper to decode the bytes into
func Decoder(rawBytes []byte) (decoder *gob.Decoder) {
reader := bytes.NewReader(rawBytes)
decoder = gob.NewDecoder(reader)
return
}
Example:
type MyStruct struct {
Name string
}
toEncode := MyStruct{"John Doe"}
// convert the struct to bytes
structBytes, err := StructToBytes(toEncode)
if err != nil {
panic(err)
}
//-----------
// send over RPC and decode on other side
//-----------
// create a new struct to decode into
var DecodeInto MyStruct
// pass the bytes to the decoder
decoder := Decoder(structBytes)
// Decode into the struct
decoder.Decode(&DecodeInto)
fmt.Println(DecodeInto.Name) // John Doe
Due to the use of the gob package you can swap types and typecast to a certain extent.
For more see: https://golang.org/pkg/encoding/gob/

Understand this code (golang), double parantheses ()()

I was wondering if someone can explain this syntax to me. In the google maps go api, they have
type Client struct {
httpClient *http.Client
apiKey string
baseURL string
clientID string
signature []byte
requestsPerSecond int
rateLimiter chan int
}
// NewClient constructs a new Client which can make requests to the Google Maps WebService APIs.
func NewClient(options ...ClientOption) (*Client, error) {
c := &Client{requestsPerSecond: defaultRequestsPerSecond}
WithHTTPClient(&http.Client{})(c) //???????????
for _, option := range options {
err := option(c)
if err != nil {
return nil, err
}
}
if c.apiKey == "" && (c.clientID == "" || len(c.signature) == 0) {
return nil, errors.New("maps: API Key or Maps for Work credentials missing")
}
// Implement a bursty rate limiter.
// Allow up to 1 second worth of requests to be made at once.
c.rateLimiter = make(chan int, c.requestsPerSecond)
// Prefill rateLimiter with 1 seconds worth of requests.
for i := 0; i < c.requestsPerSecond; i++ {
c.rateLimiter <- 1
}
go func() {
// Wait a second for pre-filled quota to drain
time.Sleep(time.Second)
// Then, refill rateLimiter continuously
for _ = range time.Tick(time.Second / time.Duration(c.requestsPerSecond)) {
c.rateLimiter <- 1
}
}()
return c, nil
}
// WithHTTPClient configures a Maps API client with a http.Client to make requests over.
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
if _, ok := c.Transport.(*transport); !ok {
t := c.Transport
if t != nil {
c.Transport = &transport{Base: t}
} else {
c.Transport = &transport{Base: http.DefaultTransport}
}
}
client.httpClient = c
return nil
}
}
And this is the line I don't understand in NewClient
WithHTTPClient(&http.Client{})(c)
Why are there two ()()?
I see that WithHTTPClient takes in a *http.Client which that line does, but then it also passes in a pointer to the client struct declared above it?
WithHTTPClient returns a function, ie:
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
....
return nil
}
}
WithHTTPClient(&http.Client{})(c) is just calling that function with c (a pointer to a Client) as parameter. It could be written as:
f := WithHTTPClient(&http.Client{})
f(c)

Resources