Initial QSfera import
This commit is contained in:
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
package addoffsetstotxn
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
TransactionalID string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
ProducerID int64 `kafka:"min=v0,max=v3"`
|
||||
ProducerEpoch int16 `kafka:"min=v0,max=v3"`
|
||||
GroupID string `kafka:"min=v0,max=v3|min=v3,max=v3,compact"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.AddOffsetsToTxn }
|
||||
|
||||
func (r *Request) Transaction() string { return r.TransactionalID }
|
||||
|
||||
var _ protocol.TransactionalMessage = (*Request)(nil)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.AddOffsetsToTxn }
|
||||
Generated
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
package addpartitionstotxn
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
TransactionalID string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
ProducerID int64 `kafka:"min=v0,max=v3"`
|
||||
ProducerEpoch int16 `kafka:"min=v0,max=v3"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
Partitions []int32 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.AddPartitionsToTxn }
|
||||
|
||||
func (r *Request) Transaction() string { return r.TransactionalID }
|
||||
|
||||
var _ protocol.TransactionalMessage = (*Request)(nil)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
Results []ResponseResult `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type ResponseResult struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
Results []ResponsePartition `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v3"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.AddPartitionsToTxn }
|
||||
Generated
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
package alterclientquotas
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_AlterClientQuotas
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
Entries []Entry `kafka:"min=v0,max=v1"`
|
||||
ValidateOnly bool `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.AlterClientQuotas }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
Entities []Entity `kafka:"min=v0,max=v1"`
|
||||
Ops []Ops `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
EntityType string `kafka:"min=v0,max=v0|min=v1,max=v1,compact"`
|
||||
EntityName string `kafka:"min=v0,max=v0,nullable|min=v1,max=v1,nullable,compact"`
|
||||
}
|
||||
|
||||
type Ops struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
Key string `kafka:"min=v0,max=v0|min=v1,max=v1,compact"`
|
||||
Value float64 `kafka:"min=v0,max=v1"`
|
||||
Remove bool `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v1"`
|
||||
Results []ResponseQuotas `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.AlterClientQuotas }
|
||||
|
||||
type ResponseQuotas struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v1"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable"`
|
||||
Entities []Entity `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
package alterconfigs
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_AlterConfigs
|
||||
type Request struct {
|
||||
Resources []RequestResources `kafka:"min=v0,max=v1"`
|
||||
ValidateOnly bool `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.AlterConfigs }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type RequestResources struct {
|
||||
ResourceType int8 `kafka:"min=v0,max=v1"`
|
||||
ResourceName string `kafka:"min=v0,max=v1"`
|
||||
Configs []RequestConfig `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type RequestConfig struct {
|
||||
Name string `kafka:"min=v0,max=v1"`
|
||||
Value string `kafka:"min=v0,max=v1,nullable"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v1"`
|
||||
Responses []ResponseResponses `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.AlterConfigs }
|
||||
|
||||
type ResponseResponses struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v1"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable"`
|
||||
ResourceType int8 `kafka:"min=v0,max=v1"`
|
||||
ResourceName string `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.BrokerMessage = (*Request)(nil)
|
||||
)
|
||||
Generated
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
package alterpartitionreassignments
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_AlterPartitionReassignments
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
TimeoutMs int32 `kafka:"min=v0,max=v0"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
Name string `kafka:"min=v0,max=v0"`
|
||||
Partitions []RequestPartition `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v0"`
|
||||
Replicas []int32 `kafka:"min=v0,max=v0,nullable"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey {
|
||||
return protocol.AlterPartitionReassignments
|
||||
}
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v0"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable"`
|
||||
Results []ResponseResult `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type ResponseResult struct {
|
||||
Name string `kafka:"min=v0,max=v0"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v0"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey {
|
||||
return protocol.AlterPartitionReassignments
|
||||
}
|
||||
Generated
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
package alteruserscramcredentials
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Deletions []RequestUserScramCredentialsDeletion `kafka:"min=v0,max=v0"`
|
||||
Upsertions []RequestUserScramCredentialsUpsertion `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.AlterUserScramCredentials }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type RequestUserScramCredentialsDeletion struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v0,compact"`
|
||||
Mechanism int8 `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type RequestUserScramCredentialsUpsertion struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v0,compact"`
|
||||
Mechanism int8 `kafka:"min=v0,max=v0"`
|
||||
Iterations int32 `kafka:"min=v0,max=v0"`
|
||||
Salt []byte `kafka:"min=v0,max=v0,compact"`
|
||||
SaltedPassword []byte `kafka:"min=v0,max=v0,compact"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v0"`
|
||||
Results []ResponseUserScramCredentials `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.AlterUserScramCredentials }
|
||||
|
||||
type ResponseUserScramCredentials struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
User string `kafka:"min=v0,max=v0,compact"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package apiversions
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
_ struct{} `kafka:"min=v0,max=v2"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.ApiVersions }
|
||||
|
||||
type Response struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v2"`
|
||||
ApiKeys []ApiKeyResponse `kafka:"min=v0,max=v2"`
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v2"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.ApiVersions }
|
||||
|
||||
type ApiKeyResponse struct {
|
||||
ApiKey int16 `kafka:"min=v0,max=v2"`
|
||||
MinVersion int16 `kafka:"min=v0,max=v2"`
|
||||
MaxVersion int16 `kafka:"min=v0,max=v2"`
|
||||
}
|
||||
+634
@@ -0,0 +1,634 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Bytes is an interface implemented by types that represent immutable
|
||||
// sequences of bytes.
|
||||
//
|
||||
// Bytes values are used to abstract the location where record keys and
|
||||
// values are read from (e.g. in-memory buffers, network sockets, files).
|
||||
//
|
||||
// The Close method should be called to release resources held by the object
|
||||
// when the program is done with it.
|
||||
//
|
||||
// Bytes values are generally not safe to use concurrently from multiple
|
||||
// goroutines.
|
||||
type Bytes interface {
|
||||
io.ReadCloser
|
||||
// Returns the number of bytes remaining to be read from the payload.
|
||||
Len() int
|
||||
}
|
||||
|
||||
// NewBytes constructs a Bytes value from b.
|
||||
//
|
||||
// The returned value references b, it does not make a copy of the backing
|
||||
// array.
|
||||
//
|
||||
// If b is nil, nil is returned to represent a null BYTES value in the kafka
|
||||
// protocol.
|
||||
func NewBytes(b []byte) Bytes {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
r := new(bytesReader)
|
||||
r.Reset(b)
|
||||
return r
|
||||
}
|
||||
|
||||
// ReadAll is similar to ioutil.ReadAll, but it takes advantage of knowing the
|
||||
// length of b to minimize the memory footprint.
|
||||
//
|
||||
// The function returns a nil slice if b is nil.
|
||||
func ReadAll(b Bytes) ([]byte, error) {
|
||||
if b == nil {
|
||||
return nil, nil
|
||||
}
|
||||
s := make([]byte, b.Len())
|
||||
_, err := io.ReadFull(b, s)
|
||||
return s, err
|
||||
}
|
||||
|
||||
type bytesReader struct{ bytes.Reader }
|
||||
|
||||
func (*bytesReader) Close() error { return nil }
|
||||
|
||||
type refCount uintptr
|
||||
|
||||
func (rc *refCount) ref() { atomic.AddUintptr((*uintptr)(rc), 1) }
|
||||
|
||||
func (rc *refCount) unref(onZero func()) {
|
||||
if atomic.AddUintptr((*uintptr)(rc), ^uintptr(0)) == 0 {
|
||||
onZero()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// Size of the memory buffer for a single page. We use a farily
|
||||
// large size here (64 KiB) because batches exchanged with kafka
|
||||
// tend to be multiple kilobytes in size, sometimes hundreds.
|
||||
// Using large pages amortizes the overhead of the page metadata
|
||||
// and algorithms to manage the pages.
|
||||
pageSize = 65536
|
||||
)
|
||||
|
||||
type page struct {
|
||||
refc refCount
|
||||
offset int64
|
||||
length int
|
||||
buffer *[pageSize]byte
|
||||
}
|
||||
|
||||
func newPage(offset int64) *page {
|
||||
p, _ := pagePool.Get().(*page)
|
||||
if p != nil {
|
||||
p.offset = offset
|
||||
p.length = 0
|
||||
p.ref()
|
||||
} else {
|
||||
p = &page{
|
||||
refc: 1,
|
||||
offset: offset,
|
||||
buffer: &[pageSize]byte{},
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *page) ref() { p.refc.ref() }
|
||||
|
||||
func (p *page) unref() { p.refc.unref(func() { pagePool.Put(p) }) }
|
||||
|
||||
func (p *page) slice(begin, end int64) []byte {
|
||||
i, j := begin-p.offset, end-p.offset
|
||||
|
||||
if i < 0 {
|
||||
i = 0
|
||||
} else if i > pageSize {
|
||||
i = pageSize
|
||||
}
|
||||
|
||||
if j < 0 {
|
||||
j = 0
|
||||
} else if j > pageSize {
|
||||
j = pageSize
|
||||
}
|
||||
|
||||
if i < j {
|
||||
return p.buffer[i:j]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *page) Cap() int { return pageSize }
|
||||
|
||||
func (p *page) Len() int { return p.length }
|
||||
|
||||
func (p *page) Size() int64 { return int64(p.length) }
|
||||
|
||||
func (p *page) Truncate(n int) {
|
||||
if n < p.length {
|
||||
p.length = n
|
||||
}
|
||||
}
|
||||
|
||||
func (p *page) ReadAt(b []byte, off int64) (int, error) {
|
||||
if off -= p.offset; off < 0 || off > pageSize {
|
||||
panic("offset out of range")
|
||||
}
|
||||
if off > int64(p.length) {
|
||||
return 0, nil
|
||||
}
|
||||
return copy(b, p.buffer[off:p.length]), nil
|
||||
}
|
||||
|
||||
func (p *page) ReadFrom(r io.Reader) (int64, error) {
|
||||
n, err := io.ReadFull(r, p.buffer[p.length:])
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
err = nil
|
||||
}
|
||||
p.length += n
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
func (p *page) WriteAt(b []byte, off int64) (int, error) {
|
||||
if off -= p.offset; off < 0 || off > pageSize {
|
||||
panic("offset out of range")
|
||||
}
|
||||
n := copy(p.buffer[off:], b)
|
||||
if end := int(off) + n; end > p.length {
|
||||
p.length = end
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *page) Write(b []byte) (int, error) {
|
||||
return p.WriteAt(b, p.offset+int64(p.length))
|
||||
}
|
||||
|
||||
var (
|
||||
_ io.ReaderAt = (*page)(nil)
|
||||
_ io.ReaderFrom = (*page)(nil)
|
||||
_ io.Writer = (*page)(nil)
|
||||
_ io.WriterAt = (*page)(nil)
|
||||
)
|
||||
|
||||
type pageBuffer struct {
|
||||
refc refCount
|
||||
pages contiguousPages
|
||||
length int
|
||||
cursor int
|
||||
}
|
||||
|
||||
func newPageBuffer() *pageBuffer {
|
||||
b, _ := pageBufferPool.Get().(*pageBuffer)
|
||||
if b != nil {
|
||||
b.cursor = 0
|
||||
b.refc.ref()
|
||||
} else {
|
||||
b = &pageBuffer{
|
||||
refc: 1,
|
||||
pages: make(contiguousPages, 0, 16),
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) refTo(ref *pageRef, begin, end int64) {
|
||||
length := end - begin
|
||||
|
||||
if length > math.MaxUint32 {
|
||||
panic("reference to contiguous buffer pages exceeds the maximum size of 4 GB")
|
||||
}
|
||||
|
||||
ref.pages = append(ref.buffer[:0], pb.pages.slice(begin, end)...)
|
||||
ref.pages.ref()
|
||||
ref.offset = begin
|
||||
ref.length = uint32(length)
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) ref(begin, end int64) *pageRef {
|
||||
ref := new(pageRef)
|
||||
pb.refTo(ref, begin, end)
|
||||
return ref
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) unref() {
|
||||
pb.refc.unref(func() {
|
||||
pb.pages.unref()
|
||||
pb.pages.clear()
|
||||
pb.pages = pb.pages[:0]
|
||||
pb.length = 0
|
||||
pageBufferPool.Put(pb)
|
||||
})
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) newPage() *page {
|
||||
return newPage(int64(pb.length))
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Len() int {
|
||||
return pb.length - pb.cursor
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Size() int64 {
|
||||
return int64(pb.length)
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Discard(n int) (int, error) {
|
||||
remain := pb.length - pb.cursor
|
||||
if remain < n {
|
||||
n = remain
|
||||
}
|
||||
pb.cursor += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Truncate(n int) {
|
||||
if n < pb.length {
|
||||
pb.length = n
|
||||
|
||||
if n < pb.cursor {
|
||||
pb.cursor = n
|
||||
}
|
||||
|
||||
for i := range pb.pages {
|
||||
if p := pb.pages[i]; p.length <= n {
|
||||
n -= p.length
|
||||
} else {
|
||||
if n > 0 {
|
||||
pb.pages[i].Truncate(n)
|
||||
i++
|
||||
}
|
||||
pb.pages[i:].unref()
|
||||
pb.pages[i:].clear()
|
||||
pb.pages = pb.pages[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Seek(offset int64, whence int) (int64, error) {
|
||||
c, err := seek(int64(pb.cursor), int64(pb.length), offset, whence)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
pb.cursor = int(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) ReadByte() (byte, error) {
|
||||
b := [1]byte{}
|
||||
_, err := pb.Read(b[:])
|
||||
return b[0], err
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Read(b []byte) (int, error) {
|
||||
if pb.cursor >= pb.length {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n, err := pb.ReadAt(b, int64(pb.cursor))
|
||||
pb.cursor += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) ReadAt(b []byte, off int64) (int, error) {
|
||||
return pb.pages.ReadAt(b, off)
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) ReadFrom(r io.Reader) (int64, error) {
|
||||
if len(pb.pages) == 0 {
|
||||
pb.pages = append(pb.pages, pb.newPage())
|
||||
}
|
||||
|
||||
rn := int64(0)
|
||||
|
||||
for {
|
||||
tail := pb.pages[len(pb.pages)-1]
|
||||
free := tail.Cap() - tail.Len()
|
||||
|
||||
if free == 0 {
|
||||
tail = pb.newPage()
|
||||
free = pageSize
|
||||
pb.pages = append(pb.pages, tail)
|
||||
}
|
||||
|
||||
n, err := tail.ReadFrom(r)
|
||||
pb.length += int(n)
|
||||
rn += n
|
||||
if n < int64(free) {
|
||||
return rn, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) WriteString(s string) (int, error) {
|
||||
return pb.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) Write(b []byte) (int, error) {
|
||||
wn := len(b)
|
||||
if wn == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if len(pb.pages) == 0 {
|
||||
pb.pages = append(pb.pages, pb.newPage())
|
||||
}
|
||||
|
||||
for len(b) != 0 {
|
||||
tail := pb.pages[len(pb.pages)-1]
|
||||
free := tail.Cap() - tail.Len()
|
||||
|
||||
if len(b) <= free {
|
||||
tail.Write(b)
|
||||
pb.length += len(b)
|
||||
break
|
||||
}
|
||||
|
||||
tail.Write(b[:free])
|
||||
b = b[free:]
|
||||
|
||||
pb.length += free
|
||||
pb.pages = append(pb.pages, pb.newPage())
|
||||
}
|
||||
|
||||
return wn, nil
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) WriteAt(b []byte, off int64) (int, error) {
|
||||
n, err := pb.pages.WriteAt(b, off)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if n < len(b) {
|
||||
pb.Write(b[n:])
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (pb *pageBuffer) WriteTo(w io.Writer) (int64, error) {
|
||||
var wn int
|
||||
var err error
|
||||
pb.pages.scan(int64(pb.cursor), int64(pb.length), func(b []byte) bool {
|
||||
var n int
|
||||
n, err = w.Write(b)
|
||||
wn += n
|
||||
return err == nil
|
||||
})
|
||||
pb.cursor += wn
|
||||
return int64(wn), err
|
||||
}
|
||||
|
||||
var (
|
||||
_ io.ReaderAt = (*pageBuffer)(nil)
|
||||
_ io.ReaderFrom = (*pageBuffer)(nil)
|
||||
_ io.StringWriter = (*pageBuffer)(nil)
|
||||
_ io.Writer = (*pageBuffer)(nil)
|
||||
_ io.WriterAt = (*pageBuffer)(nil)
|
||||
_ io.WriterTo = (*pageBuffer)(nil)
|
||||
|
||||
pagePool sync.Pool
|
||||
pageBufferPool sync.Pool
|
||||
)
|
||||
|
||||
type contiguousPages []*page
|
||||
|
||||
func (pages contiguousPages) ref() {
|
||||
for _, p := range pages {
|
||||
p.ref()
|
||||
}
|
||||
}
|
||||
|
||||
func (pages contiguousPages) unref() {
|
||||
for _, p := range pages {
|
||||
p.unref()
|
||||
}
|
||||
}
|
||||
|
||||
func (pages contiguousPages) clear() {
|
||||
for i := range pages {
|
||||
pages[i] = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (pages contiguousPages) ReadAt(b []byte, off int64) (int, error) {
|
||||
rn := 0
|
||||
|
||||
for _, p := range pages.slice(off, off+int64(len(b))) {
|
||||
n, _ := p.ReadAt(b, off)
|
||||
b = b[n:]
|
||||
rn += n
|
||||
off += int64(n)
|
||||
}
|
||||
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
func (pages contiguousPages) WriteAt(b []byte, off int64) (int, error) {
|
||||
wn := 0
|
||||
|
||||
for _, p := range pages.slice(off, off+int64(len(b))) {
|
||||
n, _ := p.WriteAt(b, off)
|
||||
b = b[n:]
|
||||
wn += n
|
||||
off += int64(n)
|
||||
}
|
||||
|
||||
return wn, nil
|
||||
}
|
||||
|
||||
func (pages contiguousPages) slice(begin, end int64) contiguousPages {
|
||||
i := pages.indexOf(begin)
|
||||
j := pages.indexOf(end)
|
||||
if j < len(pages) {
|
||||
j++
|
||||
}
|
||||
return pages[i:j]
|
||||
}
|
||||
|
||||
func (pages contiguousPages) indexOf(offset int64) int {
|
||||
if len(pages) == 0 {
|
||||
return 0
|
||||
}
|
||||
return int((offset - pages[0].offset) / pageSize)
|
||||
}
|
||||
|
||||
func (pages contiguousPages) scan(begin, end int64, f func([]byte) bool) {
|
||||
for _, p := range pages.slice(begin, end) {
|
||||
if !f(p.slice(begin, end)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ io.ReaderAt = contiguousPages{}
|
||||
_ io.WriterAt = contiguousPages{}
|
||||
)
|
||||
|
||||
type pageRef struct {
|
||||
buffer [2]*page
|
||||
pages contiguousPages
|
||||
offset int64
|
||||
cursor int64
|
||||
length uint32
|
||||
once uint32
|
||||
}
|
||||
|
||||
func (ref *pageRef) unref() {
|
||||
if atomic.CompareAndSwapUint32(&ref.once, 0, 1) {
|
||||
ref.pages.unref()
|
||||
ref.pages.clear()
|
||||
ref.pages = nil
|
||||
ref.offset = 0
|
||||
ref.cursor = 0
|
||||
ref.length = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (ref *pageRef) Len() int { return int(ref.Size() - ref.cursor) }
|
||||
|
||||
func (ref *pageRef) Size() int64 { return int64(ref.length) }
|
||||
|
||||
func (ref *pageRef) Close() error { ref.unref(); return nil }
|
||||
|
||||
func (ref *pageRef) String() string {
|
||||
return fmt.Sprintf("[offset=%d cursor=%d length=%d]", ref.offset, ref.cursor, ref.length)
|
||||
}
|
||||
|
||||
func (ref *pageRef) Seek(offset int64, whence int) (int64, error) {
|
||||
c, err := seek(ref.cursor, int64(ref.length), offset, whence)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
ref.cursor = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (ref *pageRef) ReadByte() (byte, error) {
|
||||
var c byte
|
||||
var ok bool
|
||||
ref.scan(ref.cursor, func(b []byte) bool {
|
||||
c, ok = b[0], true
|
||||
return false
|
||||
})
|
||||
if ok {
|
||||
ref.cursor++
|
||||
} else {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (ref *pageRef) Read(b []byte) (int, error) {
|
||||
if ref.cursor >= int64(ref.length) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n, err := ref.ReadAt(b, ref.cursor)
|
||||
ref.cursor += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (ref *pageRef) ReadAt(b []byte, off int64) (int, error) {
|
||||
limit := ref.offset + int64(ref.length)
|
||||
off += ref.offset
|
||||
|
||||
if off >= limit {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if off+int64(len(b)) > limit {
|
||||
b = b[:limit-off]
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n, err := ref.pages.ReadAt(b, off)
|
||||
if n == 0 && err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (ref *pageRef) WriteTo(w io.Writer) (wn int64, err error) {
|
||||
ref.scan(ref.cursor, func(b []byte) bool {
|
||||
var n int
|
||||
n, err = w.Write(b)
|
||||
wn += int64(n)
|
||||
return err == nil
|
||||
})
|
||||
ref.cursor += wn
|
||||
return
|
||||
}
|
||||
|
||||
func (ref *pageRef) scan(off int64, f func([]byte) bool) {
|
||||
begin := ref.offset + off
|
||||
end := ref.offset + int64(ref.length)
|
||||
ref.pages.scan(begin, end, f)
|
||||
}
|
||||
|
||||
var (
|
||||
_ io.Closer = (*pageRef)(nil)
|
||||
_ io.Seeker = (*pageRef)(nil)
|
||||
_ io.Reader = (*pageRef)(nil)
|
||||
_ io.ReaderAt = (*pageRef)(nil)
|
||||
_ io.WriterTo = (*pageRef)(nil)
|
||||
)
|
||||
|
||||
type pageRefAllocator struct {
|
||||
refs []pageRef
|
||||
head int
|
||||
size int
|
||||
}
|
||||
|
||||
func (a *pageRefAllocator) newPageRef() *pageRef {
|
||||
if a.head == len(a.refs) {
|
||||
a.refs = make([]pageRef, a.size)
|
||||
a.head = 0
|
||||
}
|
||||
ref := &a.refs[a.head]
|
||||
a.head++
|
||||
return ref
|
||||
}
|
||||
|
||||
func seek(cursor, limit, offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
// absolute offset
|
||||
case io.SeekCurrent:
|
||||
offset = cursor + offset
|
||||
case io.SeekEnd:
|
||||
offset = limit - offset
|
||||
default:
|
||||
return -1, fmt.Errorf("seek: invalid whence value: %d", whence)
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if offset > limit {
|
||||
offset = limit
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
func closeBytes(b Bytes) {
|
||||
if b != nil {
|
||||
b.Close()
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type Cluster struct {
|
||||
ClusterID string
|
||||
Controller int32
|
||||
Brokers map[int32]Broker
|
||||
Topics map[string]Topic
|
||||
}
|
||||
|
||||
func (c Cluster) BrokerIDs() []int32 {
|
||||
brokerIDs := make([]int32, 0, len(c.Brokers))
|
||||
for id := range c.Brokers {
|
||||
brokerIDs = append(brokerIDs, id)
|
||||
}
|
||||
sort.Slice(brokerIDs, func(i, j int) bool {
|
||||
return brokerIDs[i] < brokerIDs[j]
|
||||
})
|
||||
return brokerIDs
|
||||
}
|
||||
|
||||
func (c Cluster) TopicNames() []string {
|
||||
topicNames := make([]string, 0, len(c.Topics))
|
||||
for name := range c.Topics {
|
||||
topicNames = append(topicNames, name)
|
||||
}
|
||||
sort.Strings(topicNames)
|
||||
return topicNames
|
||||
}
|
||||
|
||||
func (c Cluster) IsZero() bool {
|
||||
return c.ClusterID == "" && c.Controller == 0 && len(c.Brokers) == 0 && len(c.Topics) == 0
|
||||
}
|
||||
|
||||
func (c Cluster) Format(w fmt.State, _ rune) {
|
||||
tw := new(tabwriter.Writer)
|
||||
fmt.Fprintf(w, "CLUSTER: %q\n\n", c.ClusterID)
|
||||
|
||||
tw.Init(w, 0, 8, 2, ' ', 0)
|
||||
fmt.Fprint(tw, " BROKER\tHOST\tPORT\tRACK\tCONTROLLER\n")
|
||||
|
||||
for _, id := range c.BrokerIDs() {
|
||||
broker := c.Brokers[id]
|
||||
fmt.Fprintf(tw, " %d\t%s\t%d\t%s\t%t\n", broker.ID, broker.Host, broker.Port, broker.Rack, broker.ID == c.Controller)
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
fmt.Fprintln(w)
|
||||
|
||||
tw.Init(w, 0, 8, 2, ' ', 0)
|
||||
fmt.Fprint(tw, " TOPIC\tPARTITIONS\tBROKERS\n")
|
||||
topicNames := c.TopicNames()
|
||||
brokers := make(map[int32]struct{}, len(c.Brokers))
|
||||
brokerIDs := make([]int32, 0, len(c.Brokers))
|
||||
|
||||
for _, name := range topicNames {
|
||||
topic := c.Topics[name]
|
||||
|
||||
for _, p := range topic.Partitions {
|
||||
for _, id := range p.Replicas {
|
||||
brokers[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for id := range brokers {
|
||||
brokerIDs = append(brokerIDs, id)
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, " %s\t%d\t%s\n", topic.Name, len(topic.Partitions), formatBrokerIDs(brokerIDs, -1))
|
||||
|
||||
for id := range brokers {
|
||||
delete(brokers, id)
|
||||
}
|
||||
|
||||
brokerIDs = brokerIDs[:0]
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
fmt.Fprintln(w)
|
||||
|
||||
if w.Flag('+') {
|
||||
for _, name := range topicNames {
|
||||
fmt.Fprintf(w, " TOPIC: %q\n\n", name)
|
||||
|
||||
tw.Init(w, 0, 8, 2, ' ', 0)
|
||||
fmt.Fprint(tw, " PARTITION\tREPLICAS\tISR\tOFFLINE\n")
|
||||
|
||||
for _, p := range c.Topics[name].Partitions {
|
||||
fmt.Fprintf(tw, " %d\t%s\t%s\t%s\n", p.ID,
|
||||
formatBrokerIDs(p.Replicas, -1),
|
||||
formatBrokerIDs(p.ISR, p.Leader),
|
||||
formatBrokerIDs(p.Offline, -1),
|
||||
)
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatBrokerIDs(brokerIDs []int32, leader int32) string {
|
||||
if len(brokerIDs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(brokerIDs) == 1 {
|
||||
return itoa(brokerIDs[0])
|
||||
}
|
||||
|
||||
sort.Slice(brokerIDs, func(i, j int) bool {
|
||||
id1 := brokerIDs[i]
|
||||
id2 := brokerIDs[j]
|
||||
|
||||
if id1 == leader {
|
||||
return true
|
||||
}
|
||||
|
||||
if id2 == leader {
|
||||
return false
|
||||
}
|
||||
|
||||
return id1 < id2
|
||||
})
|
||||
|
||||
brokerNames := make([]string, len(brokerIDs))
|
||||
|
||||
for i, id := range brokerIDs {
|
||||
brokerNames[i] = itoa(id)
|
||||
}
|
||||
|
||||
return strings.Join(brokerNames, ",")
|
||||
}
|
||||
|
||||
var (
|
||||
_ fmt.Formatter = Cluster{}
|
||||
)
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
buffer *bufio.Reader
|
||||
conn net.Conn
|
||||
clientID string
|
||||
idgen int32
|
||||
versions atomic.Value // map[ApiKey]int16
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn, clientID string) *Conn {
|
||||
return &Conn{
|
||||
buffer: bufio.NewReader(conn),
|
||||
conn: conn,
|
||||
clientID: clientID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) String() string {
|
||||
return fmt.Sprintf("kafka://%s@%s->%s", c.clientID, c.LocalAddr(), c.RemoteAddr())
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) Discard(n int) (int, error) {
|
||||
return c.buffer.Discard(n)
|
||||
}
|
||||
|
||||
func (c *Conn) Peek(n int) ([]byte, error) {
|
||||
return c.buffer.Peek(n)
|
||||
}
|
||||
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
return c.buffer.Read(b)
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) (int, error) {
|
||||
return c.conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *Conn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *Conn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *Conn) SetDeadline(t time.Time) error {
|
||||
return c.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *Conn) SetVersions(versions map[ApiKey]int16) {
|
||||
connVersions := make(map[ApiKey]int16, len(versions))
|
||||
|
||||
for k, v := range versions {
|
||||
connVersions[k] = v
|
||||
}
|
||||
|
||||
c.versions.Store(connVersions)
|
||||
}
|
||||
|
||||
func (c *Conn) RoundTrip(msg Message) (Message, error) {
|
||||
correlationID := atomic.AddInt32(&c.idgen, +1)
|
||||
versions, _ := c.versions.Load().(map[ApiKey]int16)
|
||||
apiVersion := versions[msg.ApiKey()]
|
||||
|
||||
if p, _ := msg.(PreparedMessage); p != nil {
|
||||
p.Prepare(apiVersion)
|
||||
}
|
||||
|
||||
if raw, ok := msg.(RawExchanger); ok && raw.Required(versions) {
|
||||
return raw.RawExchange(c)
|
||||
}
|
||||
|
||||
return RoundTrip(c, apiVersion, correlationID, c.clientID, msg)
|
||||
}
|
||||
|
||||
var (
|
||||
_ net.Conn = (*Conn)(nil)
|
||||
_ bufferedReader = (*Conn)(nil)
|
||||
)
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package consumer
|
||||
|
||||
const MaxVersionSupported = 1
|
||||
|
||||
type Subscription struct {
|
||||
Version int16 `kafka:"min=v0,max=v1"`
|
||||
Topics []string `kafka:"min=v0,max=v1"`
|
||||
UserData []byte `kafka:"min=v0,max=v1,nullable"`
|
||||
OwnedPartitions []TopicPartition `kafka:"min=v1,max=v1"`
|
||||
}
|
||||
|
||||
type Assignment struct {
|
||||
Version int16 `kafka:"min=v0,max=v1"`
|
||||
AssignedPartitions []TopicPartition `kafka:"min=v0,max=v1"`
|
||||
UserData []byte `kafka:"min=v0,max=v1,nullable"`
|
||||
}
|
||||
|
||||
type TopicPartition struct {
|
||||
Topic string `kafka:"min=v0,max=v1"`
|
||||
Partitions []int32 `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package createacls
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
Creations []RequestACLs `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.CreateAcls }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type RequestACLs struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ResourceType int8 `kafka:"min=v0,max=v3"`
|
||||
ResourceName string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
ResourcePatternType int8 `kafka:"min=v1,max=v3"`
|
||||
Principal string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
Host string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
Operation int8 `kafka:"min=v0,max=v3"`
|
||||
PermissionType int8 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
Results []ResponseACLs `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.CreateAcls }
|
||||
|
||||
type ResponseACLs struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
package createpartitions
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_CreatePartitions.
|
||||
// TODO: Support version 2.
|
||||
type Request struct {
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v1"`
|
||||
TimeoutMs int32 `kafka:"min=v0,max=v1"`
|
||||
ValidateOnly bool `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.CreatePartitions }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
Name string `kafka:"min=v0,max=v1"`
|
||||
Count int32 `kafka:"min=v0,max=v1"`
|
||||
Assignments []RequestAssignment `kafka:"min=v0,max=v1,nullable"`
|
||||
}
|
||||
|
||||
type RequestAssignment struct {
|
||||
BrokerIDs []int32 `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v1"`
|
||||
Results []ResponseResult `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.CreatePartitions }
|
||||
|
||||
type ResponseResult struct {
|
||||
Name string `kafka:"min=v0,max=v1"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v1"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+74
@@ -0,0 +1,74 @@
|
||||
package createtopics
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that v5+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v5,max=v5,tag"`
|
||||
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v5"`
|
||||
TimeoutMs int32 `kafka:"min=v0,max=v5"`
|
||||
ValidateOnly bool `kafka:"min=v1,max=v5"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.CreateTopics }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
Name string `kafka:"min=v0,max=v5"`
|
||||
NumPartitions int32 `kafka:"min=v0,max=v5"`
|
||||
ReplicationFactor int16 `kafka:"min=v0,max=v5"`
|
||||
Assignments []RequestAssignment `kafka:"min=v0,max=v5"`
|
||||
Configs []RequestConfig `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
|
||||
type RequestAssignment struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v5"`
|
||||
BrokerIDs []int32 `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
|
||||
type RequestConfig struct {
|
||||
Name string `kafka:"min=v0,max=v5"`
|
||||
Value string `kafka:"min=v0,max=v5,nullable"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that v5+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v5,max=v5,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v2,max=v5"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.CreateTopics }
|
||||
|
||||
type ResponseTopic struct {
|
||||
Name string `kafka:"min=v0,max=v5"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v5"`
|
||||
ErrorMessage string `kafka:"min=v1,max=v5,nullable"`
|
||||
NumPartitions int32 `kafka:"min=v5,max=v5"`
|
||||
ReplicationFactor int16 `kafka:"min=v5,max=v5"`
|
||||
|
||||
Configs []ResponseTopicConfig `kafka:"min=v5,max=v5"`
|
||||
}
|
||||
|
||||
type ResponseTopicConfig struct {
|
||||
Name string `kafka:"min=v5,max=v5"`
|
||||
Value string `kafka:"min=v5,max=v5,nullable"`
|
||||
ReadOnly bool `kafka:"min=v5,max=v5"`
|
||||
ConfigSource int8 `kafka:"min=v5,max=v5"`
|
||||
IsSensitive bool `kafka:"min=v5,max=v5"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.BrokerMessage = (*Request)(nil)
|
||||
)
|
||||
+537
@@ -0,0 +1,537 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type discarder interface {
|
||||
Discard(int) (int, error)
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
reader io.Reader
|
||||
remain int
|
||||
buffer [8]byte
|
||||
err error
|
||||
table *crc32.Table
|
||||
crc32 uint32
|
||||
}
|
||||
|
||||
func (d *decoder) Reset(r io.Reader, n int) {
|
||||
d.reader = r
|
||||
d.remain = n
|
||||
d.buffer = [8]byte{}
|
||||
d.err = nil
|
||||
d.table = nil
|
||||
d.crc32 = 0
|
||||
}
|
||||
|
||||
func (d *decoder) Read(b []byte) (int, error) {
|
||||
if d.err != nil {
|
||||
return 0, d.err
|
||||
}
|
||||
if d.remain == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if len(b) > d.remain {
|
||||
b = b[:d.remain]
|
||||
}
|
||||
n, err := d.reader.Read(b)
|
||||
if n > 0 && d.table != nil {
|
||||
d.crc32 = crc32.Update(d.crc32, d.table, b[:n])
|
||||
}
|
||||
d.remain -= n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (d *decoder) ReadByte() (byte, error) {
|
||||
c := d.readByte()
|
||||
return c, d.err
|
||||
}
|
||||
|
||||
func (d *decoder) done() bool {
|
||||
return d.remain == 0 || d.err != nil
|
||||
}
|
||||
|
||||
func (d *decoder) setCRC(table *crc32.Table) {
|
||||
d.table, d.crc32 = table, 0
|
||||
}
|
||||
|
||||
func (d *decoder) decodeBool(v value) {
|
||||
v.setBool(d.readBool())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeInt8(v value) {
|
||||
v.setInt8(d.readInt8())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeInt16(v value) {
|
||||
v.setInt16(d.readInt16())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeInt32(v value) {
|
||||
v.setInt32(d.readInt32())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeInt64(v value) {
|
||||
v.setInt64(d.readInt64())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeFloat64(v value) {
|
||||
v.setFloat64(d.readFloat64())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeString(v value) {
|
||||
v.setString(d.readString())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeCompactString(v value) {
|
||||
v.setString(d.readCompactString())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeBytes(v value) {
|
||||
v.setBytes(d.readBytes())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeCompactBytes(v value) {
|
||||
v.setBytes(d.readCompactBytes())
|
||||
}
|
||||
|
||||
func (d *decoder) decodeArray(v value, elemType reflect.Type, decodeElem decodeFunc) {
|
||||
if n := d.readInt32(); n < 0 {
|
||||
v.setArray(array{})
|
||||
} else {
|
||||
a := makeArray(elemType, int(n))
|
||||
for i := 0; i < int(n) && d.remain > 0; i++ {
|
||||
decodeElem(d, a.index(i))
|
||||
}
|
||||
v.setArray(a)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeCompactArray(v value, elemType reflect.Type, decodeElem decodeFunc) {
|
||||
if n := d.readUnsignedVarInt(); n < 1 {
|
||||
v.setArray(array{})
|
||||
} else {
|
||||
a := makeArray(elemType, int(n-1))
|
||||
for i := 0; i < int(n-1) && d.remain > 0; i++ {
|
||||
decodeElem(d, a.index(i))
|
||||
}
|
||||
v.setArray(a)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) discardAll() {
|
||||
d.discard(d.remain)
|
||||
}
|
||||
|
||||
func (d *decoder) discard(n int) {
|
||||
if n > d.remain {
|
||||
n = d.remain
|
||||
}
|
||||
var err error
|
||||
if r, _ := d.reader.(discarder); r != nil {
|
||||
n, err = r.Discard(n)
|
||||
d.remain -= n
|
||||
} else {
|
||||
_, err = io.Copy(ioutil.Discard, d)
|
||||
}
|
||||
d.setError(err)
|
||||
}
|
||||
|
||||
func (d *decoder) read(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
n, err := io.ReadFull(d, b)
|
||||
b = b[:n]
|
||||
d.setError(err)
|
||||
return b
|
||||
}
|
||||
|
||||
func (d *decoder) writeTo(w io.Writer, n int) {
|
||||
limit := d.remain
|
||||
if n < limit {
|
||||
d.remain = n
|
||||
}
|
||||
c, err := io.Copy(w, d)
|
||||
if int(c) < n && err == nil {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
d.remain = limit - int(c)
|
||||
d.setError(err)
|
||||
}
|
||||
|
||||
func (d *decoder) setError(err error) {
|
||||
if d.err == nil && err != nil {
|
||||
d.err = err
|
||||
d.discardAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) readFull(b []byte) bool {
|
||||
n, err := io.ReadFull(d, b)
|
||||
d.setError(err)
|
||||
return n == len(b)
|
||||
}
|
||||
|
||||
func (d *decoder) readByte() byte {
|
||||
if d.readFull(d.buffer[:1]) {
|
||||
return d.buffer[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *decoder) readBool() bool {
|
||||
return d.readByte() != 0
|
||||
}
|
||||
|
||||
func (d *decoder) readInt8() int8 {
|
||||
if d.readFull(d.buffer[:1]) {
|
||||
return readInt8(d.buffer[:1])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *decoder) readInt16() int16 {
|
||||
if d.readFull(d.buffer[:2]) {
|
||||
return readInt16(d.buffer[:2])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *decoder) readInt32() int32 {
|
||||
if d.readFull(d.buffer[:4]) {
|
||||
return readInt32(d.buffer[:4])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *decoder) readInt64() int64 {
|
||||
if d.readFull(d.buffer[:8]) {
|
||||
return readInt64(d.buffer[:8])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *decoder) readFloat64() float64 {
|
||||
if d.readFull(d.buffer[:8]) {
|
||||
return readFloat64(d.buffer[:8])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *decoder) readString() string {
|
||||
if n := d.readInt16(); n < 0 {
|
||||
return ""
|
||||
} else {
|
||||
return bytesToString(d.read(int(n)))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) readVarString() string {
|
||||
if n := d.readVarInt(); n < 0 {
|
||||
return ""
|
||||
} else {
|
||||
return bytesToString(d.read(int(n)))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) readCompactString() string {
|
||||
if n := d.readUnsignedVarInt(); n < 1 {
|
||||
return ""
|
||||
} else {
|
||||
return bytesToString(d.read(int(n - 1)))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) readBytes() []byte {
|
||||
if n := d.readInt32(); n < 0 {
|
||||
return nil
|
||||
} else {
|
||||
return d.read(int(n))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) readVarBytes() []byte {
|
||||
if n := d.readVarInt(); n < 0 {
|
||||
return nil
|
||||
} else {
|
||||
return d.read(int(n))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) readCompactBytes() []byte {
|
||||
if n := d.readUnsignedVarInt(); n < 1 {
|
||||
return nil
|
||||
} else {
|
||||
return d.read(int(n - 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) readVarInt() int64 {
|
||||
n := 11 // varints are at most 11 bytes
|
||||
|
||||
if n > d.remain {
|
||||
n = d.remain
|
||||
}
|
||||
|
||||
x := uint64(0)
|
||||
s := uint(0)
|
||||
|
||||
for n > 0 {
|
||||
b := d.readByte()
|
||||
|
||||
if (b & 0x80) == 0 {
|
||||
x |= uint64(b) << s
|
||||
return int64(x>>1) ^ -(int64(x) & 1)
|
||||
}
|
||||
|
||||
x |= uint64(b&0x7f) << s
|
||||
s += 7
|
||||
n--
|
||||
}
|
||||
|
||||
d.setError(fmt.Errorf("cannot decode varint from input stream"))
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *decoder) readUnsignedVarInt() uint64 {
|
||||
n := 11 // varints are at most 11 bytes
|
||||
|
||||
if n > d.remain {
|
||||
n = d.remain
|
||||
}
|
||||
|
||||
x := uint64(0)
|
||||
s := uint(0)
|
||||
|
||||
for n > 0 {
|
||||
b := d.readByte()
|
||||
|
||||
if (b & 0x80) == 0 {
|
||||
x |= uint64(b) << s
|
||||
return x
|
||||
}
|
||||
|
||||
x |= uint64(b&0x7f) << s
|
||||
s += 7
|
||||
n--
|
||||
}
|
||||
|
||||
d.setError(fmt.Errorf("cannot decode unsigned varint from input stream"))
|
||||
return 0
|
||||
}
|
||||
|
||||
type decodeFunc func(*decoder, value)
|
||||
|
||||
var (
|
||||
_ io.Reader = (*decoder)(nil)
|
||||
_ io.ByteReader = (*decoder)(nil)
|
||||
|
||||
readerFrom = reflect.TypeOf((*io.ReaderFrom)(nil)).Elem()
|
||||
)
|
||||
|
||||
func decodeFuncOf(typ reflect.Type, version int16, flexible bool, tag structTag) decodeFunc {
|
||||
if reflect.PtrTo(typ).Implements(readerFrom) {
|
||||
return readerDecodeFuncOf(typ)
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
return (*decoder).decodeBool
|
||||
case reflect.Int8:
|
||||
return (*decoder).decodeInt8
|
||||
case reflect.Int16:
|
||||
return (*decoder).decodeInt16
|
||||
case reflect.Int32:
|
||||
return (*decoder).decodeInt32
|
||||
case reflect.Int64:
|
||||
return (*decoder).decodeInt64
|
||||
case reflect.Float64:
|
||||
return (*decoder).decodeFloat64
|
||||
case reflect.String:
|
||||
return stringDecodeFuncOf(flexible, tag)
|
||||
case reflect.Struct:
|
||||
return structDecodeFuncOf(typ, version, flexible)
|
||||
case reflect.Slice:
|
||||
if typ.Elem().Kind() == reflect.Uint8 { // []byte
|
||||
return bytesDecodeFuncOf(flexible, tag)
|
||||
}
|
||||
return arrayDecodeFuncOf(typ, version, flexible, tag)
|
||||
default:
|
||||
panic("unsupported type: " + typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
func stringDecodeFuncOf(flexible bool, tag structTag) decodeFunc {
|
||||
if flexible {
|
||||
// In flexible messages, all strings are compact
|
||||
return (*decoder).decodeCompactString
|
||||
}
|
||||
return (*decoder).decodeString
|
||||
}
|
||||
|
||||
func bytesDecodeFuncOf(flexible bool, tag structTag) decodeFunc {
|
||||
if flexible {
|
||||
// In flexible messages, all arrays are compact
|
||||
return (*decoder).decodeCompactBytes
|
||||
}
|
||||
return (*decoder).decodeBytes
|
||||
}
|
||||
|
||||
func structDecodeFuncOf(typ reflect.Type, version int16, flexible bool) decodeFunc {
|
||||
type field struct {
|
||||
decode decodeFunc
|
||||
index index
|
||||
tagID int
|
||||
}
|
||||
|
||||
var fields []field
|
||||
taggedFields := map[int]*field{}
|
||||
|
||||
forEachStructField(typ, func(typ reflect.Type, index index, tag string) {
|
||||
forEachStructTag(tag, func(tag structTag) bool {
|
||||
if tag.MinVersion <= version && version <= tag.MaxVersion {
|
||||
f := field{
|
||||
decode: decodeFuncOf(typ, version, flexible, tag),
|
||||
index: index,
|
||||
tagID: tag.TagID,
|
||||
}
|
||||
|
||||
if tag.TagID < -1 {
|
||||
// Normal required field
|
||||
fields = append(fields, f)
|
||||
} else {
|
||||
// Optional tagged field (flexible messages only)
|
||||
taggedFields[tag.TagID] = &f
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
return func(d *decoder, v value) {
|
||||
for i := range fields {
|
||||
f := &fields[i]
|
||||
f.decode(d, v.fieldByIndex(f.index))
|
||||
}
|
||||
|
||||
if flexible {
|
||||
// See https://cwiki.apache.org/confluence/display/KAFKA/KIP-482%3A+The+Kafka+Protocol+should+Support+Optional+Tagged+Fields
|
||||
// for details of tag buffers in "flexible" messages.
|
||||
n := int(d.readUnsignedVarInt())
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
tagID := int(d.readUnsignedVarInt())
|
||||
size := int(d.readUnsignedVarInt())
|
||||
|
||||
f, ok := taggedFields[tagID]
|
||||
if ok {
|
||||
f.decode(d, v.fieldByIndex(f.index))
|
||||
} else {
|
||||
d.read(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func arrayDecodeFuncOf(typ reflect.Type, version int16, flexible bool, tag structTag) decodeFunc {
|
||||
elemType := typ.Elem()
|
||||
elemFunc := decodeFuncOf(elemType, version, flexible, tag)
|
||||
if flexible {
|
||||
// In flexible messages, all arrays are compact
|
||||
return func(d *decoder, v value) { d.decodeCompactArray(v, elemType, elemFunc) }
|
||||
}
|
||||
|
||||
return func(d *decoder, v value) { d.decodeArray(v, elemType, elemFunc) }
|
||||
}
|
||||
|
||||
func readerDecodeFuncOf(typ reflect.Type) decodeFunc {
|
||||
typ = reflect.PtrTo(typ)
|
||||
return func(d *decoder, v value) {
|
||||
if d.err == nil {
|
||||
_, err := v.iface(typ).(io.ReaderFrom).ReadFrom(d)
|
||||
if err != nil {
|
||||
d.setError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readInt8(b []byte) int8 {
|
||||
return int8(b[0])
|
||||
}
|
||||
|
||||
func readInt16(b []byte) int16 {
|
||||
return int16(binary.BigEndian.Uint16(b))
|
||||
}
|
||||
|
||||
func readInt32(b []byte) int32 {
|
||||
return int32(binary.BigEndian.Uint32(b))
|
||||
}
|
||||
|
||||
func readInt64(b []byte) int64 {
|
||||
return int64(binary.BigEndian.Uint64(b))
|
||||
}
|
||||
|
||||
func readFloat64(b []byte) float64 {
|
||||
return math.Float64frombits(binary.BigEndian.Uint64(b))
|
||||
}
|
||||
|
||||
func Unmarshal(data []byte, version int16, value interface{}) error {
|
||||
typ := elemTypeOf(value)
|
||||
cache, _ := unmarshalers.Load().(map[versionedType]decodeFunc)
|
||||
key := versionedType{typ: typ, version: version}
|
||||
decode := cache[key]
|
||||
|
||||
if decode == nil {
|
||||
decode = decodeFuncOf(reflect.TypeOf(value).Elem(), version, false, structTag{
|
||||
MinVersion: -1,
|
||||
MaxVersion: -1,
|
||||
TagID: -2,
|
||||
Compact: true,
|
||||
Nullable: true,
|
||||
})
|
||||
|
||||
newCache := make(map[versionedType]decodeFunc, len(cache)+1)
|
||||
newCache[key] = decode
|
||||
|
||||
for typ, fun := range cache {
|
||||
newCache[typ] = fun
|
||||
}
|
||||
|
||||
unmarshalers.Store(newCache)
|
||||
}
|
||||
|
||||
d, _ := decoders.Get().(*decoder)
|
||||
if d == nil {
|
||||
d = &decoder{reader: bytes.NewReader(nil)}
|
||||
}
|
||||
|
||||
d.remain = len(data)
|
||||
r, _ := d.reader.(*bytes.Reader)
|
||||
r.Reset(data)
|
||||
|
||||
defer func() {
|
||||
r.Reset(nil)
|
||||
d.Reset(r, 0)
|
||||
decoders.Put(d)
|
||||
}()
|
||||
|
||||
decode(d, valueOf(value))
|
||||
return dontExpectEOF(d.err)
|
||||
}
|
||||
|
||||
var (
|
||||
decoders sync.Pool // *decoder
|
||||
unmarshalers atomic.Value // map[versionedType]decodeFunc
|
||||
)
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package deleteacls
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
Filters []RequestFilter `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DeleteAcls }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type RequestFilter struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ResourceTypeFilter int8 `kafka:"min=v0,max=v3"`
|
||||
ResourceNameFilter string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
ResourcePatternTypeFilter int8 `kafka:"min=v1,max=v3"`
|
||||
PrincipalFilter string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
HostFilter string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
Operation int8 `kafka:"min=v0,max=v3"`
|
||||
PermissionType int8 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
FilterResults []FilterResult `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DeleteAcls }
|
||||
|
||||
type FilterResult struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
MatchingACLs []MatchingACL `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type MatchingACL struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
ResourceType int8 `kafka:"min=v0,max=v3"`
|
||||
ResourceName string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
ResourcePatternType int8 `kafka:"min=v1,max=v3"`
|
||||
Principal string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
Host string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
Operation int8 `kafka:"min=v0,max=v3"`
|
||||
PermissionType int8 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
package deletegroups
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v2,max=v2,tag"`
|
||||
|
||||
GroupIDs []string `kafka:"min=v0,max=v2"`
|
||||
}
|
||||
|
||||
func (r *Request) Group() string {
|
||||
// use first group to determine group coordinator
|
||||
if len(r.GroupIDs) > 0 {
|
||||
return r.GroupIDs[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DeleteGroups }
|
||||
|
||||
var (
|
||||
_ protocol.GroupMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v2,max=v2,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v2"`
|
||||
Responses []ResponseGroup `kafka:"min=v0,max=v2"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DeleteGroups }
|
||||
|
||||
type ResponseGroup struct {
|
||||
GroupID string `kafka:"min=v0,max=v2"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v2"`
|
||||
}
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
package deletetopics
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
TopicNames []string `kafka:"min=v0,max=v3"`
|
||||
TimeoutMs int32 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DeleteTopics }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v3"`
|
||||
Responses []ResponseTopic `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DeleteTopics }
|
||||
|
||||
type ResponseTopic struct {
|
||||
Name string `kafka:"min=v0,max=v3"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.BrokerMessage = (*Request)(nil)
|
||||
)
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
package describeacls
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
Filter ACLFilter `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DescribeAcls }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type ACLFilter struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ResourceTypeFilter int8 `kafka:"min=v0,max=v3"`
|
||||
ResourceNameFilter string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
ResourcePatternTypeFilter int8 `kafka:"min=v1,max=v3"`
|
||||
PrincipalFilter string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
HostFilter string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
Operation int8 `kafka:"min=v0,max=v3"`
|
||||
PermissionType int8 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable|min=v2,max=v3,nullable,compact"`
|
||||
Resources []Resource `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DescribeAcls }
|
||||
|
||||
type Resource struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
ResourceType int8 `kafka:"min=v0,max=v3"`
|
||||
ResourceName string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
PatternType int8 `kafka:"min=v1,max=v3"`
|
||||
ACLs []ResponseACL `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type ResponseACL struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v2,max=v3,tag"`
|
||||
|
||||
Principal string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
Host string `kafka:"min=v0,max=v1|min=v2,max=v3,compact"`
|
||||
Operation int8 `kafka:"min=v0,max=v3"`
|
||||
PermissionType int8 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
package describeclientquotas
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
Components []Component `kafka:"min=v0,max=v1"`
|
||||
Strict bool `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DescribeClientQuotas }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type Component struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
EntityType string `kafka:"min=v0,max=v1"`
|
||||
MatchType int8 `kafka:"min=v0,max=v1"`
|
||||
Match string `kafka:"min=v0,max=v1,nullable"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v1"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v1"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable|min=v1,max=v1,nullable,compact"`
|
||||
Entries []ResponseQuotas `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DescribeClientQuotas }
|
||||
|
||||
type Entity struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
EntityType string `kafka:"min=v0,max=v0|min=v1,max=v1,compact"`
|
||||
EntityName string `kafka:"min=v0,max=v0,nullable|min=v1,max=v1,nullable,compact"`
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
Key string `kafka:"min=v0,max=v0|min=v1,max=v1,compact"`
|
||||
Value float64 `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type ResponseQuotas struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v1,max=v1,tag"`
|
||||
Entities []Entity `kafka:"min=v0,max=v1"`
|
||||
Values []Value `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+129
@@ -0,0 +1,129 @@
|
||||
package describeconfigs
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceTypeBroker int8 = 4
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_DescribeConfigs
|
||||
type Request struct {
|
||||
Resources []RequestResource `kafka:"min=v0,max=v3"`
|
||||
IncludeSynonyms bool `kafka:"min=v1,max=v3"`
|
||||
IncludeDocumentation bool `kafka:"min=v3,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DescribeConfigs }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
// Broker metadata requests must be sent to the associated broker
|
||||
for _, resource := range r.Resources {
|
||||
if resource.ResourceType == resourceTypeBroker {
|
||||
brokerID, err := strconv.Atoi(resource.ResourceName)
|
||||
if err != nil {
|
||||
return protocol.Broker{}, err
|
||||
}
|
||||
|
||||
return cluster.Brokers[int32(brokerID)], nil
|
||||
}
|
||||
}
|
||||
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
func (r *Request) Split(cluster protocol.Cluster) (
|
||||
[]protocol.Message,
|
||||
protocol.Merger,
|
||||
error,
|
||||
) {
|
||||
messages := []protocol.Message{}
|
||||
topicsMessage := Request{}
|
||||
|
||||
for _, resource := range r.Resources {
|
||||
// Split out broker requests to separate brokers
|
||||
if resource.ResourceType == resourceTypeBroker {
|
||||
messages = append(messages, &Request{
|
||||
Resources: []RequestResource{resource},
|
||||
})
|
||||
} else {
|
||||
topicsMessage.Resources = append(
|
||||
topicsMessage.Resources, resource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(topicsMessage.Resources) > 0 {
|
||||
messages = append(messages, &topicsMessage)
|
||||
}
|
||||
|
||||
return messages, new(Response), nil
|
||||
}
|
||||
|
||||
type RequestResource struct {
|
||||
ResourceType int8 `kafka:"min=v0,max=v3"`
|
||||
ResourceName string `kafka:"min=v0,max=v3"`
|
||||
ConfigNames []string `kafka:"min=v0,max=v3,nullable"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
Resources []ResponseResource `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DescribeConfigs }
|
||||
|
||||
func (r *Response) Merge(requests []protocol.Message, results []interface{}) (
|
||||
protocol.Message,
|
||||
error,
|
||||
) {
|
||||
response := &Response{}
|
||||
|
||||
for _, result := range results {
|
||||
m, err := protocol.Result(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.Resources = append(
|
||||
response.Resources,
|
||||
m.(*Response).Resources...,
|
||||
)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type ResponseResource struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v3,nullable"`
|
||||
ResourceType int8 `kafka:"min=v0,max=v3"`
|
||||
ResourceName string `kafka:"min=v0,max=v3"`
|
||||
ConfigEntries []ResponseConfigEntry `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type ResponseConfigEntry struct {
|
||||
ConfigName string `kafka:"min=v0,max=v3"`
|
||||
ConfigValue string `kafka:"min=v0,max=v3,nullable"`
|
||||
ReadOnly bool `kafka:"min=v0,max=v3"`
|
||||
IsDefault bool `kafka:"min=v0,max=v0"`
|
||||
ConfigSource int8 `kafka:"min=v1,max=v3"`
|
||||
IsSensitive bool `kafka:"min=v0,max=v3"`
|
||||
ConfigSynonyms []ResponseConfigSynonym `kafka:"min=v1,max=v3"`
|
||||
ConfigType int8 `kafka:"min=v3,max=v3"`
|
||||
ConfigDocumentation string `kafka:"min=v3,max=v3,nullable"`
|
||||
}
|
||||
|
||||
type ResponseConfigSynonym struct {
|
||||
ConfigName string `kafka:"min=v1,max=v3"`
|
||||
ConfigValue string `kafka:"min=v1,max=v3,nullable"`
|
||||
ConfigSource int8 `kafka:"min=v1,max=v3"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
package describegroups
|
||||
|
||||
import (
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_DescribeGroups
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v5,max=v5,tag"`
|
||||
Groups []string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
IncludeAuthorizedOperations bool `kafka:"min=v3,max=v5"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DescribeGroups }
|
||||
|
||||
func (r *Request) Group() string {
|
||||
return r.Groups[0]
|
||||
}
|
||||
|
||||
func (r *Request) Split(cluster protocol.Cluster) (
|
||||
[]protocol.Message,
|
||||
protocol.Merger,
|
||||
error,
|
||||
) {
|
||||
messages := []protocol.Message{}
|
||||
|
||||
// Split requests by group since they'll need to go to different coordinators.
|
||||
for _, group := range r.Groups {
|
||||
messages = append(
|
||||
messages,
|
||||
&Request{
|
||||
Groups: []string{group},
|
||||
IncludeAuthorizedOperations: r.IncludeAuthorizedOperations,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return messages, new(Response), nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v5,max=v5,tag"`
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v5"`
|
||||
Groups []ResponseGroup `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
|
||||
type ResponseGroup struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v5,max=v5,tag"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v5"`
|
||||
GroupID string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
GroupState string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
ProtocolType string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
ProtocolData string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
Members []ResponseGroupMember `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
AuthorizedOperations int32 `kafka:"min=v3,max=v5"`
|
||||
}
|
||||
|
||||
type ResponseGroupMember struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v5,max=v5,tag"`
|
||||
MemberID string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
GroupInstanceID string `kafka:"min=v4,max=v4,nullable|min=v5,max=v5,compact,nullable"`
|
||||
ClientID string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
ClientHost string `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
MemberMetadata []byte `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
MemberAssignment []byte `kafka:"min=v0,max=v4|min=v5,max=v5,compact"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DescribeGroups }
|
||||
|
||||
func (r *Response) Merge(requests []protocol.Message, results []interface{}) (
|
||||
protocol.Message,
|
||||
error,
|
||||
) {
|
||||
response := &Response{}
|
||||
|
||||
for _, result := range results {
|
||||
m, err := protocol.Result(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.Groups = append(response.Groups, m.(*Response).Groups...)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
Generated
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
package describeuserscramcredentials
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Users []RequestUser `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.DescribeUserScramCredentials }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type RequestUser struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v0,compact"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v0"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable"`
|
||||
Results []ResponseResult `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.DescribeUserScramCredentials }
|
||||
|
||||
type ResponseResult struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
User string `kafka:"min=v0,max=v0,compact"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable"`
|
||||
CredentialInfos []CredentialInfo `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type CredentialInfo struct {
|
||||
// We need at least one tagged field to indicate that v2+ uses "flexible"
|
||||
// messages.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Mechanism int8 `kafka:"min=v0,max=v0"`
|
||||
Iterations int32 `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
var _ protocol.BrokerMessage = (*Request)(nil)
|
||||
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
package electleaders
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_ElectLeaders
|
||||
type Request struct {
|
||||
ElectionType int8 `kafka:"min=v1,max=v1"`
|
||||
TopicPartitions []RequestTopicPartitions `kafka:"min=v0,max=v1"`
|
||||
TimeoutMs int32 `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type RequestTopicPartitions struct {
|
||||
Topic string `kafka:"min=v0,max=v1"`
|
||||
PartitionIDs []int32 `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.ElectLeaders }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTime int32 `kafka:"min=v0,max=v1"`
|
||||
ErrorCode int16 `kafka:"min=v1,max=v1"`
|
||||
ReplicaElectionResults []ResponseReplicaElectionResult `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type ResponseReplicaElectionResult struct {
|
||||
Topic string `kafka:"min=v0,max=v1"`
|
||||
PartitionResults []ResponsePartitionResult `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
type ResponsePartitionResult struct {
|
||||
PartitionID int32 `kafka:"min=v0,max=v1"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v1"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.ElectLeaders }
|
||||
+606
@@ -0,0 +1,606 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
writer io.Writer
|
||||
err error
|
||||
table *crc32.Table
|
||||
crc32 uint32
|
||||
buffer [32]byte
|
||||
}
|
||||
|
||||
type encoderChecksum struct {
|
||||
reader io.Reader
|
||||
encoder *encoder
|
||||
}
|
||||
|
||||
func (e *encoderChecksum) Read(b []byte) (int, error) {
|
||||
n, err := e.reader.Read(b)
|
||||
if n > 0 {
|
||||
e.encoder.update(b[:n])
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (e *encoder) Reset(w io.Writer) {
|
||||
e.writer = w
|
||||
e.err = nil
|
||||
e.table = nil
|
||||
e.crc32 = 0
|
||||
e.buffer = [32]byte{}
|
||||
}
|
||||
|
||||
func (e *encoder) ReadFrom(r io.Reader) (int64, error) {
|
||||
if e.table != nil {
|
||||
r = &encoderChecksum{
|
||||
reader: r,
|
||||
encoder: e,
|
||||
}
|
||||
}
|
||||
return io.Copy(e.writer, r)
|
||||
}
|
||||
|
||||
func (e *encoder) Write(b []byte) (int, error) {
|
||||
if e.err != nil {
|
||||
return 0, e.err
|
||||
}
|
||||
n, err := e.writer.Write(b)
|
||||
if n > 0 {
|
||||
e.update(b[:n])
|
||||
}
|
||||
if err != nil {
|
||||
e.err = err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (e *encoder) WriteByte(b byte) error {
|
||||
e.buffer[0] = b
|
||||
_, err := e.Write(e.buffer[:1])
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *encoder) WriteString(s string) (int, error) {
|
||||
// This implementation is an optimization to avoid the heap allocation that
|
||||
// would occur when converting the string to a []byte to call crc32.Update.
|
||||
//
|
||||
// Strings are rarely long in the kafka protocol, so the use of a 32 byte
|
||||
// buffer is a good comprise between keeping the encoder value small and
|
||||
// limiting the number of calls to Write.
|
||||
//
|
||||
// We introduced this optimization because memory profiles on the benchmarks
|
||||
// showed that most heap allocations were caused by this code path.
|
||||
n := 0
|
||||
|
||||
for len(s) != 0 {
|
||||
c := copy(e.buffer[:], s)
|
||||
w, err := e.Write(e.buffer[:c])
|
||||
n += w
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
s = s[c:]
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *encoder) setCRC(table *crc32.Table) {
|
||||
e.table, e.crc32 = table, 0
|
||||
}
|
||||
|
||||
func (e *encoder) update(b []byte) {
|
||||
if e.table != nil {
|
||||
e.crc32 = crc32.Update(e.crc32, e.table, b)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) encodeBool(v value) {
|
||||
b := int8(0)
|
||||
if v.bool() {
|
||||
b = 1
|
||||
}
|
||||
e.writeInt8(b)
|
||||
}
|
||||
|
||||
func (e *encoder) encodeInt8(v value) {
|
||||
e.writeInt8(v.int8())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeInt16(v value) {
|
||||
e.writeInt16(v.int16())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeInt32(v value) {
|
||||
e.writeInt32(v.int32())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeInt64(v value) {
|
||||
e.writeInt64(v.int64())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeFloat64(v value) {
|
||||
e.writeFloat64(v.float64())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeString(v value) {
|
||||
e.writeString(v.string())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeCompactString(v value) {
|
||||
e.writeCompactString(v.string())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeNullString(v value) {
|
||||
e.writeNullString(v.string())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeCompactNullString(v value) {
|
||||
e.writeCompactNullString(v.string())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeBytes(v value) {
|
||||
e.writeBytes(v.bytes())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeCompactBytes(v value) {
|
||||
e.writeCompactBytes(v.bytes())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeNullBytes(v value) {
|
||||
e.writeNullBytes(v.bytes())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeCompactNullBytes(v value) {
|
||||
e.writeCompactNullBytes(v.bytes())
|
||||
}
|
||||
|
||||
func (e *encoder) encodeArray(v value, elemType reflect.Type, encodeElem encodeFunc) {
|
||||
a := v.array(elemType)
|
||||
n := a.length()
|
||||
e.writeInt32(int32(n))
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
encodeElem(e, a.index(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) encodeCompactArray(v value, elemType reflect.Type, encodeElem encodeFunc) {
|
||||
a := v.array(elemType)
|
||||
n := a.length()
|
||||
e.writeUnsignedVarInt(uint64(n + 1))
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
encodeElem(e, a.index(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) encodeNullArray(v value, elemType reflect.Type, encodeElem encodeFunc) {
|
||||
a := v.array(elemType)
|
||||
if a.isNil() {
|
||||
e.writeInt32(-1)
|
||||
return
|
||||
}
|
||||
|
||||
n := a.length()
|
||||
e.writeInt32(int32(n))
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
encodeElem(e, a.index(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) encodeCompactNullArray(v value, elemType reflect.Type, encodeElem encodeFunc) {
|
||||
a := v.array(elemType)
|
||||
if a.isNil() {
|
||||
e.writeUnsignedVarInt(0)
|
||||
return
|
||||
}
|
||||
|
||||
n := a.length()
|
||||
e.writeUnsignedVarInt(uint64(n + 1))
|
||||
for i := 0; i < n; i++ {
|
||||
encodeElem(e, a.index(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeInt8(i int8) {
|
||||
writeInt8(e.buffer[:1], i)
|
||||
e.Write(e.buffer[:1])
|
||||
}
|
||||
|
||||
func (e *encoder) writeInt16(i int16) {
|
||||
writeInt16(e.buffer[:2], i)
|
||||
e.Write(e.buffer[:2])
|
||||
}
|
||||
|
||||
func (e *encoder) writeInt32(i int32) {
|
||||
writeInt32(e.buffer[:4], i)
|
||||
e.Write(e.buffer[:4])
|
||||
}
|
||||
|
||||
func (e *encoder) writeInt64(i int64) {
|
||||
writeInt64(e.buffer[:8], i)
|
||||
e.Write(e.buffer[:8])
|
||||
}
|
||||
|
||||
func (e *encoder) writeFloat64(f float64) {
|
||||
writeFloat64(e.buffer[:8], f)
|
||||
e.Write(e.buffer[:8])
|
||||
}
|
||||
|
||||
func (e *encoder) writeString(s string) {
|
||||
e.writeInt16(int16(len(s)))
|
||||
e.WriteString(s)
|
||||
}
|
||||
|
||||
func (e *encoder) writeVarString(s string) {
|
||||
e.writeVarInt(int64(len(s)))
|
||||
e.WriteString(s)
|
||||
}
|
||||
|
||||
func (e *encoder) writeCompactString(s string) {
|
||||
e.writeUnsignedVarInt(uint64(len(s)) + 1)
|
||||
e.WriteString(s)
|
||||
}
|
||||
|
||||
func (e *encoder) writeNullString(s string) {
|
||||
if s == "" {
|
||||
e.writeInt16(-1)
|
||||
} else {
|
||||
e.writeInt16(int16(len(s)))
|
||||
e.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeCompactNullString(s string) {
|
||||
if s == "" {
|
||||
e.writeUnsignedVarInt(0)
|
||||
} else {
|
||||
e.writeUnsignedVarInt(uint64(len(s)) + 1)
|
||||
e.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeBytes(b []byte) {
|
||||
e.writeInt32(int32(len(b)))
|
||||
e.Write(b)
|
||||
}
|
||||
|
||||
func (e *encoder) writeCompactBytes(b []byte) {
|
||||
e.writeUnsignedVarInt(uint64(len(b)) + 1)
|
||||
e.Write(b)
|
||||
}
|
||||
|
||||
func (e *encoder) writeNullBytes(b []byte) {
|
||||
if b == nil {
|
||||
e.writeInt32(-1)
|
||||
} else {
|
||||
e.writeInt32(int32(len(b)))
|
||||
e.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeVarNullBytes(b []byte) {
|
||||
if b == nil {
|
||||
e.writeVarInt(-1)
|
||||
} else {
|
||||
e.writeVarInt(int64(len(b)))
|
||||
e.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeCompactNullBytes(b []byte) {
|
||||
if b == nil {
|
||||
e.writeUnsignedVarInt(0)
|
||||
} else {
|
||||
e.writeUnsignedVarInt(uint64(len(b)) + 1)
|
||||
e.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeNullBytesFrom(b Bytes) error {
|
||||
if b == nil {
|
||||
e.writeInt32(-1)
|
||||
return nil
|
||||
} else {
|
||||
size := int64(b.Len())
|
||||
e.writeInt32(int32(size))
|
||||
n, err := io.Copy(e, b)
|
||||
if err == nil && n != size {
|
||||
err = fmt.Errorf("size of nullable bytes does not match the number of bytes that were written (size=%d, written=%d): %w", size, n, io.ErrUnexpectedEOF)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeVarNullBytesFrom(b Bytes) error {
|
||||
if b == nil {
|
||||
e.writeVarInt(-1)
|
||||
return nil
|
||||
} else {
|
||||
size := int64(b.Len())
|
||||
e.writeVarInt(size)
|
||||
n, err := io.Copy(e, b)
|
||||
if err == nil && n != size {
|
||||
err = fmt.Errorf("size of nullable bytes does not match the number of bytes that were written (size=%d, written=%d): %w", size, n, io.ErrUnexpectedEOF)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeVarInt(i int64) {
|
||||
e.writeUnsignedVarInt(uint64((i << 1) ^ (i >> 63)))
|
||||
}
|
||||
|
||||
func (e *encoder) writeUnsignedVarInt(i uint64) {
|
||||
b := e.buffer[:]
|
||||
n := 0
|
||||
|
||||
for i >= 0x80 && n < len(b) {
|
||||
b[n] = byte(i) | 0x80
|
||||
i >>= 7
|
||||
n++
|
||||
}
|
||||
|
||||
if n < len(b) {
|
||||
b[n] = byte(i)
|
||||
n++
|
||||
}
|
||||
|
||||
e.Write(b[:n])
|
||||
}
|
||||
|
||||
type encodeFunc func(*encoder, value)
|
||||
|
||||
var (
|
||||
_ io.ReaderFrom = (*encoder)(nil)
|
||||
_ io.Writer = (*encoder)(nil)
|
||||
_ io.ByteWriter = (*encoder)(nil)
|
||||
_ io.StringWriter = (*encoder)(nil)
|
||||
|
||||
writerTo = reflect.TypeOf((*io.WriterTo)(nil)).Elem()
|
||||
)
|
||||
|
||||
func encodeFuncOf(typ reflect.Type, version int16, flexible bool, tag structTag) encodeFunc {
|
||||
if reflect.PtrTo(typ).Implements(writerTo) {
|
||||
return writerEncodeFuncOf(typ)
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
return (*encoder).encodeBool
|
||||
case reflect.Int8:
|
||||
return (*encoder).encodeInt8
|
||||
case reflect.Int16:
|
||||
return (*encoder).encodeInt16
|
||||
case reflect.Int32:
|
||||
return (*encoder).encodeInt32
|
||||
case reflect.Int64:
|
||||
return (*encoder).encodeInt64
|
||||
case reflect.Float64:
|
||||
return (*encoder).encodeFloat64
|
||||
case reflect.String:
|
||||
return stringEncodeFuncOf(flexible, tag)
|
||||
case reflect.Struct:
|
||||
return structEncodeFuncOf(typ, version, flexible)
|
||||
case reflect.Slice:
|
||||
if typ.Elem().Kind() == reflect.Uint8 { // []byte
|
||||
return bytesEncodeFuncOf(flexible, tag)
|
||||
}
|
||||
return arrayEncodeFuncOf(typ, version, flexible, tag)
|
||||
default:
|
||||
panic("unsupported type: " + typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
func stringEncodeFuncOf(flexible bool, tag structTag) encodeFunc {
|
||||
switch {
|
||||
case flexible && tag.Nullable:
|
||||
// In flexible messages, all strings are compact
|
||||
return (*encoder).encodeCompactNullString
|
||||
case flexible:
|
||||
// In flexible messages, all strings are compact
|
||||
return (*encoder).encodeCompactString
|
||||
case tag.Nullable:
|
||||
return (*encoder).encodeNullString
|
||||
default:
|
||||
return (*encoder).encodeString
|
||||
}
|
||||
}
|
||||
|
||||
func bytesEncodeFuncOf(flexible bool, tag structTag) encodeFunc {
|
||||
switch {
|
||||
case flexible && tag.Nullable:
|
||||
// In flexible messages, all arrays are compact
|
||||
return (*encoder).encodeCompactNullBytes
|
||||
case flexible:
|
||||
// In flexible messages, all arrays are compact
|
||||
return (*encoder).encodeCompactBytes
|
||||
case tag.Nullable:
|
||||
return (*encoder).encodeNullBytes
|
||||
default:
|
||||
return (*encoder).encodeBytes
|
||||
}
|
||||
}
|
||||
|
||||
func structEncodeFuncOf(typ reflect.Type, version int16, flexible bool) encodeFunc {
|
||||
type field struct {
|
||||
encode encodeFunc
|
||||
index index
|
||||
tagID int
|
||||
}
|
||||
|
||||
var fields []field
|
||||
var taggedFields []field
|
||||
|
||||
forEachStructField(typ, func(typ reflect.Type, index index, tag string) {
|
||||
if typ.Size() != 0 { // skip struct{}
|
||||
forEachStructTag(tag, func(tag structTag) bool {
|
||||
if tag.MinVersion <= version && version <= tag.MaxVersion {
|
||||
f := field{
|
||||
encode: encodeFuncOf(typ, version, flexible, tag),
|
||||
index: index,
|
||||
tagID: tag.TagID,
|
||||
}
|
||||
|
||||
if tag.TagID < -1 {
|
||||
// Normal required field
|
||||
fields = append(fields, f)
|
||||
} else {
|
||||
// Optional tagged field (flexible messages only)
|
||||
taggedFields = append(taggedFields, f)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return func(e *encoder, v value) {
|
||||
for i := range fields {
|
||||
f := &fields[i]
|
||||
f.encode(e, v.fieldByIndex(f.index))
|
||||
}
|
||||
|
||||
if flexible {
|
||||
// See https://cwiki.apache.org/confluence/display/KAFKA/KIP-482%3A+The+Kafka+Protocol+should+Support+Optional+Tagged+Fields
|
||||
// for details of tag buffers in "flexible" messages.
|
||||
e.writeUnsignedVarInt(uint64(len(taggedFields)))
|
||||
|
||||
for i := range taggedFields {
|
||||
f := &taggedFields[i]
|
||||
e.writeUnsignedVarInt(uint64(f.tagID))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
se := &encoder{writer: buf}
|
||||
f.encode(se, v.fieldByIndex(f.index))
|
||||
e.writeUnsignedVarInt(uint64(buf.Len()))
|
||||
e.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func arrayEncodeFuncOf(typ reflect.Type, version int16, flexible bool, tag structTag) encodeFunc {
|
||||
elemType := typ.Elem()
|
||||
elemFunc := encodeFuncOf(elemType, version, flexible, tag)
|
||||
switch {
|
||||
case flexible && tag.Nullable:
|
||||
// In flexible messages, all arrays are compact
|
||||
return func(e *encoder, v value) { e.encodeCompactNullArray(v, elemType, elemFunc) }
|
||||
case flexible:
|
||||
// In flexible messages, all arrays are compact
|
||||
return func(e *encoder, v value) { e.encodeCompactArray(v, elemType, elemFunc) }
|
||||
case tag.Nullable:
|
||||
return func(e *encoder, v value) { e.encodeNullArray(v, elemType, elemFunc) }
|
||||
default:
|
||||
return func(e *encoder, v value) { e.encodeArray(v, elemType, elemFunc) }
|
||||
}
|
||||
}
|
||||
|
||||
func writerEncodeFuncOf(typ reflect.Type) encodeFunc {
|
||||
typ = reflect.PtrTo(typ)
|
||||
return func(e *encoder, v value) {
|
||||
// Optimization to write directly into the buffer when the encoder
|
||||
// does no need to compute a crc32 checksum.
|
||||
w := io.Writer(e)
|
||||
if e.table == nil {
|
||||
w = e.writer
|
||||
}
|
||||
_, err := v.iface(typ).(io.WriterTo).WriteTo(w)
|
||||
if err != nil {
|
||||
e.err = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeInt8(b []byte, i int8) {
|
||||
b[0] = byte(i)
|
||||
}
|
||||
|
||||
func writeInt16(b []byte, i int16) {
|
||||
binary.BigEndian.PutUint16(b, uint16(i))
|
||||
}
|
||||
|
||||
func writeInt32(b []byte, i int32) {
|
||||
binary.BigEndian.PutUint32(b, uint32(i))
|
||||
}
|
||||
|
||||
func writeInt64(b []byte, i int64) {
|
||||
binary.BigEndian.PutUint64(b, uint64(i))
|
||||
}
|
||||
|
||||
func writeFloat64(b []byte, f float64) {
|
||||
binary.BigEndian.PutUint64(b, math.Float64bits(f))
|
||||
}
|
||||
|
||||
func Marshal(version int16, value interface{}) ([]byte, error) {
|
||||
typ := typeOf(value)
|
||||
cache, _ := marshalers.Load().(map[versionedType]encodeFunc)
|
||||
key := versionedType{typ: typ, version: version}
|
||||
encode := cache[key]
|
||||
|
||||
if encode == nil {
|
||||
encode = encodeFuncOf(reflect.TypeOf(value), version, false, structTag{
|
||||
MinVersion: -1,
|
||||
MaxVersion: -1,
|
||||
TagID: -2,
|
||||
Compact: true,
|
||||
Nullable: true,
|
||||
})
|
||||
|
||||
newCache := make(map[versionedType]encodeFunc, len(cache)+1)
|
||||
newCache[key] = encode
|
||||
|
||||
for typ, fun := range cache {
|
||||
newCache[typ] = fun
|
||||
}
|
||||
|
||||
marshalers.Store(newCache)
|
||||
}
|
||||
|
||||
e, _ := encoders.Get().(*encoder)
|
||||
if e == nil {
|
||||
e = &encoder{writer: new(bytes.Buffer)}
|
||||
}
|
||||
|
||||
b, _ := e.writer.(*bytes.Buffer)
|
||||
defer func() {
|
||||
b.Reset()
|
||||
e.Reset(b)
|
||||
encoders.Put(e)
|
||||
}()
|
||||
|
||||
encode(e, nonAddressableValueOf(value))
|
||||
|
||||
if e.err != nil {
|
||||
return nil, e.err
|
||||
}
|
||||
|
||||
buf := b.Bytes()
|
||||
out := make([]byte, len(buf))
|
||||
copy(out, buf)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type versionedType struct {
|
||||
typ _type
|
||||
version int16
|
||||
}
|
||||
|
||||
var (
|
||||
encoders sync.Pool // *encoder
|
||||
marshalers atomic.Value // map[versionedType]encodeFunc
|
||||
)
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package endtxn
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
TransactionalID string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
ProducerID int64 `kafka:"min=v0,max=v3"`
|
||||
ProducerEpoch int16 `kafka:"min=v0,max=v3"`
|
||||
Committed bool `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.EndTxn }
|
||||
|
||||
func (r *Request) Transaction() string { return r.TransactionalID }
|
||||
|
||||
var _ protocol.TransactionalMessage = (*Request)(nil)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.EndTxn }
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error represents client-side protocol errors.
|
||||
type Error string
|
||||
|
||||
func (e Error) Error() string { return string(e) }
|
||||
|
||||
func Errorf(msg string, args ...interface{}) Error {
|
||||
return Error(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
const (
|
||||
// ErrNoTopic is returned when a request needs to be sent to a specific.
|
||||
ErrNoTopic Error = "topic not found"
|
||||
|
||||
// ErrNoPartition is returned when a request needs to be sent to a specific
|
||||
// partition, but the client did not find it in the cluster metadata.
|
||||
ErrNoPartition Error = "topic partition not found"
|
||||
|
||||
// ErrNoLeader is returned when a request needs to be sent to a partition
|
||||
// leader, but the client could not determine what the leader was at this
|
||||
// time.
|
||||
ErrNoLeader Error = "topic partition has no leader"
|
||||
|
||||
// ErrNoRecord is returned when attempting to write a message containing an
|
||||
// empty record set (which kafka forbids).
|
||||
//
|
||||
// We handle this case client-side because kafka will close the connection
|
||||
// that it received an empty produce request on, causing all concurrent
|
||||
// requests to be aborted.
|
||||
ErrNoRecord Error = "record set contains no records"
|
||||
|
||||
// ErrNoReset is returned by ResetRecordReader when the record reader does
|
||||
// not support being reset.
|
||||
ErrNoReset Error = "record sequence does not support reset"
|
||||
)
|
||||
|
||||
type TopicError struct {
|
||||
Topic string
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewTopicError(topic string, err error) *TopicError {
|
||||
return &TopicError{Topic: topic, Err: err}
|
||||
}
|
||||
|
||||
func NewErrNoTopic(topic string) *TopicError {
|
||||
return NewTopicError(topic, ErrNoTopic)
|
||||
}
|
||||
|
||||
func (e *TopicError) Error() string {
|
||||
return fmt.Sprintf("%v (topic=%q)", e.Err, e.Topic)
|
||||
}
|
||||
|
||||
func (e *TopicError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type TopicPartitionError struct {
|
||||
Topic string
|
||||
Partition int32
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewTopicPartitionError(topic string, partition int32, err error) *TopicPartitionError {
|
||||
return &TopicPartitionError{
|
||||
Topic: topic,
|
||||
Partition: partition,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func NewErrNoPartition(topic string, partition int32) *TopicPartitionError {
|
||||
return NewTopicPartitionError(topic, partition, ErrNoPartition)
|
||||
}
|
||||
|
||||
func NewErrNoLeader(topic string, partition int32) *TopicPartitionError {
|
||||
return NewTopicPartitionError(topic, partition, ErrNoLeader)
|
||||
}
|
||||
|
||||
func (e *TopicPartitionError) Error() string {
|
||||
return fmt.Sprintf("%v (topic=%q partition=%d)", e.Err, e.Topic, e.Partition)
|
||||
}
|
||||
|
||||
func (e *TopicPartitionError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package fetch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
ReplicaID int32 `kafka:"min=v0,max=v11"`
|
||||
MaxWaitTime int32 `kafka:"min=v0,max=v11"`
|
||||
MinBytes int32 `kafka:"min=v0,max=v11"`
|
||||
MaxBytes int32 `kafka:"min=v3,max=v11"`
|
||||
IsolationLevel int8 `kafka:"min=v4,max=v11"`
|
||||
SessionID int32 `kafka:"min=v7,max=v11"`
|
||||
SessionEpoch int32 `kafka:"min=v7,max=v11"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v11"`
|
||||
ForgottenTopics []RequestForgottenTopic `kafka:"min=v7,max=v11"`
|
||||
RackID string `kafka:"min=v11,max=v11"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.Fetch }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
broker := protocol.Broker{ID: -1}
|
||||
|
||||
for i := range r.Topics {
|
||||
t := &r.Topics[i]
|
||||
|
||||
topic, ok := cluster.Topics[t.Topic]
|
||||
if !ok {
|
||||
return broker, NewError(protocol.NewErrNoTopic(t.Topic))
|
||||
}
|
||||
|
||||
for j := range t.Partitions {
|
||||
p := &t.Partitions[j]
|
||||
|
||||
partition, ok := topic.Partitions[p.Partition]
|
||||
if !ok {
|
||||
return broker, NewError(protocol.NewErrNoPartition(t.Topic, p.Partition))
|
||||
}
|
||||
|
||||
if b, ok := cluster.Brokers[partition.Leader]; !ok {
|
||||
return broker, NewError(protocol.NewErrNoLeader(t.Topic, p.Partition))
|
||||
} else if broker.ID < 0 {
|
||||
broker = b
|
||||
} else if b.ID != broker.ID {
|
||||
return broker, NewError(fmt.Errorf("mismatching leaders (%d!=%d)", b.ID, broker.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return broker, nil
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
Topic string `kafka:"min=v0,max=v11"`
|
||||
Partitions []RequestPartition `kafka:"min=v0,max=v11"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
Partition int32 `kafka:"min=v0,max=v11"`
|
||||
CurrentLeaderEpoch int32 `kafka:"min=v9,max=v11"`
|
||||
FetchOffset int64 `kafka:"min=v0,max=v11"`
|
||||
LogStartOffset int64 `kafka:"min=v5,max=v11"`
|
||||
PartitionMaxBytes int32 `kafka:"min=v0,max=v11"`
|
||||
}
|
||||
|
||||
type RequestForgottenTopic struct {
|
||||
Topic string `kafka:"min=v7,max=v11"`
|
||||
Partitions []int32 `kafka:"min=v7,max=v11"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v11"`
|
||||
ErrorCode int16 `kafka:"min=v7,max=v11"`
|
||||
SessionID int32 `kafka:"min=v7,max=v11"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v11"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.Fetch }
|
||||
|
||||
type ResponseTopic struct {
|
||||
Topic string `kafka:"min=v0,max=v11"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v11"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
Partition int32 `kafka:"min=v0,max=v11"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v11"`
|
||||
HighWatermark int64 `kafka:"min=v0,max=v11"`
|
||||
LastStableOffset int64 `kafka:"min=v4,max=v11"`
|
||||
LogStartOffset int64 `kafka:"min=v5,max=v11"`
|
||||
AbortedTransactions []ResponseTransaction `kafka:"min=v4,max=v11"`
|
||||
PreferredReadReplica int32 `kafka:"min=v11,max=v11"`
|
||||
RecordSet protocol.RecordSet `kafka:"min=v0,max=v11"`
|
||||
}
|
||||
|
||||
type ResponseTransaction struct {
|
||||
ProducerID int64 `kafka:"min=v4,max=v11"`
|
||||
FirstOffset int64 `kafka:"min=v4,max=v11"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.BrokerMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewError(err error) *Error {
|
||||
return &Error{Err: err}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("fetch request error: %v", e.Err)
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
Generated
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
package findcoordinator
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Key string `kafka:"min=v0,max=v2"`
|
||||
KeyType int8 `kafka:"min=v1,max=v2"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.FindCoordinator }
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v2"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v2"`
|
||||
ErrorMessage string `kafka:"min=v1,max=v2,nullable"`
|
||||
NodeID int32 `kafka:"min=v0,max=v2"`
|
||||
Host string `kafka:"min=v0,max=v2"`
|
||||
Port int32 `kafka:"min=v0,max=v2"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.FindCoordinator }
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package heartbeat
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_Heartbeat
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v4,tag"`
|
||||
|
||||
GroupID string `kafka:"min=v0,max=v4"`
|
||||
GenerationID int32 `kafka:"min=v0,max=v4"`
|
||||
MemberID string `kafka:"min=v0,max=v4"`
|
||||
GroupInstanceID string `kafka:"min=v3,max=v4,nullable"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey {
|
||||
return protocol.Heartbeat
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v4,tag"`
|
||||
|
||||
ErrorCode int16 `kafka:"min=v0,max=v4"`
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v4"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey {
|
||||
return protocol.Heartbeat
|
||||
}
|
||||
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
package incrementalalterconfigs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
resourceTypeBroker int8 = 4
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_IncrementalAlterConfigs
|
||||
type Request struct {
|
||||
Resources []RequestResource `kafka:"min=v0,max=v0"`
|
||||
ValidateOnly bool `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type RequestResource struct {
|
||||
ResourceType int8 `kafka:"min=v0,max=v0"`
|
||||
ResourceName string `kafka:"min=v0,max=v0"`
|
||||
Configs []RequestConfig `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type RequestConfig struct {
|
||||
Name string `kafka:"min=v0,max=v0"`
|
||||
ConfigOperation int8 `kafka:"min=v0,max=v0"`
|
||||
Value string `kafka:"min=v0,max=v0,nullable"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.IncrementalAlterConfigs }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
// Check that at most only one broker is being updated.
|
||||
//
|
||||
// TODO: Support updating multiple brokers in a single request.
|
||||
brokers := map[string]struct{}{}
|
||||
for _, resource := range r.Resources {
|
||||
if resource.ResourceType == resourceTypeBroker {
|
||||
brokers[resource.ResourceName] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(brokers) > 1 {
|
||||
return protocol.Broker{},
|
||||
errors.New("Updating more than one broker in a single request is not supported yet")
|
||||
}
|
||||
|
||||
for _, resource := range r.Resources {
|
||||
if resource.ResourceType == resourceTypeBroker {
|
||||
brokerID, err := strconv.Atoi(resource.ResourceName)
|
||||
if err != nil {
|
||||
return protocol.Broker{}, err
|
||||
}
|
||||
|
||||
return cluster.Brokers[int32(brokerID)], nil
|
||||
}
|
||||
}
|
||||
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v0"`
|
||||
Responses []ResponseAlterResponse `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type ResponseAlterResponse struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable"`
|
||||
ResourceType int8 `kafka:"min=v0,max=v0"`
|
||||
ResourceName string `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.IncrementalAlterConfigs }
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
package initproducerid
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v2,max=v4,tag"`
|
||||
|
||||
TransactionalID string `kafka:"min=v0,max=v4,nullable"`
|
||||
TransactionTimeoutMs int32 `kafka:"min=v0,max=v4"`
|
||||
ProducerID int64 `kafka:"min=v3,max=v4"`
|
||||
ProducerEpoch int16 `kafka:"min=v3,max=v4"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.InitProducerId }
|
||||
|
||||
func (r *Request) Transaction() string { return r.TransactionalID }
|
||||
|
||||
var _ protocol.TransactionalMessage = (*Request)(nil)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v2,max=v4,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v4"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v4"`
|
||||
ProducerID int64 `kafka:"min=v0,max=v4"`
|
||||
ProducerEpoch int16 `kafka:"min=v0,max=v4"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.InitProducerId }
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package joingroup
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v6,max=v7,tag"`
|
||||
|
||||
GroupID string `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
SessionTimeoutMS int32 `kafka:"min=v0,max=v7"`
|
||||
RebalanceTimeoutMS int32 `kafka:"min=v1,max=v7"`
|
||||
MemberID string `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
GroupInstanceID string `kafka:"min=v5,max=v5,nullable|min=v6,max=v7,compact,nullable"`
|
||||
ProtocolType string `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
Protocols []RequestProtocol `kafka:"min=v0,max=v7"`
|
||||
}
|
||||
|
||||
type RequestProtocol struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v6,max=v7,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
Metadata []byte `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey {
|
||||
return protocol.JoinGroup
|
||||
}
|
||||
|
||||
func (r *Request) Group() string { return r.GroupID }
|
||||
|
||||
var _ protocol.GroupMessage = (*Request)(nil)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v6,max=v7,tag"`
|
||||
|
||||
ThrottleTimeMS int32 `kafka:"min=v2,max=v7"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v7"`
|
||||
GenerationID int32 `kafka:"min=v0,max=v7"`
|
||||
ProtocolType string `kafka:"min=v7,max=v7,compact,nullable"`
|
||||
ProtocolName string `kafka:"min=v0,max=v5|min=v6,max=v6,compact|min=v7,max=v7,compact,nullable"`
|
||||
LeaderID string `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
MemberID string `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
Members []ResponseMember `kafka:"min=v0,max=v7"`
|
||||
}
|
||||
|
||||
type ResponseMember struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v6,max=v7,tag"`
|
||||
|
||||
MemberID string `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
GroupInstanceID string `kafka:"min=v5,max=v5,nullable|min=v6,max=v7,nullable,compact"`
|
||||
Metadata []byte `kafka:"min=v0,max=v5|min=v6,max=v7,compact"`
|
||||
}
|
||||
|
||||
type ResponseMemberMetadata struct{}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.JoinGroup }
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package leavegroup
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v4,tag"`
|
||||
|
||||
GroupID string `kafka:"min=v0,max=v2|min=v3,max=v4,compact"`
|
||||
MemberID string `kafka:"min=v0,max=v2"`
|
||||
Members []RequestMember `kafka:"min=v3,max=v4"`
|
||||
}
|
||||
|
||||
func (r *Request) Prepare(apiVersion int16) {
|
||||
if apiVersion < 3 {
|
||||
if len(r.Members) > 0 {
|
||||
r.MemberID = r.Members[0].MemberID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RequestMember struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v4,tag"`
|
||||
|
||||
MemberID string `kafka:"min=v3,max=v3|min=v4,max=v4,compact"`
|
||||
GroupInstanceID string `kafka:"min=v3,max=v3,nullable|min=v4,max=v4,nullable,compact"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.LeaveGroup }
|
||||
|
||||
func (r *Request) Group() string { return r.GroupID }
|
||||
|
||||
var (
|
||||
_ protocol.GroupMessage = (*Request)(nil)
|
||||
_ protocol.PreparedMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v4,tag"`
|
||||
|
||||
ErrorCode int16 `kafka:"min=v0,max=v4"`
|
||||
ThrottleTimeMS int32 `kafka:"min=v1,max=v4"`
|
||||
Members []ResponseMember `kafka:"min=v3,max=v4"`
|
||||
}
|
||||
|
||||
type ResponseMember struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v4,tag"`
|
||||
|
||||
MemberID string `kafka:"min=v3,max=v3|min=v4,max=v4,compact"`
|
||||
GroupInstanceID string `kafka:"min=v3,max=v3,nullable|min=v4,max=v4,nullable,compact"`
|
||||
ErrorCode int16 `kafka:"min=v3,max=v4"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.LeaveGroup }
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package listgroups
|
||||
|
||||
import (
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_ListGroups
|
||||
type Request struct {
|
||||
_ struct{} `kafka:"min=v0,max=v2"`
|
||||
brokerID int32
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.ListGroups }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[r.brokerID], nil
|
||||
}
|
||||
|
||||
func (r *Request) Split(cluster protocol.Cluster) (
|
||||
[]protocol.Message,
|
||||
protocol.Merger,
|
||||
error,
|
||||
) {
|
||||
messages := []protocol.Message{}
|
||||
|
||||
for _, broker := range cluster.Brokers {
|
||||
messages = append(messages, &Request{brokerID: broker.ID})
|
||||
}
|
||||
|
||||
return messages, new(Response), nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v2"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v2"`
|
||||
Groups []ResponseGroup `kafka:"min=v0,max=v2"`
|
||||
}
|
||||
|
||||
type ResponseGroup struct {
|
||||
GroupID string `kafka:"min=v0,max=v2"`
|
||||
ProtocolType string `kafka:"min=v0,max=v2"`
|
||||
|
||||
// Use this to store which broker returned the response
|
||||
BrokerID int32 `kafka:"-"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.ListGroups }
|
||||
|
||||
func (r *Response) Merge(requests []protocol.Message, results []interface{}) (
|
||||
protocol.Message,
|
||||
error,
|
||||
) {
|
||||
response := &Response{}
|
||||
|
||||
for r, result := range results {
|
||||
m, err := protocol.Result(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
brokerResp := m.(*Response)
|
||||
respGroups := []ResponseGroup{}
|
||||
|
||||
for _, brokerResp := range brokerResp.Groups {
|
||||
respGroups = append(
|
||||
respGroups,
|
||||
ResponseGroup{
|
||||
GroupID: brokerResp.GroupID,
|
||||
ProtocolType: brokerResp.ProtocolType,
|
||||
BrokerID: requests[r].(*Request).brokerID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
response.Groups = append(response.Groups, respGroups...)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
package listoffsets
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
ReplicaID int32 `kafka:"min=v1,max=v5"`
|
||||
IsolationLevel int8 `kafka:"min=v2,max=v5"`
|
||||
Topics []RequestTopic `kafka:"min=v1,max=v5"`
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
Topic string `kafka:"min=v1,max=v5"`
|
||||
Partitions []RequestPartition `kafka:"min=v1,max=v5"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
Partition int32 `kafka:"min=v1,max=v5"`
|
||||
CurrentLeaderEpoch int32 `kafka:"min=v4,max=v5"`
|
||||
Timestamp int64 `kafka:"min=v1,max=v5"`
|
||||
// v0 of the API predates kafka 0.10, and doesn't make much sense to
|
||||
// use so we chose not to support it. It had this extra field to limit
|
||||
// the number of offsets returned, which has been removed in v1.
|
||||
//
|
||||
// MaxNumOffsets int32 `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.ListOffsets }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
// Expects r to be a request that was returned by Map, will likely panic
|
||||
// or produce the wrong result if that's not the case.
|
||||
partition := r.Topics[0].Partitions[0].Partition
|
||||
topic := r.Topics[0].Topic
|
||||
|
||||
for _, p := range cluster.Topics[topic].Partitions {
|
||||
if p.ID == partition {
|
||||
return cluster.Brokers[p.Leader], nil
|
||||
}
|
||||
}
|
||||
|
||||
return protocol.Broker{ID: -1}, nil
|
||||
}
|
||||
|
||||
func (r *Request) Split(cluster protocol.Cluster) ([]protocol.Message, protocol.Merger, error) {
|
||||
// Because kafka refuses to answer ListOffsets requests containing multiple
|
||||
// entries of unique topic/partition pairs, we submit multiple requests on
|
||||
// the wire and merge their results back.
|
||||
//
|
||||
// ListOffsets requests also need to be sent to partition leaders, to keep
|
||||
// the logic simple we simply split each offset request into a single
|
||||
// message. This may cause a bit more requests to be sent on the wire but
|
||||
// it keeps the code sane, we can still optimize the aggregation mechanism
|
||||
// later if it becomes a problem.
|
||||
//
|
||||
// Really the idea here is to shield applications from having to deal with
|
||||
// the limitation of the kafka server, so they can request any combinations
|
||||
// of topic/partition/offsets.
|
||||
requests := make([]Request, 0, 2*len(r.Topics))
|
||||
|
||||
for _, t := range r.Topics {
|
||||
for _, p := range t.Partitions {
|
||||
requests = append(requests, Request{
|
||||
ReplicaID: r.ReplicaID,
|
||||
IsolationLevel: r.IsolationLevel,
|
||||
Topics: []RequestTopic{{
|
||||
Topic: t.Topic,
|
||||
Partitions: []RequestPartition{{
|
||||
Partition: p.Partition,
|
||||
CurrentLeaderEpoch: p.CurrentLeaderEpoch,
|
||||
Timestamp: p.Timestamp,
|
||||
}},
|
||||
}},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
messages := make([]protocol.Message, len(requests))
|
||||
|
||||
for i := range requests {
|
||||
messages[i] = &requests[i]
|
||||
}
|
||||
|
||||
return messages, new(Response), nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v2,max=v5"`
|
||||
Topics []ResponseTopic `kafka:"min=v1,max=v5"`
|
||||
}
|
||||
|
||||
type ResponseTopic struct {
|
||||
Topic string `kafka:"min=v1,max=v5"`
|
||||
Partitions []ResponsePartition `kafka:"min=v1,max=v5"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
Partition int32 `kafka:"min=v1,max=v5"`
|
||||
ErrorCode int16 `kafka:"min=v1,max=v5"`
|
||||
Timestamp int64 `kafka:"min=v1,max=v5"`
|
||||
Offset int64 `kafka:"min=v1,max=v5"`
|
||||
LeaderEpoch int32 `kafka:"min=v4,max=v5"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.ListOffsets }
|
||||
|
||||
func (r *Response) Merge(requests []protocol.Message, results []interface{}) (protocol.Message, error) {
|
||||
type topicPartition struct {
|
||||
topic string
|
||||
partition int32
|
||||
}
|
||||
|
||||
// Kafka doesn't always return the timestamp in the response, for example
|
||||
// when the request sends -2 (for the first offset) it always returns -1,
|
||||
// probably to indicate that the timestamp is unknown. This means that we
|
||||
// can't correlate the requests and responses based on their timestamps,
|
||||
// the primary key is the topic/partition pair.
|
||||
//
|
||||
// To make the API a bit friendly, we reconstructing an index of topic
|
||||
// partitions to the timestamps that were requested, and override the
|
||||
// timestamp value in the response.
|
||||
timestamps := make([]map[topicPartition]int64, len(requests))
|
||||
|
||||
for i, m := range requests {
|
||||
req := m.(*Request)
|
||||
ts := make(map[topicPartition]int64, len(req.Topics))
|
||||
|
||||
for _, t := range req.Topics {
|
||||
for _, p := range t.Partitions {
|
||||
ts[topicPartition{
|
||||
topic: t.Topic,
|
||||
partition: p.Partition,
|
||||
}] = p.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
timestamps[i] = ts
|
||||
}
|
||||
|
||||
topics := make(map[string][]ResponsePartition)
|
||||
errors := 0
|
||||
|
||||
for i, res := range results {
|
||||
m, err := protocol.Result(res)
|
||||
if err != nil {
|
||||
for _, t := range requests[i].(*Request).Topics {
|
||||
partitions := topics[t.Topic]
|
||||
|
||||
for _, p := range t.Partitions {
|
||||
partitions = append(partitions, ResponsePartition{
|
||||
Partition: p.Partition,
|
||||
ErrorCode: -1, // UNKNOWN, can we do better?
|
||||
Timestamp: -1,
|
||||
Offset: -1,
|
||||
LeaderEpoch: -1,
|
||||
})
|
||||
}
|
||||
|
||||
topics[t.Topic] = partitions
|
||||
}
|
||||
errors++
|
||||
continue
|
||||
}
|
||||
|
||||
response := m.(*Response)
|
||||
|
||||
if r.ThrottleTimeMs < response.ThrottleTimeMs {
|
||||
r.ThrottleTimeMs = response.ThrottleTimeMs
|
||||
}
|
||||
|
||||
for _, t := range response.Topics {
|
||||
for _, p := range t.Partitions {
|
||||
if timestamp, ok := timestamps[i][topicPartition{
|
||||
topic: t.Topic,
|
||||
partition: p.Partition,
|
||||
}]; ok {
|
||||
p.Timestamp = timestamp
|
||||
}
|
||||
topics[t.Topic] = append(topics[t.Topic], p)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if errors > 0 && errors == len(results) {
|
||||
_, err := protocol.Result(results[0])
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.Topics = make([]ResponseTopic, 0, len(topics))
|
||||
|
||||
for topicName, partitions := range topics {
|
||||
r.Topics = append(r.Topics, ResponseTopic{
|
||||
Topic: topicName,
|
||||
Partitions: partitions,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(r.Topics, func(i, j int) bool {
|
||||
return r.Topics[i].Topic < r.Topics[j].Topic
|
||||
})
|
||||
|
||||
for _, t := range r.Topics {
|
||||
sort.Slice(t.Partitions, func(i, j int) bool {
|
||||
p1 := &t.Partitions[i]
|
||||
p2 := &t.Partitions[j]
|
||||
|
||||
if p1.Partition != p2.Partition {
|
||||
return p1.Partition < p2.Partition
|
||||
}
|
||||
|
||||
return p1.Offset < p2.Offset
|
||||
})
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.BrokerMessage = (*Request)(nil)
|
||||
_ protocol.Splitter = (*Request)(nil)
|
||||
_ protocol.Merger = (*Response)(nil)
|
||||
)
|
||||
Generated
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
package listpartitionreassignments
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
// Detailed API definition: https://kafka.apache.org/protocol#The_Messages_ListPartitionReassignments.
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
TimeoutMs int32 `kafka:"min=v0,max=v0"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v0,nullable"`
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v0"`
|
||||
PartitionIndexes []int32 `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey {
|
||||
return protocol.ListPartitionReassignments
|
||||
}
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
return cluster.Brokers[cluster.Controller], nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v0"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v0,nullable"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type ResponseTopic struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v0"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v0,max=v0,tag"`
|
||||
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v0"`
|
||||
Replicas []int32 `kafka:"min=v0,max=v0"`
|
||||
AddingReplicas []int32 `kafka:"min=v0,max=v0"`
|
||||
RemovingReplicas []int32 `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey {
|
||||
return protocol.ListPartitionReassignments
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package metadata
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
TopicNames []string `kafka:"min=v0,max=v8,nullable"`
|
||||
AllowAutoTopicCreation bool `kafka:"min=v4,max=v8"`
|
||||
IncludeClusterAuthorizedOperations bool `kafka:"min=v8,max=v8"`
|
||||
IncludeTopicAuthorizedOperations bool `kafka:"min=v8,max=v8"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.Metadata }
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v3,max=v8"`
|
||||
Brokers []ResponseBroker `kafka:"min=v0,max=v8"`
|
||||
ClusterID string `kafka:"min=v2,max=v8,nullable"`
|
||||
ControllerID int32 `kafka:"min=v1,max=v8"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v8"`
|
||||
ClusterAuthorizedOperations int32 `kafka:"min=v8,max=v8"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.Metadata }
|
||||
|
||||
type ResponseBroker struct {
|
||||
NodeID int32 `kafka:"min=v0,max=v8"`
|
||||
Host string `kafka:"min=v0,max=v8"`
|
||||
Port int32 `kafka:"min=v0,max=v8"`
|
||||
Rack string `kafka:"min=v1,max=v8,nullable"`
|
||||
}
|
||||
|
||||
type ResponseTopic struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v8"`
|
||||
Name string `kafka:"min=v0,max=v8"`
|
||||
IsInternal bool `kafka:"min=v1,max=v8"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v8"`
|
||||
TopicAuthorizedOperations int32 `kafka:"min=v8,max=v8"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v8"`
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v8"`
|
||||
LeaderID int32 `kafka:"min=v0,max=v8"`
|
||||
LeaderEpoch int32 `kafka:"min=v7,max=v8"`
|
||||
ReplicaNodes []int32 `kafka:"min=v0,max=v8"`
|
||||
IsrNodes []int32 `kafka:"min=v0,max=v8"`
|
||||
OfflineReplicas []int32 `kafka:"min=v5,max=v8"`
|
||||
}
|
||||
Generated
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
package offsetcommit
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
GroupID string `kafka:"min=v0,max=v7"`
|
||||
GenerationID int32 `kafka:"min=v1,max=v7"`
|
||||
MemberID string `kafka:"min=v1,max=v7"`
|
||||
RetentionTimeMs int64 `kafka:"min=v2,max=v4"`
|
||||
GroupInstanceID string `kafka:"min=v7,max=v7,nullable"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v7"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.OffsetCommit }
|
||||
|
||||
func (r *Request) Group() string { return r.GroupID }
|
||||
|
||||
type RequestTopic struct {
|
||||
Name string `kafka:"min=v0,max=v7"`
|
||||
Partitions []RequestPartition `kafka:"min=v0,max=v7"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v7"`
|
||||
CommittedOffset int64 `kafka:"min=v0,max=v7"`
|
||||
CommitTimestamp int64 `kafka:"min=v1,max=v1"`
|
||||
CommittedLeaderEpoch int32 `kafka:"min=v6,max=v7"`
|
||||
CommittedMetadata string `kafka:"min=v0,max=v7,nullable"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.GroupMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v3,max=v7"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v7"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.OffsetCommit }
|
||||
|
||||
type ResponseTopic struct {
|
||||
Name string `kafka:"min=v0,max=v7"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v7"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v7"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v7"`
|
||||
}
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
package offsetdelete
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
GroupID string `kafka:"min=v0,max=v0"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.OffsetDelete }
|
||||
|
||||
func (r *Request) Group() string { return r.GroupID }
|
||||
|
||||
type RequestTopic struct {
|
||||
Name string `kafka:"min=v0,max=v0"`
|
||||
Partitions []RequestPartition `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.GroupMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v0"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.OffsetDelete }
|
||||
|
||||
type ResponseTopic struct {
|
||||
Name string `kafka:"min=v0,max=v0"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v0"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v0"`
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package offsetfetch
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
GroupID string `kafka:"min=v0,max=v5"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v5,nullable"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.OffsetFetch }
|
||||
|
||||
func (r *Request) Group() string { return r.GroupID }
|
||||
|
||||
type RequestTopic struct {
|
||||
Name string `kafka:"min=v0,max=v5"`
|
||||
PartitionIndexes []int32 `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.GroupMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
ThrottleTimeMs int32 `kafka:"min=v3,max=v5"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v5"`
|
||||
ErrorCode int16 `kafka:"min=v2,max=v5"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.OffsetFetch }
|
||||
|
||||
type ResponseTopic struct {
|
||||
Name string `kafka:"min=v0,max=v5"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
PartitionIndex int32 `kafka:"min=v0,max=v5"`
|
||||
CommittedOffset int64 `kafka:"min=v0,max=v5"`
|
||||
ComittedLeaderEpoch int32 `kafka:"min=v5,max=v5"`
|
||||
Metadata string `kafka:"min=v0,max=v5,nullable"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package produce
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
TransactionalID string `kafka:"min=v3,max=v8,nullable"`
|
||||
Acks int16 `kafka:"min=v0,max=v8"`
|
||||
Timeout int32 `kafka:"min=v0,max=v8"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v8"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.Produce }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
broker := protocol.Broker{ID: -1}
|
||||
|
||||
for i := range r.Topics {
|
||||
t := &r.Topics[i]
|
||||
|
||||
topic, ok := cluster.Topics[t.Topic]
|
||||
if !ok {
|
||||
return broker, NewError(protocol.NewErrNoTopic(t.Topic))
|
||||
}
|
||||
|
||||
for j := range t.Partitions {
|
||||
p := &t.Partitions[j]
|
||||
|
||||
partition, ok := topic.Partitions[p.Partition]
|
||||
if !ok {
|
||||
return broker, NewError(protocol.NewErrNoPartition(t.Topic, p.Partition))
|
||||
}
|
||||
|
||||
if b, ok := cluster.Brokers[partition.Leader]; !ok {
|
||||
return broker, NewError(protocol.NewErrNoLeader(t.Topic, p.Partition))
|
||||
} else if broker.ID < 0 {
|
||||
broker = b
|
||||
} else if b.ID != broker.ID {
|
||||
return broker, NewError(fmt.Errorf("mismatching leaders (%d!=%d)", b.ID, broker.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return broker, nil
|
||||
}
|
||||
|
||||
func (r *Request) Prepare(apiVersion int16) {
|
||||
// Determine which version of the message should be used, based on which
|
||||
// version of the Produce API is supported by the server.
|
||||
//
|
||||
// In version 0.11, kafka gives this error:
|
||||
//
|
||||
// org.apache.kafka.common.record.InvalidRecordException
|
||||
// Produce requests with version 3 are only allowed to contain record batches with magic version.
|
||||
//
|
||||
// In version 2.x, kafka refuses the message claiming that the CRC32
|
||||
// checksum is invalid.
|
||||
var recordVersion int8
|
||||
|
||||
if apiVersion < 3 {
|
||||
recordVersion = 1
|
||||
} else {
|
||||
recordVersion = 2
|
||||
}
|
||||
|
||||
for i := range r.Topics {
|
||||
t := &r.Topics[i]
|
||||
|
||||
for j := range t.Partitions {
|
||||
p := &t.Partitions[j]
|
||||
|
||||
// Allow the program to overload the version if really needed.
|
||||
if p.RecordSet.Version == 0 {
|
||||
p.RecordSet.Version = recordVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) HasResponse() bool {
|
||||
return r.Acks != 0
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
Topic string `kafka:"min=v0,max=v8"`
|
||||
Partitions []RequestPartition `kafka:"min=v0,max=v8"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
Partition int32 `kafka:"min=v0,max=v8"`
|
||||
RecordSet protocol.RecordSet `kafka:"min=v0,max=v8"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v8"`
|
||||
ThrottleTimeMs int32 `kafka:"min=v1,max=v8"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.Produce }
|
||||
|
||||
type ResponseTopic struct {
|
||||
Topic string `kafka:"min=v0,max=v8"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v8"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
Partition int32 `kafka:"min=v0,max=v8"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v8"`
|
||||
BaseOffset int64 `kafka:"min=v0,max=v8"`
|
||||
LogAppendTime int64 `kafka:"min=v2,max=v8"`
|
||||
LogStartOffset int64 `kafka:"min=v5,max=v8"`
|
||||
RecordErrors []ResponseError `kafka:"min=v8,max=v8"`
|
||||
ErrorMessage string `kafka:"min=v8,max=v8,nullable"`
|
||||
}
|
||||
|
||||
type ResponseError struct {
|
||||
BatchIndex int32 `kafka:"min=v8,max=v8"`
|
||||
BatchIndexErrorMessage string `kafka:"min=v8,max=v8,nullable"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.BrokerMessage = (*Request)(nil)
|
||||
_ protocol.PreparedMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewError(err error) *Error {
|
||||
return &Error{Err: err}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("fetch request error: %v", e.Err)
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
+541
@@ -0,0 +1,541 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Message is an interface implemented by all request and response types of the
|
||||
// kafka protocol.
|
||||
//
|
||||
// This interface is used mostly as a safe-guard to provide a compile-time check
|
||||
// for values passed to functions dealing kafka message types.
|
||||
type Message interface {
|
||||
ApiKey() ApiKey
|
||||
}
|
||||
|
||||
type ApiKey int16
|
||||
|
||||
func (k ApiKey) String() string {
|
||||
if i := int(k); i >= 0 && i < len(apiNames) {
|
||||
return apiNames[i]
|
||||
}
|
||||
return strconv.Itoa(int(k))
|
||||
}
|
||||
|
||||
func (k ApiKey) MinVersion() int16 { return k.apiType().minVersion() }
|
||||
|
||||
func (k ApiKey) MaxVersion() int16 { return k.apiType().maxVersion() }
|
||||
|
||||
func (k ApiKey) SelectVersion(minVersion, maxVersion int16) int16 {
|
||||
min := k.MinVersion()
|
||||
max := k.MaxVersion()
|
||||
switch {
|
||||
case min > maxVersion:
|
||||
return min
|
||||
case max < maxVersion:
|
||||
return max
|
||||
default:
|
||||
return maxVersion
|
||||
}
|
||||
}
|
||||
|
||||
func (k ApiKey) apiType() apiType {
|
||||
if i := int(k); i >= 0 && i < len(apiTypes) {
|
||||
return apiTypes[i]
|
||||
}
|
||||
return apiType{}
|
||||
}
|
||||
|
||||
const (
|
||||
Produce ApiKey = 0
|
||||
Fetch ApiKey = 1
|
||||
ListOffsets ApiKey = 2
|
||||
Metadata ApiKey = 3
|
||||
LeaderAndIsr ApiKey = 4
|
||||
StopReplica ApiKey = 5
|
||||
UpdateMetadata ApiKey = 6
|
||||
ControlledShutdown ApiKey = 7
|
||||
OffsetCommit ApiKey = 8
|
||||
OffsetFetch ApiKey = 9
|
||||
FindCoordinator ApiKey = 10
|
||||
JoinGroup ApiKey = 11
|
||||
Heartbeat ApiKey = 12
|
||||
LeaveGroup ApiKey = 13
|
||||
SyncGroup ApiKey = 14
|
||||
DescribeGroups ApiKey = 15
|
||||
ListGroups ApiKey = 16
|
||||
SaslHandshake ApiKey = 17
|
||||
ApiVersions ApiKey = 18
|
||||
CreateTopics ApiKey = 19
|
||||
DeleteTopics ApiKey = 20
|
||||
DeleteRecords ApiKey = 21
|
||||
InitProducerId ApiKey = 22
|
||||
OffsetForLeaderEpoch ApiKey = 23
|
||||
AddPartitionsToTxn ApiKey = 24
|
||||
AddOffsetsToTxn ApiKey = 25
|
||||
EndTxn ApiKey = 26
|
||||
WriteTxnMarkers ApiKey = 27
|
||||
TxnOffsetCommit ApiKey = 28
|
||||
DescribeAcls ApiKey = 29
|
||||
CreateAcls ApiKey = 30
|
||||
DeleteAcls ApiKey = 31
|
||||
DescribeConfigs ApiKey = 32
|
||||
AlterConfigs ApiKey = 33
|
||||
AlterReplicaLogDirs ApiKey = 34
|
||||
DescribeLogDirs ApiKey = 35
|
||||
SaslAuthenticate ApiKey = 36
|
||||
CreatePartitions ApiKey = 37
|
||||
CreateDelegationToken ApiKey = 38
|
||||
RenewDelegationToken ApiKey = 39
|
||||
ExpireDelegationToken ApiKey = 40
|
||||
DescribeDelegationToken ApiKey = 41
|
||||
DeleteGroups ApiKey = 42
|
||||
ElectLeaders ApiKey = 43
|
||||
IncrementalAlterConfigs ApiKey = 44
|
||||
AlterPartitionReassignments ApiKey = 45
|
||||
ListPartitionReassignments ApiKey = 46
|
||||
OffsetDelete ApiKey = 47
|
||||
DescribeClientQuotas ApiKey = 48
|
||||
AlterClientQuotas ApiKey = 49
|
||||
DescribeUserScramCredentials ApiKey = 50
|
||||
AlterUserScramCredentials ApiKey = 51
|
||||
|
||||
numApis = 52
|
||||
)
|
||||
|
||||
var apiNames = [numApis]string{
|
||||
Produce: "Produce",
|
||||
Fetch: "Fetch",
|
||||
ListOffsets: "ListOffsets",
|
||||
Metadata: "Metadata",
|
||||
LeaderAndIsr: "LeaderAndIsr",
|
||||
StopReplica: "StopReplica",
|
||||
UpdateMetadata: "UpdateMetadata",
|
||||
ControlledShutdown: "ControlledShutdown",
|
||||
OffsetCommit: "OffsetCommit",
|
||||
OffsetFetch: "OffsetFetch",
|
||||
FindCoordinator: "FindCoordinator",
|
||||
JoinGroup: "JoinGroup",
|
||||
Heartbeat: "Heartbeat",
|
||||
LeaveGroup: "LeaveGroup",
|
||||
SyncGroup: "SyncGroup",
|
||||
DescribeGroups: "DescribeGroups",
|
||||
ListGroups: "ListGroups",
|
||||
SaslHandshake: "SaslHandshake",
|
||||
ApiVersions: "ApiVersions",
|
||||
CreateTopics: "CreateTopics",
|
||||
DeleteTopics: "DeleteTopics",
|
||||
DeleteRecords: "DeleteRecords",
|
||||
InitProducerId: "InitProducerId",
|
||||
OffsetForLeaderEpoch: "OffsetForLeaderEpoch",
|
||||
AddPartitionsToTxn: "AddPartitionsToTxn",
|
||||
AddOffsetsToTxn: "AddOffsetsToTxn",
|
||||
EndTxn: "EndTxn",
|
||||
WriteTxnMarkers: "WriteTxnMarkers",
|
||||
TxnOffsetCommit: "TxnOffsetCommit",
|
||||
DescribeAcls: "DescribeAcls",
|
||||
CreateAcls: "CreateAcls",
|
||||
DeleteAcls: "DeleteAcls",
|
||||
DescribeConfigs: "DescribeConfigs",
|
||||
AlterConfigs: "AlterConfigs",
|
||||
AlterReplicaLogDirs: "AlterReplicaLogDirs",
|
||||
DescribeLogDirs: "DescribeLogDirs",
|
||||
SaslAuthenticate: "SaslAuthenticate",
|
||||
CreatePartitions: "CreatePartitions",
|
||||
CreateDelegationToken: "CreateDelegationToken",
|
||||
RenewDelegationToken: "RenewDelegationToken",
|
||||
ExpireDelegationToken: "ExpireDelegationToken",
|
||||
DescribeDelegationToken: "DescribeDelegationToken",
|
||||
DeleteGroups: "DeleteGroups",
|
||||
ElectLeaders: "ElectLeaders",
|
||||
IncrementalAlterConfigs: "IncrementalAlterConfigs",
|
||||
AlterPartitionReassignments: "AlterPartitionReassignments",
|
||||
ListPartitionReassignments: "ListPartitionReassignments",
|
||||
OffsetDelete: "OffsetDelete",
|
||||
DescribeClientQuotas: "DescribeClientQuotas",
|
||||
AlterClientQuotas: "AlterClientQuotas",
|
||||
DescribeUserScramCredentials: "DescribeUserScramCredentials",
|
||||
AlterUserScramCredentials: "AlterUserScramCredentials",
|
||||
}
|
||||
|
||||
type messageType struct {
|
||||
version int16
|
||||
flexible bool
|
||||
gotype reflect.Type
|
||||
decode decodeFunc
|
||||
encode encodeFunc
|
||||
}
|
||||
|
||||
func (t *messageType) new() Message {
|
||||
return reflect.New(t.gotype).Interface().(Message)
|
||||
}
|
||||
|
||||
type apiType struct {
|
||||
requests []messageType
|
||||
responses []messageType
|
||||
}
|
||||
|
||||
func (t apiType) minVersion() int16 {
|
||||
if len(t.requests) == 0 {
|
||||
return 0
|
||||
}
|
||||
return t.requests[0].version
|
||||
}
|
||||
|
||||
func (t apiType) maxVersion() int16 {
|
||||
if len(t.requests) == 0 {
|
||||
return 0
|
||||
}
|
||||
return t.requests[len(t.requests)-1].version
|
||||
}
|
||||
|
||||
var apiTypes [numApis]apiType
|
||||
|
||||
// Register is automatically called by sub-packages are imported to install a
|
||||
// new pair of request/response message types.
|
||||
func Register(req, res Message) {
|
||||
k1 := req.ApiKey()
|
||||
k2 := res.ApiKey()
|
||||
|
||||
if k1 != k2 {
|
||||
panic(fmt.Sprintf("[%T/%T]: request and response API keys mismatch: %d != %d", req, res, k1, k2))
|
||||
}
|
||||
|
||||
apiTypes[k1] = apiType{
|
||||
requests: typesOf(req),
|
||||
responses: typesOf(res),
|
||||
}
|
||||
}
|
||||
|
||||
// OverrideTypeMessage is an interface implemented by messages that want to override the standard
|
||||
// request/response types for a given API.
|
||||
type OverrideTypeMessage interface {
|
||||
TypeKey() OverrideTypeKey
|
||||
}
|
||||
|
||||
type OverrideTypeKey int16
|
||||
|
||||
const (
|
||||
RawProduceOverride OverrideTypeKey = 0
|
||||
)
|
||||
|
||||
var overrideApiTypes [numApis]map[OverrideTypeKey]apiType
|
||||
|
||||
func RegisterOverride(req, res Message, key OverrideTypeKey) {
|
||||
k1 := req.ApiKey()
|
||||
k2 := res.ApiKey()
|
||||
|
||||
if k1 != k2 {
|
||||
panic(fmt.Sprintf("[%T/%T]: request and response API keys mismatch: %d != %d", req, res, k1, k2))
|
||||
}
|
||||
|
||||
if overrideApiTypes[k1] == nil {
|
||||
overrideApiTypes[k1] = make(map[OverrideTypeKey]apiType)
|
||||
}
|
||||
overrideApiTypes[k1][key] = apiType{
|
||||
requests: typesOf(req),
|
||||
responses: typesOf(res),
|
||||
}
|
||||
}
|
||||
|
||||
func typesOf(v interface{}) []messageType {
|
||||
return makeTypes(reflect.TypeOf(v).Elem())
|
||||
}
|
||||
|
||||
func makeTypes(t reflect.Type) []messageType {
|
||||
minVersion := int16(-1)
|
||||
maxVersion := int16(-1)
|
||||
|
||||
// All future versions will be flexible (according to spec), so don't need to
|
||||
// worry about maxes here.
|
||||
minFlexibleVersion := int16(-1)
|
||||
|
||||
forEachStructField(t, func(_ reflect.Type, _ index, tag string) {
|
||||
forEachStructTag(tag, func(tag structTag) bool {
|
||||
if minVersion < 0 || tag.MinVersion < minVersion {
|
||||
minVersion = tag.MinVersion
|
||||
}
|
||||
if maxVersion < 0 || tag.MaxVersion > maxVersion {
|
||||
maxVersion = tag.MaxVersion
|
||||
}
|
||||
if tag.TagID > -2 && (minFlexibleVersion < 0 || tag.MinVersion < minFlexibleVersion) {
|
||||
minFlexibleVersion = tag.MinVersion
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
types := make([]messageType, 0, (maxVersion-minVersion)+1)
|
||||
|
||||
for v := minVersion; v <= maxVersion; v++ {
|
||||
flexible := minFlexibleVersion >= 0 && v >= minFlexibleVersion
|
||||
|
||||
types = append(types, messageType{
|
||||
version: v,
|
||||
gotype: t,
|
||||
flexible: flexible,
|
||||
decode: decodeFuncOf(t, v, flexible, structTag{}),
|
||||
encode: encodeFuncOf(t, v, flexible, structTag{}),
|
||||
})
|
||||
}
|
||||
|
||||
return types
|
||||
}
|
||||
|
||||
type structTag struct {
|
||||
MinVersion int16
|
||||
MaxVersion int16
|
||||
Compact bool
|
||||
Nullable bool
|
||||
TagID int
|
||||
}
|
||||
|
||||
func forEachStructTag(tag string, do func(structTag) bool) {
|
||||
if tag == "-" {
|
||||
return // special case to ignore the field
|
||||
}
|
||||
|
||||
forEach(tag, '|', func(s string) bool {
|
||||
tag := structTag{
|
||||
MinVersion: -1,
|
||||
MaxVersion: -1,
|
||||
|
||||
// Legitimate tag IDs can start at 0. We use -1 as a placeholder to indicate
|
||||
// that the message type is flexible, so that leaves -2 as the default for
|
||||
// indicating that there is no tag ID and the message is not flexible.
|
||||
TagID: -2,
|
||||
}
|
||||
|
||||
var err error
|
||||
forEach(s, ',', func(s string) bool {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "min="):
|
||||
tag.MinVersion, err = parseVersion(s[4:])
|
||||
case strings.HasPrefix(s, "max="):
|
||||
tag.MaxVersion, err = parseVersion(s[4:])
|
||||
case s == "tag":
|
||||
tag.TagID = -1
|
||||
case strings.HasPrefix(s, "tag="):
|
||||
tag.TagID, err = strconv.Atoi(s[4:])
|
||||
case s == "compact":
|
||||
tag.Compact = true
|
||||
case s == "nullable":
|
||||
tag.Nullable = true
|
||||
default:
|
||||
err = fmt.Errorf("unrecognized option: %q", s)
|
||||
}
|
||||
return err == nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("malformed struct tag: %w", err))
|
||||
}
|
||||
|
||||
if tag.MinVersion < 0 && tag.MaxVersion >= 0 {
|
||||
panic(fmt.Errorf("missing minimum version in struct tag: %q", s))
|
||||
}
|
||||
|
||||
if tag.MaxVersion < 0 && tag.MinVersion >= 0 {
|
||||
panic(fmt.Errorf("missing maximum version in struct tag: %q", s))
|
||||
}
|
||||
|
||||
if tag.MinVersion > tag.MaxVersion {
|
||||
panic(fmt.Errorf("invalid version range in struct tag: %q", s))
|
||||
}
|
||||
|
||||
return do(tag)
|
||||
})
|
||||
}
|
||||
|
||||
func forEach(s string, sep byte, do func(string) bool) bool {
|
||||
for len(s) != 0 {
|
||||
p := ""
|
||||
i := strings.IndexByte(s, sep)
|
||||
if i < 0 {
|
||||
p, s = s, ""
|
||||
} else {
|
||||
p, s = s[:i], s[i+1:]
|
||||
}
|
||||
if !do(p) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func forEachStructField(t reflect.Type, do func(reflect.Type, index, string)) {
|
||||
for i, n := 0, t.NumField(); i < n; i++ {
|
||||
f := t.Field(i)
|
||||
|
||||
if f.PkgPath != "" && f.Name != "_" {
|
||||
continue
|
||||
}
|
||||
|
||||
kafkaTag, ok := f.Tag.Lookup("kafka")
|
||||
if !ok {
|
||||
kafkaTag = "|"
|
||||
}
|
||||
|
||||
do(f.Type, indexOf(f), kafkaTag)
|
||||
}
|
||||
}
|
||||
|
||||
func parseVersion(s string) (int16, error) {
|
||||
if !strings.HasPrefix(s, "v") {
|
||||
return 0, fmt.Errorf("invalid version number: %q", s)
|
||||
}
|
||||
i, err := strconv.ParseInt(s[1:], 10, 16)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid version number: %q: %w", s, err)
|
||||
}
|
||||
if i < 0 {
|
||||
return 0, fmt.Errorf("invalid negative version number: %q", s)
|
||||
}
|
||||
return int16(i), nil
|
||||
}
|
||||
|
||||
func dontExpectEOF(err error) error {
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Broker struct {
|
||||
Rack string
|
||||
Host string
|
||||
Port int32
|
||||
ID int32
|
||||
}
|
||||
|
||||
func (b Broker) String() string {
|
||||
return net.JoinHostPort(b.Host, itoa(b.Port))
|
||||
}
|
||||
|
||||
func (b Broker) Format(w fmt.State, v rune) {
|
||||
switch v {
|
||||
case 'd':
|
||||
io.WriteString(w, itoa(b.ID))
|
||||
case 's':
|
||||
io.WriteString(w, b.String())
|
||||
case 'v':
|
||||
io.WriteString(w, itoa(b.ID))
|
||||
io.WriteString(w, " ")
|
||||
io.WriteString(w, b.String())
|
||||
if b.Rack != "" {
|
||||
io.WriteString(w, " ")
|
||||
io.WriteString(w, b.Rack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func itoa(i int32) string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
type Topic struct {
|
||||
Name string
|
||||
Error int16
|
||||
Partitions map[int32]Partition
|
||||
}
|
||||
|
||||
type Partition struct {
|
||||
ID int32
|
||||
Error int16
|
||||
Leader int32
|
||||
Replicas []int32
|
||||
ISR []int32
|
||||
Offline []int32
|
||||
}
|
||||
|
||||
// RawExchanger is an extention to the Message interface to allow messages
|
||||
// to control the request response cycle for the message. This is currently
|
||||
// only used to facilitate v0 SASL Authenticate requests being written in
|
||||
// a non-standard fashion when the SASL Handshake was done at v0 but not
|
||||
// when done at v1.
|
||||
type RawExchanger interface {
|
||||
// Required should return true when a RawExchange is needed.
|
||||
// The passed in versions are the negotiated versions for the connection
|
||||
// performing the request.
|
||||
Required(versions map[ApiKey]int16) bool
|
||||
// RawExchange is given the raw connection to the broker and the Message
|
||||
// is responsible for writing itself to the connection as well as reading
|
||||
// the response.
|
||||
RawExchange(rw io.ReadWriter) (Message, error)
|
||||
}
|
||||
|
||||
// BrokerMessage is an extension of the Message interface implemented by some
|
||||
// request types to customize the broker assignment logic.
|
||||
type BrokerMessage interface {
|
||||
// Given a representation of the kafka cluster state as argument, returns
|
||||
// the broker that the message should be routed to.
|
||||
Broker(Cluster) (Broker, error)
|
||||
}
|
||||
|
||||
// GroupMessage is an extension of the Message interface implemented by some
|
||||
// request types to inform the program that they should be routed to a group
|
||||
// coordinator.
|
||||
type GroupMessage interface {
|
||||
// Returns the group configured on the message.
|
||||
Group() string
|
||||
}
|
||||
|
||||
// TransactionalMessage is an extension of the Message interface implemented by some
|
||||
// request types to inform the program that they should be routed to a transaction
|
||||
// coordinator.
|
||||
type TransactionalMessage interface {
|
||||
// Returns the transactional id configured on the message.
|
||||
Transaction() string
|
||||
}
|
||||
|
||||
// PreparedMessage is an extension of the Message interface implemented by some
|
||||
// request types which may need to run some pre-processing on their state before
|
||||
// being sent.
|
||||
type PreparedMessage interface {
|
||||
// Prepares the message before being sent to a kafka broker using the API
|
||||
// version passed as argument.
|
||||
Prepare(apiVersion int16)
|
||||
}
|
||||
|
||||
// Splitter is an interface implemented by messages that can be split into
|
||||
// multiple requests and have their results merged back by a Merger.
|
||||
type Splitter interface {
|
||||
// For a given cluster layout, returns the list of messages constructed
|
||||
// from the receiver for each requests that should be sent to the cluster.
|
||||
// The second return value is a Merger which can be used to merge back the
|
||||
// results of each request into a single message (or an error).
|
||||
Split(Cluster) ([]Message, Merger, error)
|
||||
}
|
||||
|
||||
// Merger is an interface implemented by messages which can merge multiple
|
||||
// results into one response.
|
||||
type Merger interface {
|
||||
// Given a list of message and associated results, merge them back into a
|
||||
// response (or an error). The results must be either Message or error
|
||||
// values, other types should trigger a panic.
|
||||
Merge(messages []Message, results []interface{}) (Message, error)
|
||||
}
|
||||
|
||||
// Result converts r to a Message or an error, or panics if r could not be
|
||||
// converted to these types.
|
||||
func Result(r interface{}) (Message, error) {
|
||||
switch v := r.(type) {
|
||||
case Message:
|
||||
return v, nil
|
||||
case error:
|
||||
return nil, v
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: result must be a message or an error but not %T", v))
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package rawproduce
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
"github.com/segmentio/kafka-go/protocol/produce"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register a type override so that raw produce requests will be encoded with the correct type.
|
||||
req := &Request{}
|
||||
protocol.RegisterOverride(req, &produce.Response{}, req.TypeKey())
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
TransactionalID string `kafka:"min=v3,max=v8,nullable"`
|
||||
Acks int16 `kafka:"min=v0,max=v8"`
|
||||
Timeout int32 `kafka:"min=v0,max=v8"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v8"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.Produce }
|
||||
|
||||
func (r *Request) TypeKey() protocol.OverrideTypeKey { return protocol.RawProduceOverride }
|
||||
|
||||
func (r *Request) Broker(cluster protocol.Cluster) (protocol.Broker, error) {
|
||||
broker := protocol.Broker{ID: -1}
|
||||
|
||||
for i := range r.Topics {
|
||||
t := &r.Topics[i]
|
||||
|
||||
topic, ok := cluster.Topics[t.Topic]
|
||||
if !ok {
|
||||
return broker, NewError(protocol.NewErrNoTopic(t.Topic))
|
||||
}
|
||||
|
||||
for j := range t.Partitions {
|
||||
p := &t.Partitions[j]
|
||||
|
||||
partition, ok := topic.Partitions[p.Partition]
|
||||
if !ok {
|
||||
return broker, NewError(protocol.NewErrNoPartition(t.Topic, p.Partition))
|
||||
}
|
||||
|
||||
if b, ok := cluster.Brokers[partition.Leader]; !ok {
|
||||
return broker, NewError(protocol.NewErrNoLeader(t.Topic, p.Partition))
|
||||
} else if broker.ID < 0 {
|
||||
broker = b
|
||||
} else if b.ID != broker.ID {
|
||||
return broker, NewError(fmt.Errorf("mismatching leaders (%d!=%d)", b.ID, broker.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return broker, nil
|
||||
}
|
||||
|
||||
func (r *Request) HasResponse() bool {
|
||||
return r.Acks != 0
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
Topic string `kafka:"min=v0,max=v8"`
|
||||
Partitions []RequestPartition `kafka:"min=v0,max=v8"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
Partition int32 `kafka:"min=v0,max=v8"`
|
||||
RecordSet protocol.RawRecordSet `kafka:"min=v0,max=v8"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ protocol.BrokerMessage = (*Request)(nil)
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func NewError(err error) *Error {
|
||||
return &Error{Err: err}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("fetch request error: %v", e.Err)
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/segmentio/kafka-go/compress"
|
||||
)
|
||||
|
||||
// Attributes is a bitset representing special attributes set on records.
|
||||
type Attributes int16
|
||||
|
||||
const (
|
||||
Gzip Attributes = Attributes(compress.Gzip) // 1
|
||||
Snappy Attributes = Attributes(compress.Snappy) // 2
|
||||
Lz4 Attributes = Attributes(compress.Lz4) // 3
|
||||
Zstd Attributes = Attributes(compress.Zstd) // 4
|
||||
Transactional Attributes = 1 << 4
|
||||
Control Attributes = 1 << 5
|
||||
)
|
||||
|
||||
func (a Attributes) Compression() compress.Compression {
|
||||
return compress.Compression(a & 7)
|
||||
}
|
||||
|
||||
func (a Attributes) Transactional() bool {
|
||||
return (a & Transactional) != 0
|
||||
}
|
||||
|
||||
func (a Attributes) Control() bool {
|
||||
return (a & Control) != 0
|
||||
}
|
||||
|
||||
func (a Attributes) String() string {
|
||||
s := a.Compression().String()
|
||||
if a.Transactional() {
|
||||
s += "+transactional"
|
||||
}
|
||||
if a.Control() {
|
||||
s += "+control"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Header represents a single entry in a list of record headers.
|
||||
type Header struct {
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// Record is an interface representing a single kafka record.
|
||||
//
|
||||
// Record values are not safe to use concurrently from multiple goroutines.
|
||||
type Record struct {
|
||||
// The offset at which the record exists in a topic partition. This value
|
||||
// is ignored in produce requests.
|
||||
Offset int64
|
||||
|
||||
// Returns the time of the record. This value may be omitted in produce
|
||||
// requests to let kafka set the time when it saves the record.
|
||||
Time time.Time
|
||||
|
||||
// Returns a byte sequence containing the key of this record. The returned
|
||||
// sequence may be nil to indicate that the record has no key. If the record
|
||||
// is part of a RecordSet, the content of the key must remain valid at least
|
||||
// until the record set is closed (or until the key is closed).
|
||||
Key Bytes
|
||||
|
||||
// Returns a byte sequence containing the value of this record. The returned
|
||||
// sequence may be nil to indicate that the record has no value. If the
|
||||
// record is part of a RecordSet, the content of the value must remain valid
|
||||
// at least until the record set is closed (or until the value is closed).
|
||||
Value Bytes
|
||||
|
||||
// Returns the list of headers associated with this record. The returned
|
||||
// slice may be reused across calls, the program should use it as an
|
||||
// immutable value.
|
||||
Headers []Header
|
||||
}
|
||||
|
||||
// RecordSet represents a sequence of records in Produce requests and Fetch
|
||||
// responses. All v0, v1, and v2 formats are supported.
|
||||
type RecordSet struct {
|
||||
// The message version that this record set will be represented as, valid
|
||||
// values are 1, or 2.
|
||||
//
|
||||
// When reading, this is the value of the highest version used in the
|
||||
// batches that compose the record set.
|
||||
//
|
||||
// When writing, this value dictates the format that the records will be
|
||||
// encoded in.
|
||||
Version int8
|
||||
|
||||
// Attributes set on the record set.
|
||||
//
|
||||
// When reading, the attributes are the combination of all attributes in
|
||||
// the batches that compose the record set.
|
||||
//
|
||||
// When writing, the attributes apply to the whole sequence of records in
|
||||
// the set.
|
||||
Attributes Attributes
|
||||
|
||||
// A reader exposing the sequence of records.
|
||||
//
|
||||
// When reading a RecordSet from an io.Reader, the Records field will be a
|
||||
// *RecordStream. If the program needs to access the details of each batch
|
||||
// that compose the stream, it may use type assertions to access the
|
||||
// underlying types of each batch.
|
||||
Records RecordReader
|
||||
}
|
||||
|
||||
// bufferedReader is an interface implemented by types like bufio.Reader, which
|
||||
// we use to optimize prefix reads by accessing the internal buffer directly
|
||||
// through calls to Peek.
|
||||
type bufferedReader interface {
|
||||
Discard(int) (int, error)
|
||||
Peek(int) ([]byte, error)
|
||||
}
|
||||
|
||||
// bytesBuffer is an interface implemented by types like bytes.Buffer, which we
|
||||
// use to optimize prefix reads by accessing the internal buffer directly
|
||||
// through calls to Bytes.
|
||||
type bytesBuffer interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
// magicByteOffset is the position of the magic byte in all versions of record
|
||||
// sets in the kafka protocol.
|
||||
const magicByteOffset = 16
|
||||
|
||||
// ReadFrom reads the representation of a record set from r into rs, returning
|
||||
// the number of bytes consumed from r, and an non-nil error if the record set
|
||||
// could not be read.
|
||||
func (rs *RecordSet) ReadFrom(r io.Reader) (int64, error) {
|
||||
d, _ := r.(*decoder)
|
||||
if d == nil {
|
||||
d = &decoder{
|
||||
reader: r,
|
||||
remain: 4,
|
||||
}
|
||||
}
|
||||
|
||||
*rs = RecordSet{}
|
||||
limit := d.remain
|
||||
size := d.readInt32()
|
||||
|
||||
if d.err != nil {
|
||||
return int64(limit - d.remain), d.err
|
||||
}
|
||||
|
||||
if size <= 0 {
|
||||
return 4, nil
|
||||
}
|
||||
|
||||
stream := &RecordStream{
|
||||
Records: make([]RecordReader, 0, 4),
|
||||
}
|
||||
|
||||
var err error
|
||||
d.remain = int(size)
|
||||
|
||||
for d.remain > 0 && err == nil {
|
||||
var version byte
|
||||
|
||||
if d.remain < (magicByteOffset + 1) {
|
||||
if len(stream.Records) != 0 {
|
||||
break
|
||||
}
|
||||
return 4, fmt.Errorf("impossible record set shorter than %d bytes", magicByteOffset+1)
|
||||
}
|
||||
|
||||
switch r := d.reader.(type) {
|
||||
case bufferedReader:
|
||||
b, err := r.Peek(magicByteOffset + 1)
|
||||
if err != nil {
|
||||
n, _ := r.Discard(len(b))
|
||||
return 4 + int64(n), dontExpectEOF(err)
|
||||
}
|
||||
version = b[magicByteOffset]
|
||||
case bytesBuffer:
|
||||
version = r.Bytes()[magicByteOffset]
|
||||
default:
|
||||
b := make([]byte, magicByteOffset+1)
|
||||
if n, err := io.ReadFull(d.reader, b); err != nil {
|
||||
return 4 + int64(n), dontExpectEOF(err)
|
||||
}
|
||||
version = b[magicByteOffset]
|
||||
// Reconstruct the prefix that we had to read to determine the version
|
||||
// of the record set from the magic byte.
|
||||
//
|
||||
// Technically this may recursively stack readers when consuming all
|
||||
// items of the batch, which could hurt performance. In practice this
|
||||
// path should not be taken tho, since the decoder would read from a
|
||||
// *bufio.Reader which implements the bufferedReader interface.
|
||||
d.reader = io.MultiReader(bytes.NewReader(b), d.reader)
|
||||
}
|
||||
|
||||
var tmp RecordSet
|
||||
switch version {
|
||||
case 0, 1:
|
||||
err = tmp.readFromVersion1(d)
|
||||
case 2:
|
||||
err = tmp.readFromVersion2(d)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported message version %d for message of size %d", version, size)
|
||||
}
|
||||
|
||||
if tmp.Version > rs.Version {
|
||||
rs.Version = tmp.Version
|
||||
}
|
||||
|
||||
rs.Attributes |= tmp.Attributes
|
||||
|
||||
if tmp.Records != nil {
|
||||
stream.Records = append(stream.Records, tmp.Records)
|
||||
}
|
||||
}
|
||||
|
||||
if len(stream.Records) != 0 {
|
||||
rs.Records = stream
|
||||
// Ignore errors if we've successfully read records, so the
|
||||
// program can keep making progress.
|
||||
err = nil
|
||||
}
|
||||
|
||||
d.discardAll()
|
||||
rn := 4 + (int(size) - d.remain)
|
||||
d.remain = limit - rn
|
||||
return int64(rn), err
|
||||
}
|
||||
|
||||
// WriteTo writes the representation of rs into w. The value of rs.Version
|
||||
// dictates which format that the record set will be represented as.
|
||||
//
|
||||
// The error will be ErrNoRecord if rs contained no records.
|
||||
//
|
||||
// Note: since this package is only compatible with kafka 0.10 and above, the
|
||||
// method never produces messages in version 0. If rs.Version is zero, the
|
||||
// method defaults to producing messages in version 1.
|
||||
func (rs *RecordSet) WriteTo(w io.Writer) (int64, error) {
|
||||
if rs.Records == nil {
|
||||
return 0, ErrNoRecord
|
||||
}
|
||||
|
||||
// This optimization avoids rendering the record set in an intermediary
|
||||
// buffer when the writer is already a pageBuffer, which is a common case
|
||||
// due to the way WriteRequest and WriteResponse are implemented.
|
||||
buffer, _ := w.(*pageBuffer)
|
||||
bufferOffset := int64(0)
|
||||
|
||||
if buffer != nil {
|
||||
bufferOffset = buffer.Size()
|
||||
} else {
|
||||
buffer = newPageBuffer()
|
||||
defer buffer.unref()
|
||||
}
|
||||
|
||||
size := packUint32(0)
|
||||
buffer.Write(size[:]) // size placeholder
|
||||
|
||||
var err error
|
||||
switch rs.Version {
|
||||
case 0, 1:
|
||||
err = rs.writeToVersion1(buffer, bufferOffset+4)
|
||||
case 2:
|
||||
err = rs.writeToVersion2(buffer, bufferOffset+4)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported record set version %d", rs.Version)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n := buffer.Size() - bufferOffset
|
||||
if n == 0 {
|
||||
size = packUint32(^uint32(0))
|
||||
} else {
|
||||
size = packUint32(uint32(n) - 4)
|
||||
}
|
||||
buffer.WriteAt(size[:], bufferOffset)
|
||||
|
||||
// This condition indicates that the output writer received by `WriteTo` was
|
||||
// not a *pageBuffer, in which case we need to flush the buffered records
|
||||
// data into it.
|
||||
if buffer != w {
|
||||
return buffer.WriteTo(w)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// RawRecordSet represents a record set for a RawProduce request. The record set is
|
||||
// represented as a raw sequence of pre-encoded record set bytes.
|
||||
type RawRecordSet struct {
|
||||
// Reader exposes the raw sequence of record set bytes.
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
// ReadFrom reads the representation of a record set from r into rrs. It re-uses the
|
||||
// existing RecordSet.ReadFrom implementation to first read/decode data into a RecordSet,
|
||||
// then writes/encodes the RecordSet to a buffer referenced by the RawRecordSet.
|
||||
//
|
||||
// Note: re-using the RecordSet.ReadFrom implementation makes this suboptimal from a
|
||||
// performance standpoint as it requires an extra copy of the record bytes. Holding off
|
||||
// on optimizing, as this code path is only invoked in tests.
|
||||
func (rrs *RawRecordSet) ReadFrom(r io.Reader) (int64, error) {
|
||||
rs := &RecordSet{}
|
||||
n, err := rs.ReadFrom(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
rs.WriteTo(buf)
|
||||
*rrs = RawRecordSet{
|
||||
Reader: buf,
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// WriteTo writes the RawRecordSet to an io.Writer. Since this is a raw record set representation, all that is
|
||||
// done here is copying bytes from the underlying reader to the specified writer.
|
||||
func (rrs *RawRecordSet) WriteTo(w io.Writer) (int64, error) {
|
||||
if rrs.Reader == nil {
|
||||
return 0, ErrNoRecord
|
||||
}
|
||||
|
||||
return io.Copy(w, rrs.Reader)
|
||||
}
|
||||
|
||||
func makeTime(t int64) time.Time {
|
||||
return time.Unix(t/1000, (t%1000)*int64(time.Millisecond))
|
||||
}
|
||||
|
||||
func timestamp(t time.Time) int64 {
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return t.UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
|
||||
func packUint32(u uint32) (b [4]byte) {
|
||||
binary.BigEndian.PutUint32(b[:], u)
|
||||
return
|
||||
}
|
||||
|
||||
func packUint64(u uint64) (b [8]byte) {
|
||||
binary.BigEndian.PutUint64(b[:], u)
|
||||
return
|
||||
}
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RecordReader is an interface representing a sequence of records. Record sets
|
||||
// are used in both produce and fetch requests to represent the sequence of
|
||||
// records that are sent to or receive from kafka brokers.
|
||||
//
|
||||
// RecordSet values are not safe to use concurrently from multiple goroutines.
|
||||
type RecordReader interface {
|
||||
// Returns the next record in the set, or io.EOF if the end of the sequence
|
||||
// has been reached.
|
||||
//
|
||||
// The returned Record is guaranteed to be valid until the next call to
|
||||
// ReadRecord. If the program needs to retain the Record value it must make
|
||||
// a copy.
|
||||
ReadRecord() (*Record, error)
|
||||
}
|
||||
|
||||
// NewRecordReader constructs a reader exposing the records passed as arguments.
|
||||
func NewRecordReader(records ...Record) RecordReader {
|
||||
switch len(records) {
|
||||
case 0:
|
||||
return emptyRecordReader{}
|
||||
default:
|
||||
r := &recordReader{records: make([]Record, len(records))}
|
||||
copy(r.records, records)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// MultiRecordReader merges multiple record batches into one.
|
||||
func MultiRecordReader(batches ...RecordReader) RecordReader {
|
||||
switch len(batches) {
|
||||
case 0:
|
||||
return emptyRecordReader{}
|
||||
case 1:
|
||||
return batches[0]
|
||||
default:
|
||||
m := &multiRecordReader{batches: make([]RecordReader, len(batches))}
|
||||
copy(m.batches, batches)
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
func forEachRecord(r RecordReader, f func(int, *Record) error) error {
|
||||
for i := 0; ; i++ {
|
||||
rec, err := r.ReadRecord()
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := handleRecord(i, rec, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleRecord(i int, r *Record, f func(int, *Record) error) error {
|
||||
if r.Key != nil {
|
||||
defer r.Key.Close()
|
||||
}
|
||||
if r.Value != nil {
|
||||
defer r.Value.Close()
|
||||
}
|
||||
return f(i, r)
|
||||
}
|
||||
|
||||
type recordReader struct {
|
||||
records []Record
|
||||
index int
|
||||
}
|
||||
|
||||
func (r *recordReader) ReadRecord() (*Record, error) {
|
||||
if i := r.index; i >= 0 && i < len(r.records) {
|
||||
r.index++
|
||||
return &r.records[i], nil
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
type multiRecordReader struct {
|
||||
batches []RecordReader
|
||||
index int
|
||||
}
|
||||
|
||||
func (m *multiRecordReader) ReadRecord() (*Record, error) {
|
||||
for {
|
||||
if m.index == len(m.batches) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
r, err := m.batches[m.index].ReadRecord()
|
||||
if err == nil {
|
||||
return r, nil
|
||||
}
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
m.index++
|
||||
}
|
||||
}
|
||||
|
||||
// optimizedRecordReader is an implementation of a RecordReader which exposes a
|
||||
// sequence.
|
||||
type optimizedRecordReader struct {
|
||||
records []optimizedRecord
|
||||
index int
|
||||
buffer Record
|
||||
headers [][]Header
|
||||
}
|
||||
|
||||
func (r *optimizedRecordReader) ReadRecord() (*Record, error) {
|
||||
if i := r.index; i >= 0 && i < len(r.records) {
|
||||
rec := &r.records[i]
|
||||
r.index++
|
||||
r.buffer = Record{
|
||||
Offset: rec.offset,
|
||||
Time: rec.time(),
|
||||
Key: rec.key(),
|
||||
Value: rec.value(),
|
||||
}
|
||||
if i < len(r.headers) {
|
||||
r.buffer.Headers = r.headers[i]
|
||||
}
|
||||
return &r.buffer, nil
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
type optimizedRecord struct {
|
||||
offset int64
|
||||
timestamp int64
|
||||
keyRef *pageRef
|
||||
valueRef *pageRef
|
||||
}
|
||||
|
||||
func (r *optimizedRecord) time() time.Time {
|
||||
return makeTime(r.timestamp)
|
||||
}
|
||||
|
||||
func (r *optimizedRecord) key() Bytes {
|
||||
return makeBytes(r.keyRef)
|
||||
}
|
||||
|
||||
func (r *optimizedRecord) value() Bytes {
|
||||
return makeBytes(r.valueRef)
|
||||
}
|
||||
|
||||
func makeBytes(ref *pageRef) Bytes {
|
||||
if ref == nil {
|
||||
return nil
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
type emptyRecordReader struct{}
|
||||
|
||||
func (emptyRecordReader) ReadRecord() (*Record, error) { return nil, io.EOF }
|
||||
|
||||
// ControlRecord represents a record read from a control batch.
|
||||
type ControlRecord struct {
|
||||
Offset int64
|
||||
Time time.Time
|
||||
Version int16
|
||||
Type int16
|
||||
Data []byte
|
||||
Headers []Header
|
||||
}
|
||||
|
||||
func ReadControlRecord(r *Record) (*ControlRecord, error) {
|
||||
if r.Key != nil {
|
||||
defer r.Key.Close()
|
||||
}
|
||||
if r.Value != nil {
|
||||
defer r.Value.Close()
|
||||
}
|
||||
|
||||
k, err := ReadAll(r.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k == nil {
|
||||
return nil, Error("invalid control record with nil key")
|
||||
}
|
||||
if len(k) != 4 {
|
||||
return nil, Errorf("invalid control record with key of size %d", len(k))
|
||||
}
|
||||
|
||||
v, err := ReadAll(r.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &ControlRecord{
|
||||
Offset: r.Offset,
|
||||
Time: r.Time,
|
||||
Version: readInt16(k[:2]),
|
||||
Type: readInt16(k[2:]),
|
||||
Data: v,
|
||||
Headers: r.Headers,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cr *ControlRecord) Key() Bytes {
|
||||
k := make([]byte, 4)
|
||||
writeInt16(k[:2], cr.Version)
|
||||
writeInt16(k[2:], cr.Type)
|
||||
return NewBytes(k)
|
||||
}
|
||||
|
||||
func (cr *ControlRecord) Value() Bytes {
|
||||
return NewBytes(cr.Data)
|
||||
}
|
||||
|
||||
func (cr *ControlRecord) Record() Record {
|
||||
return Record{
|
||||
Offset: cr.Offset,
|
||||
Time: cr.Time,
|
||||
Key: cr.Key(),
|
||||
Value: cr.Value(),
|
||||
Headers: cr.Headers,
|
||||
}
|
||||
}
|
||||
|
||||
// ControlBatch is an implementation of the RecordReader interface representing
|
||||
// control batches returned by kafka brokers.
|
||||
type ControlBatch struct {
|
||||
Attributes Attributes
|
||||
PartitionLeaderEpoch int32
|
||||
BaseOffset int64
|
||||
ProducerID int64
|
||||
ProducerEpoch int16
|
||||
BaseSequence int32
|
||||
Records RecordReader
|
||||
}
|
||||
|
||||
// NewControlBatch constructs a control batch from the list of records passed as
|
||||
// arguments.
|
||||
func NewControlBatch(records ...ControlRecord) *ControlBatch {
|
||||
rawRecords := make([]Record, len(records))
|
||||
for i, cr := range records {
|
||||
rawRecords[i] = cr.Record()
|
||||
}
|
||||
return &ControlBatch{
|
||||
Records: NewRecordReader(rawRecords...),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ControlBatch) ReadRecord() (*Record, error) {
|
||||
return c.Records.ReadRecord()
|
||||
}
|
||||
|
||||
func (c *ControlBatch) ReadControlRecord() (*ControlRecord, error) {
|
||||
r, err := c.ReadRecord()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Key != nil {
|
||||
defer r.Key.Close()
|
||||
}
|
||||
if r.Value != nil {
|
||||
defer r.Value.Close()
|
||||
}
|
||||
return ReadControlRecord(r)
|
||||
}
|
||||
|
||||
func (c *ControlBatch) Offset() int64 {
|
||||
return c.BaseOffset
|
||||
}
|
||||
|
||||
func (c *ControlBatch) Version() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
// RecordBatch is an implementation of the RecordReader interface representing
|
||||
// regular record batches (v2).
|
||||
type RecordBatch struct {
|
||||
Attributes Attributes
|
||||
PartitionLeaderEpoch int32
|
||||
BaseOffset int64
|
||||
ProducerID int64
|
||||
ProducerEpoch int16
|
||||
BaseSequence int32
|
||||
Records RecordReader
|
||||
}
|
||||
|
||||
func (r *RecordBatch) ReadRecord() (*Record, error) {
|
||||
return r.Records.ReadRecord()
|
||||
}
|
||||
|
||||
func (r *RecordBatch) Offset() int64 {
|
||||
return r.BaseOffset
|
||||
}
|
||||
|
||||
func (r *RecordBatch) Version() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
// MessageSet is an implementation of the RecordReader interface representing
|
||||
// regular message sets (v1).
|
||||
type MessageSet struct {
|
||||
Attributes Attributes
|
||||
BaseOffset int64
|
||||
Records RecordReader
|
||||
}
|
||||
|
||||
func (m *MessageSet) ReadRecord() (*Record, error) {
|
||||
return m.Records.ReadRecord()
|
||||
}
|
||||
|
||||
func (m *MessageSet) Offset() int64 {
|
||||
return m.BaseOffset
|
||||
}
|
||||
|
||||
func (m *MessageSet) Version() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// RecordStream is an implementation of the RecordReader interface which
|
||||
// combines multiple underlying RecordReader and only expose records that
|
||||
// are not from control batches.
|
||||
type RecordStream struct {
|
||||
Records []RecordReader
|
||||
index int
|
||||
}
|
||||
|
||||
func (s *RecordStream) ReadRecord() (*Record, error) {
|
||||
for {
|
||||
if s.index < 0 || s.index >= len(s.Records) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
if _, isControl := s.Records[s.index].(*ControlBatch); isControl {
|
||||
s.index++
|
||||
continue
|
||||
}
|
||||
|
||||
r, err := s.Records[s.index].ReadRecord()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
s.index++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
func readMessage(b *pageBuffer, d *decoder) (attributes int8, baseOffset, timestamp int64, key, value Bytes, err error) {
|
||||
md := decoder{
|
||||
reader: d,
|
||||
remain: 12,
|
||||
}
|
||||
|
||||
baseOffset = md.readInt64()
|
||||
md.remain = int(md.readInt32())
|
||||
|
||||
crc := uint32(md.readInt32())
|
||||
md.setCRC(crc32.IEEETable)
|
||||
magicByte := md.readInt8()
|
||||
attributes = md.readInt8()
|
||||
timestamp = int64(0)
|
||||
|
||||
if magicByte != 0 {
|
||||
timestamp = md.readInt64()
|
||||
}
|
||||
|
||||
keyOffset := b.Size()
|
||||
keyLength := int(md.readInt32())
|
||||
hasKey := keyLength >= 0
|
||||
if hasKey {
|
||||
md.writeTo(b, keyLength)
|
||||
key = b.ref(keyOffset, b.Size())
|
||||
}
|
||||
|
||||
valueOffset := b.Size()
|
||||
valueLength := int(md.readInt32())
|
||||
hasValue := valueLength >= 0
|
||||
if hasValue {
|
||||
md.writeTo(b, valueLength)
|
||||
value = b.ref(valueOffset, b.Size())
|
||||
}
|
||||
|
||||
if md.crc32 != crc {
|
||||
err = Errorf("crc32 checksum mismatch (computed=%d found=%d)", md.crc32, crc)
|
||||
} else {
|
||||
err = dontExpectEOF(md.err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rs *RecordSet) readFromVersion1(d *decoder) error {
|
||||
var records RecordReader
|
||||
|
||||
b := newPageBuffer()
|
||||
defer b.unref()
|
||||
|
||||
attributes, baseOffset, timestamp, key, value, err := readMessage(b, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if compression := Attributes(attributes).Compression(); compression == 0 {
|
||||
records = &message{
|
||||
Record: Record{
|
||||
Offset: baseOffset,
|
||||
Time: makeTime(timestamp),
|
||||
Key: key,
|
||||
Value: value,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Can we have a non-nil key when reading a compressed message?
|
||||
if key != nil {
|
||||
key.Close()
|
||||
}
|
||||
if value == nil {
|
||||
records = emptyRecordReader{}
|
||||
} else {
|
||||
defer value.Close()
|
||||
|
||||
codec := compression.Codec()
|
||||
if codec == nil {
|
||||
return Errorf("unsupported compression codec: %d", compression)
|
||||
}
|
||||
decompressor := codec.NewReader(value)
|
||||
defer decompressor.Close()
|
||||
|
||||
b := newPageBuffer()
|
||||
defer b.unref()
|
||||
|
||||
d := &decoder{
|
||||
reader: decompressor,
|
||||
remain: math.MaxInt32,
|
||||
}
|
||||
|
||||
r := &recordReader{
|
||||
records: make([]Record, 0, 32),
|
||||
}
|
||||
|
||||
for !d.done() {
|
||||
_, offset, timestamp, key, value, err := readMessage(b, d)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
break
|
||||
}
|
||||
for _, rec := range r.records {
|
||||
closeBytes(rec.Key)
|
||||
closeBytes(rec.Value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
r.records = append(r.records, Record{
|
||||
Offset: offset,
|
||||
Time: makeTime(timestamp),
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
if baseOffset != 0 {
|
||||
// https://kafka.apache.org/documentation/#messageset
|
||||
//
|
||||
// In version 1, to avoid server side re-compression, only the
|
||||
// wrapper message will be assigned an offset. The inner messages
|
||||
// will have relative offsets. The absolute offset can be computed
|
||||
// using the offset from the outer message, which corresponds to the
|
||||
// offset assigned to the last inner message.
|
||||
lastRelativeOffset := int64(len(r.records)) - 1
|
||||
|
||||
for i := range r.records {
|
||||
r.records[i].Offset = baseOffset - (lastRelativeOffset - r.records[i].Offset)
|
||||
}
|
||||
}
|
||||
|
||||
records = r
|
||||
}
|
||||
}
|
||||
|
||||
*rs = RecordSet{
|
||||
Version: 1,
|
||||
Attributes: Attributes(attributes),
|
||||
Records: records,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *RecordSet) writeToVersion1(buffer *pageBuffer, bufferOffset int64) error {
|
||||
attributes := rs.Attributes
|
||||
records := rs.Records
|
||||
|
||||
if compression := attributes.Compression(); compression != 0 {
|
||||
if codec := compression.Codec(); codec != nil {
|
||||
// In the message format version 1, compression is achieved by
|
||||
// compressing the value of a message which recursively contains
|
||||
// the representation of the compressed message set.
|
||||
subset := *rs
|
||||
subset.Attributes &= ^7 // erase compression
|
||||
|
||||
if err := subset.writeToVersion1(buffer, bufferOffset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compressed := newPageBuffer()
|
||||
defer compressed.unref()
|
||||
|
||||
compressor := codec.NewWriter(compressed)
|
||||
defer compressor.Close()
|
||||
|
||||
var err error
|
||||
buffer.pages.scan(bufferOffset, buffer.Size(), func(b []byte) bool {
|
||||
_, err = compressor.Write(b)
|
||||
return err == nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := compressor.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer.Truncate(int(bufferOffset))
|
||||
|
||||
records = &message{
|
||||
Record: Record{
|
||||
Value: compressed,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e := encoder{writer: buffer}
|
||||
currentTimestamp := timestamp(time.Now())
|
||||
|
||||
return forEachRecord(records, func(i int, r *Record) error {
|
||||
t := timestamp(r.Time)
|
||||
if t == 0 {
|
||||
t = currentTimestamp
|
||||
}
|
||||
|
||||
messageOffset := buffer.Size()
|
||||
e.writeInt64(int64(i))
|
||||
e.writeInt32(0) // message size placeholder
|
||||
e.writeInt32(0) // crc32 placeholder
|
||||
e.setCRC(crc32.IEEETable)
|
||||
e.writeInt8(1) // magic byte: version 1
|
||||
e.writeInt8(int8(attributes))
|
||||
e.writeInt64(t)
|
||||
|
||||
if err := e.writeNullBytesFrom(r.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.writeNullBytesFrom(r.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b0 := packUint32(uint32(buffer.Size() - (messageOffset + 12)))
|
||||
b1 := packUint32(e.crc32)
|
||||
|
||||
buffer.WriteAt(b0[:], messageOffset+8)
|
||||
buffer.WriteAt(b1[:], messageOffset+12)
|
||||
e.setCRC(nil)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type message struct {
|
||||
Record Record
|
||||
read bool
|
||||
}
|
||||
|
||||
func (m *message) ReadRecord() (*Record, error) {
|
||||
if m.read {
|
||||
return nil, io.EOF
|
||||
}
|
||||
m.read = true
|
||||
return &m.Record, nil
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (rs *RecordSet) readFromVersion2(d *decoder) error {
|
||||
baseOffset := d.readInt64()
|
||||
batchLength := d.readInt32()
|
||||
|
||||
if int(batchLength) > d.remain || d.err != nil {
|
||||
d.discardAll()
|
||||
return nil
|
||||
}
|
||||
|
||||
dec := &decoder{
|
||||
reader: d,
|
||||
remain: int(batchLength),
|
||||
}
|
||||
|
||||
partitionLeaderEpoch := dec.readInt32()
|
||||
magicByte := dec.readInt8()
|
||||
crc := dec.readInt32()
|
||||
|
||||
dec.setCRC(crc32.MakeTable(crc32.Castagnoli))
|
||||
|
||||
attributes := dec.readInt16()
|
||||
lastOffsetDelta := dec.readInt32()
|
||||
firstTimestamp := dec.readInt64()
|
||||
maxTimestamp := dec.readInt64()
|
||||
producerID := dec.readInt64()
|
||||
producerEpoch := dec.readInt16()
|
||||
baseSequence := dec.readInt32()
|
||||
numRecords := dec.readInt32()
|
||||
reader := io.Reader(dec)
|
||||
|
||||
// unused
|
||||
_ = lastOffsetDelta
|
||||
_ = maxTimestamp
|
||||
|
||||
if compression := Attributes(attributes).Compression(); compression != 0 {
|
||||
codec := compression.Codec()
|
||||
if codec == nil {
|
||||
return fmt.Errorf("unsupported compression codec (%d)", compression)
|
||||
}
|
||||
decompressor := codec.NewReader(reader)
|
||||
defer decompressor.Close()
|
||||
reader = decompressor
|
||||
}
|
||||
|
||||
buffer := newPageBuffer()
|
||||
defer buffer.unref()
|
||||
|
||||
_, err := buffer.ReadFrom(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.crc32 != uint32(crc) {
|
||||
return fmt.Errorf("crc32 checksum mismatch (computed=%d found=%d)", dec.crc32, uint32(crc))
|
||||
}
|
||||
|
||||
recordsLength := buffer.Len()
|
||||
dec.reader = buffer
|
||||
dec.remain = recordsLength
|
||||
|
||||
records := make([]optimizedRecord, numRecords)
|
||||
// These are two lazy allocators that will be used to optimize allocation of
|
||||
// page references for keys and values.
|
||||
//
|
||||
// By default, no memory is allocated and on first use, numRecords page refs
|
||||
// are allocated in a contiguous memory space, and the allocators return
|
||||
// pointers into those arrays for each page ref that get requested.
|
||||
//
|
||||
// The reasoning is that kafka partitions typically have records of a single
|
||||
// form, which either have no keys, no values, or both keys and values.
|
||||
// Using lazy allocators adapts nicely to these patterns to only allocate
|
||||
// the memory that is needed by the program, while still reducing the number
|
||||
// of malloc calls made by the program.
|
||||
//
|
||||
// Using a single allocator for both keys and values keeps related values
|
||||
// close by in memory, making access to the records more friendly to CPU
|
||||
// caches.
|
||||
alloc := pageRefAllocator{size: int(numRecords)}
|
||||
// Following the same reasoning that kafka partitions will typically have
|
||||
// records with repeating formats, we expect to either find records with
|
||||
// no headers, or records which always contain headers.
|
||||
//
|
||||
// To reduce the memory footprint when records have no headers, the Header
|
||||
// slices are lazily allocated in a separate array.
|
||||
headers := ([][]Header)(nil)
|
||||
|
||||
for i := range records {
|
||||
r := &records[i]
|
||||
_ = dec.readVarInt() // record length (unused)
|
||||
_ = dec.readInt8() // record attributes (unused)
|
||||
timestampDelta := dec.readVarInt()
|
||||
offsetDelta := dec.readVarInt()
|
||||
|
||||
r.offset = baseOffset + offsetDelta
|
||||
r.timestamp = firstTimestamp + timestampDelta
|
||||
|
||||
keyLength := dec.readVarInt()
|
||||
keyOffset := int64(recordsLength - dec.remain)
|
||||
if keyLength > 0 {
|
||||
dec.discard(int(keyLength))
|
||||
}
|
||||
|
||||
valueLength := dec.readVarInt()
|
||||
valueOffset := int64(recordsLength - dec.remain)
|
||||
if valueLength > 0 {
|
||||
dec.discard(int(valueLength))
|
||||
}
|
||||
|
||||
if numHeaders := dec.readVarInt(); numHeaders > 0 {
|
||||
if headers == nil {
|
||||
headers = make([][]Header, numRecords)
|
||||
}
|
||||
|
||||
h := make([]Header, numHeaders)
|
||||
|
||||
for i := range h {
|
||||
h[i] = Header{
|
||||
Key: dec.readVarString(),
|
||||
Value: dec.readVarBytes(),
|
||||
}
|
||||
}
|
||||
|
||||
headers[i] = h
|
||||
}
|
||||
|
||||
if dec.err != nil {
|
||||
records = records[:i]
|
||||
break
|
||||
}
|
||||
|
||||
if keyLength >= 0 {
|
||||
r.keyRef = alloc.newPageRef()
|
||||
buffer.refTo(r.keyRef, keyOffset, keyOffset+keyLength)
|
||||
}
|
||||
|
||||
if valueLength >= 0 {
|
||||
r.valueRef = alloc.newPageRef()
|
||||
buffer.refTo(r.valueRef, valueOffset, valueOffset+valueLength)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: it's unclear whether kafka 0.11+ still truncates the responses,
|
||||
// all attempts I made at constructing a test to trigger a truncation have
|
||||
// failed. I kept this code here as a safeguard but it may never execute.
|
||||
if dec.err != nil && len(records) == 0 {
|
||||
return dec.err
|
||||
}
|
||||
|
||||
*rs = RecordSet{
|
||||
Version: magicByte,
|
||||
Attributes: Attributes(attributes),
|
||||
Records: &optimizedRecordReader{
|
||||
records: records,
|
||||
headers: headers,
|
||||
},
|
||||
}
|
||||
|
||||
if rs.Attributes.Control() {
|
||||
rs.Records = &ControlBatch{
|
||||
Attributes: rs.Attributes,
|
||||
PartitionLeaderEpoch: partitionLeaderEpoch,
|
||||
BaseOffset: baseOffset,
|
||||
ProducerID: producerID,
|
||||
ProducerEpoch: producerEpoch,
|
||||
BaseSequence: baseSequence,
|
||||
Records: rs.Records,
|
||||
}
|
||||
} else {
|
||||
rs.Records = &RecordBatch{
|
||||
Attributes: rs.Attributes,
|
||||
PartitionLeaderEpoch: partitionLeaderEpoch,
|
||||
BaseOffset: baseOffset,
|
||||
ProducerID: producerID,
|
||||
ProducerEpoch: producerEpoch,
|
||||
BaseSequence: baseSequence,
|
||||
Records: rs.Records,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *RecordSet) writeToVersion2(buffer *pageBuffer, bufferOffset int64) error {
|
||||
records := rs.Records
|
||||
numRecords := int32(0)
|
||||
|
||||
e := &encoder{writer: buffer}
|
||||
e.writeInt64(0) // base offset | 0 +8
|
||||
e.writeInt32(0) // placeholder for record batch length | 8 +4
|
||||
e.writeInt32(-1) // partition leader epoch | 12 +3
|
||||
e.writeInt8(2) // magic byte | 16 +1
|
||||
e.writeInt32(0) // placeholder for crc32 checksum | 17 +4
|
||||
e.writeInt16(int16(rs.Attributes)) // attributes | 21 +2
|
||||
e.writeInt32(0) // placeholder for lastOffsetDelta | 23 +4
|
||||
e.writeInt64(0) // placeholder for firstTimestamp | 27 +8
|
||||
e.writeInt64(0) // placeholder for maxTimestamp | 35 +8
|
||||
e.writeInt64(-1) // producer id | 43 +8
|
||||
e.writeInt16(-1) // producer epoch | 51 +2
|
||||
e.writeInt32(-1) // base sequence | 53 +4
|
||||
e.writeInt32(0) // placeholder for numRecords | 57 +4
|
||||
|
||||
var compressor io.WriteCloser
|
||||
if compression := rs.Attributes.Compression(); compression != 0 {
|
||||
if codec := compression.Codec(); codec != nil {
|
||||
compressor = codec.NewWriter(buffer)
|
||||
e.writer = compressor
|
||||
}
|
||||
}
|
||||
|
||||
currentTimestamp := timestamp(time.Now())
|
||||
lastOffsetDelta := int32(0)
|
||||
firstTimestamp := int64(0)
|
||||
maxTimestamp := int64(0)
|
||||
|
||||
err := forEachRecord(records, func(i int, r *Record) error {
|
||||
t := timestamp(r.Time)
|
||||
if t == 0 {
|
||||
t = currentTimestamp
|
||||
}
|
||||
if i == 0 {
|
||||
firstTimestamp = t
|
||||
}
|
||||
if t > maxTimestamp {
|
||||
maxTimestamp = t
|
||||
}
|
||||
|
||||
timestampDelta := t - firstTimestamp
|
||||
offsetDelta := int64(i)
|
||||
lastOffsetDelta = int32(offsetDelta)
|
||||
|
||||
length := 1 + // attributes
|
||||
sizeOfVarInt(timestampDelta) +
|
||||
sizeOfVarInt(offsetDelta) +
|
||||
sizeOfVarNullBytesIface(r.Key) +
|
||||
sizeOfVarNullBytesIface(r.Value) +
|
||||
sizeOfVarInt(int64(len(r.Headers)))
|
||||
|
||||
for _, h := range r.Headers {
|
||||
length += sizeOfVarString(h.Key) + sizeOfVarNullBytes(h.Value)
|
||||
}
|
||||
|
||||
e.writeVarInt(int64(length))
|
||||
e.writeInt8(0) // record attributes (unused)
|
||||
e.writeVarInt(timestampDelta)
|
||||
e.writeVarInt(offsetDelta)
|
||||
|
||||
if err := e.writeVarNullBytesFrom(r.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.writeVarNullBytesFrom(r.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.writeVarInt(int64(len(r.Headers)))
|
||||
|
||||
for _, h := range r.Headers {
|
||||
e.writeVarString(h.Key)
|
||||
e.writeVarNullBytes(h.Value)
|
||||
}
|
||||
|
||||
numRecords++
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if compressor != nil {
|
||||
if err := compressor.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if numRecords == 0 {
|
||||
return ErrNoRecord
|
||||
}
|
||||
|
||||
b2 := packUint32(uint32(lastOffsetDelta))
|
||||
b3 := packUint64(uint64(firstTimestamp))
|
||||
b4 := packUint64(uint64(maxTimestamp))
|
||||
b5 := packUint32(uint32(numRecords))
|
||||
|
||||
buffer.WriteAt(b2[:], bufferOffset+23)
|
||||
buffer.WriteAt(b3[:], bufferOffset+27)
|
||||
buffer.WriteAt(b4[:], bufferOffset+35)
|
||||
buffer.WriteAt(b5[:], bufferOffset+57)
|
||||
|
||||
totalLength := buffer.Size() - bufferOffset
|
||||
batchLength := totalLength - 12
|
||||
|
||||
checksum := uint32(0)
|
||||
crcTable := crc32.MakeTable(crc32.Castagnoli)
|
||||
|
||||
buffer.pages.scan(bufferOffset+21, bufferOffset+totalLength, func(chunk []byte) bool {
|
||||
checksum = crc32.Update(checksum, crcTable, chunk)
|
||||
return true
|
||||
})
|
||||
|
||||
b0 := packUint32(uint32(batchLength))
|
||||
b1 := packUint32(checksum)
|
||||
|
||||
buffer.WriteAt(b0[:], bufferOffset+8)
|
||||
buffer.WriteAt(b1[:], bufferOffset+17)
|
||||
return nil
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
//go:build !unsafe
|
||||
// +build !unsafe
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type index []int
|
||||
|
||||
type _type struct{ typ reflect.Type }
|
||||
|
||||
func typeOf(x interface{}) _type {
|
||||
return makeType(reflect.TypeOf(x))
|
||||
}
|
||||
|
||||
func elemTypeOf(x interface{}) _type {
|
||||
return makeType(reflect.TypeOf(x).Elem())
|
||||
}
|
||||
|
||||
func makeType(t reflect.Type) _type {
|
||||
return _type{typ: t}
|
||||
}
|
||||
|
||||
type value struct {
|
||||
val reflect.Value
|
||||
}
|
||||
|
||||
func nonAddressableValueOf(x interface{}) value {
|
||||
return value{val: reflect.ValueOf(x)}
|
||||
}
|
||||
|
||||
func valueOf(x interface{}) value {
|
||||
return value{val: reflect.ValueOf(x).Elem()}
|
||||
}
|
||||
|
||||
func (v value) bool() bool { return v.val.Bool() }
|
||||
|
||||
func (v value) int8() int8 { return int8(v.int64()) }
|
||||
|
||||
func (v value) int16() int16 { return int16(v.int64()) }
|
||||
|
||||
func (v value) int32() int32 { return int32(v.int64()) }
|
||||
|
||||
func (v value) int64() int64 { return v.val.Int() }
|
||||
|
||||
func (v value) float64() float64 { return v.val.Float() }
|
||||
|
||||
func (v value) string() string { return v.val.String() }
|
||||
|
||||
func (v value) bytes() []byte { return v.val.Bytes() }
|
||||
|
||||
func (v value) iface(t reflect.Type) interface{} { return v.val.Addr().Interface() }
|
||||
|
||||
func (v value) array(t reflect.Type) array { return array(v) }
|
||||
|
||||
func (v value) setBool(b bool) { v.val.SetBool(b) }
|
||||
|
||||
func (v value) setInt8(i int8) { v.setInt64(int64(i)) }
|
||||
|
||||
func (v value) setInt16(i int16) { v.setInt64(int64(i)) }
|
||||
|
||||
func (v value) setInt32(i int32) { v.setInt64(int64(i)) }
|
||||
|
||||
func (v value) setInt64(i int64) { v.val.SetInt(i) }
|
||||
|
||||
func (v value) setFloat64(f float64) { v.val.SetFloat(f) }
|
||||
|
||||
func (v value) setString(s string) { v.val.SetString(s) }
|
||||
|
||||
func (v value) setBytes(b []byte) { v.val.SetBytes(b) }
|
||||
|
||||
func (v value) setArray(a array) {
|
||||
if a.val.IsValid() {
|
||||
v.val.Set(a.val)
|
||||
} else {
|
||||
v.val.Set(reflect.Zero(v.val.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
func (v value) fieldByIndex(i index) value {
|
||||
return value{val: v.val.FieldByIndex(i)}
|
||||
}
|
||||
|
||||
type array struct {
|
||||
val reflect.Value
|
||||
}
|
||||
|
||||
func makeArray(t reflect.Type, n int) array {
|
||||
return array{val: reflect.MakeSlice(reflect.SliceOf(t), n, n)}
|
||||
}
|
||||
|
||||
func (a array) index(i int) value { return value{val: a.val.Index(i)} }
|
||||
|
||||
func (a array) length() int { return a.val.Len() }
|
||||
|
||||
func (a array) isNil() bool { return a.val.IsNil() }
|
||||
|
||||
func indexOf(s reflect.StructField) index { return index(s.Index) }
|
||||
|
||||
func bytesToString(b []byte) string { return string(b) }
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
//go:build unsafe
|
||||
// +build unsafe
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type iface struct {
|
||||
typ unsafe.Pointer
|
||||
ptr unsafe.Pointer
|
||||
}
|
||||
|
||||
type slice struct {
|
||||
ptr unsafe.Pointer
|
||||
len int
|
||||
cap int
|
||||
}
|
||||
|
||||
type index uintptr
|
||||
|
||||
type _type struct {
|
||||
ptr unsafe.Pointer
|
||||
}
|
||||
|
||||
func typeOf(x interface{}) _type {
|
||||
return _type{ptr: ((*iface)(unsafe.Pointer(&x))).typ}
|
||||
}
|
||||
|
||||
func elemTypeOf(x interface{}) _type {
|
||||
return makeType(reflect.TypeOf(x).Elem())
|
||||
}
|
||||
|
||||
func makeType(t reflect.Type) _type {
|
||||
return _type{ptr: ((*iface)(unsafe.Pointer(&t))).ptr}
|
||||
}
|
||||
|
||||
type value struct {
|
||||
ptr unsafe.Pointer
|
||||
}
|
||||
|
||||
func nonAddressableValueOf(x interface{}) value {
|
||||
return valueOf(x)
|
||||
}
|
||||
|
||||
func valueOf(x interface{}) value {
|
||||
return value{ptr: ((*iface)(unsafe.Pointer(&x))).ptr}
|
||||
}
|
||||
|
||||
func makeValue(t reflect.Type) value {
|
||||
return value{ptr: unsafe.Pointer(reflect.New(t).Pointer())}
|
||||
}
|
||||
|
||||
func (v value) bool() bool { return *(*bool)(v.ptr) }
|
||||
|
||||
func (v value) int8() int8 { return *(*int8)(v.ptr) }
|
||||
|
||||
func (v value) int16() int16 { return *(*int16)(v.ptr) }
|
||||
|
||||
func (v value) int32() int32 { return *(*int32)(v.ptr) }
|
||||
|
||||
func (v value) int64() int64 { return *(*int64)(v.ptr) }
|
||||
|
||||
func (v value) float64() float64 { return *(*float64)(v.ptr) }
|
||||
|
||||
func (v value) string() string { return *(*string)(v.ptr) }
|
||||
|
||||
func (v value) bytes() []byte { return *(*[]byte)(v.ptr) }
|
||||
|
||||
func (v value) iface(t reflect.Type) interface{} {
|
||||
return *(*interface{})(unsafe.Pointer(&iface{
|
||||
typ: ((*iface)(unsafe.Pointer(&t))).ptr,
|
||||
ptr: v.ptr,
|
||||
}))
|
||||
}
|
||||
|
||||
func (v value) array(t reflect.Type) array {
|
||||
return array{
|
||||
size: uintptr(t.Size()),
|
||||
elem: ((*slice)(v.ptr)).ptr,
|
||||
len: ((*slice)(v.ptr)).len,
|
||||
}
|
||||
}
|
||||
|
||||
func (v value) setBool(b bool) { *(*bool)(v.ptr) = b }
|
||||
|
||||
func (v value) setInt8(i int8) { *(*int8)(v.ptr) = i }
|
||||
|
||||
func (v value) setInt16(i int16) { *(*int16)(v.ptr) = i }
|
||||
|
||||
func (v value) setInt32(i int32) { *(*int32)(v.ptr) = i }
|
||||
|
||||
func (v value) setInt64(i int64) { *(*int64)(v.ptr) = i }
|
||||
|
||||
func (v value) setFloat64(f float64) { *(*float64)(v.ptr) = f }
|
||||
|
||||
func (v value) setString(s string) { *(*string)(v.ptr) = s }
|
||||
|
||||
func (v value) setBytes(b []byte) { *(*[]byte)(v.ptr) = b }
|
||||
|
||||
func (v value) setArray(a array) { *(*slice)(v.ptr) = slice{ptr: a.elem, len: a.len, cap: a.len} }
|
||||
|
||||
func (v value) fieldByIndex(i index) value {
|
||||
return value{ptr: unsafe.Pointer(uintptr(v.ptr) + uintptr(i))}
|
||||
}
|
||||
|
||||
type array struct {
|
||||
elem unsafe.Pointer
|
||||
size uintptr
|
||||
len int
|
||||
}
|
||||
|
||||
var (
|
||||
emptyArray struct{}
|
||||
)
|
||||
|
||||
func makeArray(t reflect.Type, n int) array {
|
||||
var elem unsafe.Pointer
|
||||
var size = uintptr(t.Size())
|
||||
if n == 0 {
|
||||
elem = unsafe.Pointer(&emptyArray)
|
||||
} else {
|
||||
elem = unsafe_NewArray(((*iface)(unsafe.Pointer(&t))).ptr, n)
|
||||
}
|
||||
return array{elem: elem, size: size, len: n}
|
||||
}
|
||||
|
||||
func (a array) index(i int) value {
|
||||
return value{ptr: unsafe.Pointer(uintptr(a.elem) + (uintptr(i) * a.size))}
|
||||
}
|
||||
|
||||
func (a array) length() int { return a.len }
|
||||
|
||||
func (a array) isNil() bool { return a.elem == nil }
|
||||
|
||||
func indexOf(s reflect.StructField) index { return index(s.Offset) }
|
||||
|
||||
func bytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) }
|
||||
|
||||
//go:linkname unsafe_NewArray reflect.unsafe_NewArray
|
||||
func unsafe_NewArray(rtype unsafe.Pointer, length int) unsafe.Pointer
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func ReadRequest(r io.Reader) (apiVersion int16, correlationID int32, clientID string, msg Message, err error) {
|
||||
d := &decoder{reader: r, remain: 4}
|
||||
size := d.readInt32()
|
||||
|
||||
if err = d.err; err != nil {
|
||||
err = dontExpectEOF(err)
|
||||
return
|
||||
}
|
||||
|
||||
d.remain = int(size)
|
||||
apiKey := ApiKey(d.readInt16())
|
||||
apiVersion = d.readInt16()
|
||||
correlationID = d.readInt32()
|
||||
clientID = d.readString()
|
||||
|
||||
if i := int(apiKey); i < 0 || i >= len(apiTypes) {
|
||||
err = fmt.Errorf("unsupported api key: %d", i)
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.err; err != nil {
|
||||
err = dontExpectEOF(err)
|
||||
return
|
||||
}
|
||||
|
||||
t := &apiTypes[apiKey]
|
||||
if t == nil {
|
||||
err = fmt.Errorf("unsupported api: %s", apiNames[apiKey])
|
||||
return
|
||||
}
|
||||
|
||||
minVersion := t.minVersion()
|
||||
maxVersion := t.maxVersion()
|
||||
|
||||
if apiVersion < minVersion || apiVersion > maxVersion {
|
||||
err = fmt.Errorf("unsupported %s version: v%d not in range v%d-v%d", apiKey, apiVersion, minVersion, maxVersion)
|
||||
return
|
||||
}
|
||||
|
||||
req := &t.requests[apiVersion-minVersion]
|
||||
|
||||
if req.flexible {
|
||||
// In the flexible case, there's a tag buffer at the end of the request header
|
||||
taggedCount := int(d.readUnsignedVarInt())
|
||||
for i := 0; i < taggedCount; i++ {
|
||||
d.readUnsignedVarInt() // tagID
|
||||
size := d.readUnsignedVarInt()
|
||||
|
||||
// Just throw away the values for now
|
||||
d.read(int(size))
|
||||
}
|
||||
}
|
||||
|
||||
msg = req.new()
|
||||
req.decode(d, valueOf(msg))
|
||||
d.discardAll()
|
||||
|
||||
if err = d.err; err != nil {
|
||||
err = dontExpectEOF(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WriteRequest(w io.Writer, apiVersion int16, correlationID int32, clientID string, msg Message) error {
|
||||
apiKey := msg.ApiKey()
|
||||
|
||||
if i := int(apiKey); i < 0 || i >= len(apiTypes) {
|
||||
return fmt.Errorf("unsupported api key: %d", i)
|
||||
}
|
||||
|
||||
t := &apiTypes[apiKey]
|
||||
if t == nil {
|
||||
return fmt.Errorf("unsupported api: %s", apiNames[apiKey])
|
||||
}
|
||||
|
||||
if typedMessage, ok := msg.(OverrideTypeMessage); ok {
|
||||
typeKey := typedMessage.TypeKey()
|
||||
overrideType := overrideApiTypes[apiKey][typeKey]
|
||||
t = &overrideType
|
||||
}
|
||||
|
||||
minVersion := t.minVersion()
|
||||
maxVersion := t.maxVersion()
|
||||
|
||||
if apiVersion < minVersion || apiVersion > maxVersion {
|
||||
return fmt.Errorf("unsupported %s version: v%d not in range v%d-v%d", apiKey, apiVersion, minVersion, maxVersion)
|
||||
}
|
||||
|
||||
r := &t.requests[apiVersion-minVersion]
|
||||
v := valueOf(msg)
|
||||
b := newPageBuffer()
|
||||
defer b.unref()
|
||||
|
||||
e := &encoder{writer: b}
|
||||
e.writeInt32(0) // placeholder for the request size
|
||||
e.writeInt16(int16(apiKey))
|
||||
e.writeInt16(apiVersion)
|
||||
e.writeInt32(correlationID)
|
||||
|
||||
if r.flexible {
|
||||
// Flexible messages use a nullable string for the client ID, then extra space for a
|
||||
// tag buffer, which begins with a size value. Since we're not writing any fields into the
|
||||
// latter, we can just write zero for now.
|
||||
//
|
||||
// See
|
||||
// https://cwiki.apache.org/confluence/display/KAFKA/KIP-482%3A+The+Kafka+Protocol+should+Support+Optional+Tagged+Fields
|
||||
// for details.
|
||||
e.writeNullString(clientID)
|
||||
e.writeUnsignedVarInt(0)
|
||||
} else {
|
||||
// Technically, recent versions of kafka interpret this field as a nullable
|
||||
// string, however kafka 0.10 expected a non-nullable string and fails with
|
||||
// a NullPointerException when it receives a null client id.
|
||||
e.writeString(clientID)
|
||||
}
|
||||
r.encode(e, v)
|
||||
err := e.err
|
||||
|
||||
if err == nil {
|
||||
size := packUint32(uint32(b.Size()) - 4)
|
||||
b.WriteAt(size[:], 0)
|
||||
_, err = b.WriteTo(w)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func ReadResponse(r io.Reader, apiKey ApiKey, apiVersion int16) (correlationID int32, msg Message, err error) {
|
||||
if i := int(apiKey); i < 0 || i >= len(apiTypes) {
|
||||
err = fmt.Errorf("unsupported api key: %d", i)
|
||||
return
|
||||
}
|
||||
|
||||
t := &apiTypes[apiKey]
|
||||
if t == nil {
|
||||
err = fmt.Errorf("unsupported api: %s", apiNames[apiKey])
|
||||
return
|
||||
}
|
||||
|
||||
minVersion := t.minVersion()
|
||||
maxVersion := t.maxVersion()
|
||||
|
||||
if apiVersion < minVersion || apiVersion > maxVersion {
|
||||
err = fmt.Errorf("unsupported %s version: v%d not in range v%d-v%d", apiKey, apiVersion, minVersion, maxVersion)
|
||||
return
|
||||
}
|
||||
|
||||
d := &decoder{reader: r, remain: 4}
|
||||
size := d.readInt32()
|
||||
|
||||
if err = d.err; err != nil {
|
||||
err = dontExpectEOF(err)
|
||||
return
|
||||
}
|
||||
|
||||
d.remain = int(size)
|
||||
correlationID = d.readInt32()
|
||||
if err = d.err; err != nil {
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
// If a Writer/Reader is configured without TLS and connects
|
||||
// to a broker expecting TLS the only message we return to the
|
||||
// caller is io.ErrUnexpetedEOF which is opaque. This section
|
||||
// tries to determine if that's what has happened.
|
||||
// We first deconstruct the initial 4 bytes of the message
|
||||
// from the size which was read earlier.
|
||||
// Next, we examine those bytes to see if they looks like a TLS
|
||||
// error message. If they do we wrap the io.ErrUnexpectedEOF
|
||||
// with some context.
|
||||
if looksLikeUnexpectedTLS(size) {
|
||||
err = fmt.Errorf("%w: broker appears to be expecting TLS", io.ErrUnexpectedEOF)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = dontExpectEOF(err)
|
||||
return
|
||||
}
|
||||
|
||||
res := &t.responses[apiVersion-minVersion]
|
||||
|
||||
if res.flexible {
|
||||
// In the flexible case, there's a tag buffer at the end of the response header
|
||||
taggedCount := int(d.readUnsignedVarInt())
|
||||
for i := 0; i < taggedCount; i++ {
|
||||
d.readUnsignedVarInt() // tagID
|
||||
size := d.readUnsignedVarInt()
|
||||
|
||||
// Just throw away the values for now
|
||||
d.read(int(size))
|
||||
}
|
||||
}
|
||||
|
||||
msg = res.new()
|
||||
res.decode(d, valueOf(msg))
|
||||
d.discardAll()
|
||||
|
||||
if err = d.err; err != nil {
|
||||
err = dontExpectEOF(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WriteResponse(w io.Writer, apiVersion int16, correlationID int32, msg Message) error {
|
||||
apiKey := msg.ApiKey()
|
||||
|
||||
if i := int(apiKey); i < 0 || i >= len(apiTypes) {
|
||||
return fmt.Errorf("unsupported api key: %d", i)
|
||||
}
|
||||
|
||||
t := &apiTypes[apiKey]
|
||||
if t == nil {
|
||||
return fmt.Errorf("unsupported api: %s", apiNames[apiKey])
|
||||
}
|
||||
|
||||
if typedMessage, ok := msg.(OverrideTypeMessage); ok {
|
||||
typeKey := typedMessage.TypeKey()
|
||||
overrideType := overrideApiTypes[apiKey][typeKey]
|
||||
t = &overrideType
|
||||
}
|
||||
|
||||
minVersion := t.minVersion()
|
||||
maxVersion := t.maxVersion()
|
||||
|
||||
if apiVersion < minVersion || apiVersion > maxVersion {
|
||||
return fmt.Errorf("unsupported %s version: v%d not in range v%d-v%d", apiKey, apiVersion, minVersion, maxVersion)
|
||||
}
|
||||
|
||||
r := &t.responses[apiVersion-minVersion]
|
||||
v := valueOf(msg)
|
||||
b := newPageBuffer()
|
||||
defer b.unref()
|
||||
|
||||
e := &encoder{writer: b}
|
||||
e.writeInt32(0) // placeholder for the response size
|
||||
e.writeInt32(correlationID)
|
||||
if r.flexible {
|
||||
// Flexible messages use extra space for a tag buffer,
|
||||
// which begins with a size value. Since we're not writing any fields into the
|
||||
// latter, we can just write zero for now.
|
||||
//
|
||||
// See
|
||||
// https://cwiki.apache.org/confluence/display/KAFKA/KIP-482%3A+The+Kafka+Protocol+should+Support+Optional+Tagged+Fields
|
||||
// for details.
|
||||
e.writeUnsignedVarInt(0)
|
||||
}
|
||||
r.encode(e, v)
|
||||
err := e.err
|
||||
|
||||
if err == nil {
|
||||
size := packUint32(uint32(b.Size()) - 4)
|
||||
b.WriteAt(size[:], 0)
|
||||
_, err = b.WriteTo(w)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
tlsAlertByte byte = 0x15
|
||||
)
|
||||
|
||||
// looksLikeUnexpectedTLS returns true if the size passed in resemble
|
||||
// the TLS alert message that is returned to a client which sends
|
||||
// an invalid ClientHello message.
|
||||
func looksLikeUnexpectedTLS(size int32) bool {
|
||||
var sizeBytes [4]byte
|
||||
binary.BigEndian.PutUint32(sizeBytes[:], uint32(size))
|
||||
|
||||
if sizeBytes[0] != tlsAlertByte {
|
||||
return false
|
||||
}
|
||||
version := int(sizeBytes[1])<<8 | int(sizeBytes[2])
|
||||
return version <= tls.VersionTLS13 && version >= tls.VersionTLS10
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// RoundTrip sends a request to a kafka broker and returns the response.
|
||||
func RoundTrip(rw io.ReadWriter, apiVersion int16, correlationID int32, clientID string, req Message) (Message, error) {
|
||||
if err := WriteRequest(rw, apiVersion, correlationID, clientID, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasResponse(req) {
|
||||
return nil, nil
|
||||
}
|
||||
id, res, err := ReadResponse(rw, req.ApiKey(), apiVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if id != correlationID {
|
||||
return nil, Errorf("correlation id mismatch (expected=%d, found=%d)", correlationID, id)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func hasResponse(msg Message) bool {
|
||||
x, _ := msg.(interface{ HasResponse() bool })
|
||||
return x == nil || x.HasResponse()
|
||||
}
|
||||
Generated
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
package saslauthenticate
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/segmentio/kafka-go/protocol"
|
||||
)
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
AuthBytes []byte `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Request) RawExchange(rw io.ReadWriter) (protocol.Message, error) {
|
||||
if err := r.writeTo(rw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.readResp(rw)
|
||||
}
|
||||
|
||||
func (*Request) Required(versions map[protocol.ApiKey]int16) bool {
|
||||
const v0 = 0
|
||||
return versions[protocol.SaslHandshake] == v0
|
||||
}
|
||||
|
||||
func (r *Request) writeTo(w io.Writer) error {
|
||||
size := len(r.AuthBytes) + 4
|
||||
buf := make([]byte, size)
|
||||
binary.BigEndian.PutUint32(buf[:4], uint32(len(r.AuthBytes)))
|
||||
copy(buf[4:], r.AuthBytes)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Request) readResp(read io.Reader) (protocol.Message, error) {
|
||||
var lenBuf [4]byte
|
||||
if _, err := io.ReadFull(read, lenBuf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
respLen := int32(binary.BigEndian.Uint32(lenBuf[:]))
|
||||
data := make([]byte, respLen)
|
||||
|
||||
if _, err := io.ReadFull(read, data[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{
|
||||
AuthBytes: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.SaslAuthenticate }
|
||||
|
||||
type Response struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v1"`
|
||||
ErrorMessage string `kafka:"min=v0,max=v1,nullable"`
|
||||
AuthBytes []byte `kafka:"min=v0,max=v1"`
|
||||
SessionLifetimeMs int64 `kafka:"min=v1,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.SaslAuthenticate }
|
||||
|
||||
var _ protocol.RawExchanger = (*Request)(nil)
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
package saslhandshake
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Mechanism string `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.SaslHandshake }
|
||||
|
||||
type Response struct {
|
||||
ErrorCode int16 `kafka:"min=v0,max=v1"`
|
||||
Mechanisms []string `kafka:"min=v0,max=v1"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.SaslHandshake }
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
func sizeOfVarString(s string) int {
|
||||
return sizeOfVarInt(int64(len(s))) + len(s)
|
||||
}
|
||||
|
||||
func sizeOfVarNullBytes(b []byte) int {
|
||||
if b == nil {
|
||||
return sizeOfVarInt(-1)
|
||||
}
|
||||
n := len(b)
|
||||
return sizeOfVarInt(int64(n)) + n
|
||||
}
|
||||
|
||||
func sizeOfVarNullBytesIface(b Bytes) int {
|
||||
if b == nil {
|
||||
return sizeOfVarInt(-1)
|
||||
}
|
||||
n := b.Len()
|
||||
return sizeOfVarInt(int64(n)) + n
|
||||
}
|
||||
|
||||
func sizeOfVarInt(i int64) int {
|
||||
return sizeOfUnsignedVarInt(uint64((i << 1) ^ (i >> 63))) // zig-zag encoding
|
||||
}
|
||||
|
||||
func sizeOfUnsignedVarInt(i uint64) int {
|
||||
return (bits.Len64(i|1) + 6) / 7
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package syncgroup
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v5,tag"`
|
||||
|
||||
GroupID string `kafka:"min=v0,max=v3|min=v4,max=v5,compact"`
|
||||
GenerationID int32 `kafka:"min=v0,max=v5|min=v4,max=v5,compact"`
|
||||
MemberID string `kafka:"min=v0,max=v3|min=v4,max=v5,compact"`
|
||||
GroupInstanceID string `kafka:"min=v3,max=v3,nullable|min=v4,max=v5,nullable,compact"`
|
||||
ProtocolType string `kafka:"min=v5,max=v5"`
|
||||
ProtocolName string `kafka:"min=v5,max=v5"`
|
||||
Assignments []RequestAssignment `kafka:"min=v0,max=v5"`
|
||||
}
|
||||
|
||||
type RequestAssignment struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v5,tag"`
|
||||
|
||||
MemberID string `kafka:"min=v0,max=v3|min=v4,max=v5,compact"`
|
||||
Assignment []byte `kafka:"min=v0,max=v3|min=v4,max=v5,compact"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.SyncGroup }
|
||||
|
||||
func (r *Request) Group() string { return r.GroupID }
|
||||
|
||||
var _ protocol.GroupMessage = (*Request)(nil)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v4,max=v5,tag"`
|
||||
|
||||
ThrottleTimeMS int32 `kafka:"min=v1,max=v5"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v5"`
|
||||
ProtocolType string `kafka:"min=v5,max=v5"`
|
||||
ProtocolName string `kafka:"min=v5,max=v5"`
|
||||
Assignments []byte `kafka:"min=v0,max=v3|min=v4,max=v5,compact"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.SyncGroup }
|
||||
Generated
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
package txnoffsetcommit
|
||||
|
||||
import "github.com/segmentio/kafka-go/protocol"
|
||||
|
||||
func init() {
|
||||
protocol.Register(&Request{}, &Response{})
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
TransactionalID string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
GroupID string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
ProducerID int64 `kafka:"min=v0,max=v3"`
|
||||
ProducerEpoch int16 `kafka:"min=v0,max=v3"`
|
||||
GenerationID int32 `kafka:"min=v3,max=v3"`
|
||||
MemberID string `kafka:"min=v3,max=v3,compact"`
|
||||
GroupInstanceID string `kafka:"min=v3,max=v3,compact,nullable"`
|
||||
Topics []RequestTopic `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type RequestTopic struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
Partitions []RequestPartition `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type RequestPartition struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
Partition int32 `kafka:"min=v0,max=v3"`
|
||||
CommittedOffset int64 `kafka:"min=v0,max=v3"`
|
||||
CommittedLeaderEpoch int32 `kafka:"min=v2,max=v3"`
|
||||
CommittedMetadata string `kafka:"min=v0,max=v2|min=v3,max=v3,nullable,compact"`
|
||||
}
|
||||
|
||||
func (r *Request) ApiKey() protocol.ApiKey { return protocol.TxnOffsetCommit }
|
||||
|
||||
func (r *Request) Group() string { return r.GroupID }
|
||||
|
||||
var _ protocol.GroupMessage = (*Request)(nil)
|
||||
|
||||
type Response struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
ThrottleTimeMs int32 `kafka:"min=v0,max=v3"`
|
||||
Topics []ResponseTopic `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type ResponseTopic struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
Name string `kafka:"min=v0,max=v2|min=v3,max=v3,compact"`
|
||||
Partitions []ResponsePartition `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
type ResponsePartition struct {
|
||||
// We need at least one tagged field to indicate that this is a "flexible" message
|
||||
// type.
|
||||
_ struct{} `kafka:"min=v3,max=v3,tag"`
|
||||
|
||||
Partition int32 `kafka:"min=v0,max=v3"`
|
||||
ErrorCode int16 `kafka:"min=v0,max=v3"`
|
||||
}
|
||||
|
||||
func (r *Response) ApiKey() protocol.ApiKey { return protocol.TxnOffsetCommit }
|
||||
Reference in New Issue
Block a user