Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -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 }
@@ -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 }
@@ -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)
@@ -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)
)
@@ -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
}
@@ -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)
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
)
@@ -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"`
}
@@ -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)
@@ -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)
@@ -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
View File
@@ -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
)
@@ -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)
@@ -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"`
}
@@ -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)
)
@@ -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)
@@ -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)
@@ -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)
@@ -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
}
@@ -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)
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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 }
@@ -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
}
@@ -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 }
@@ -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 }
@@ -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 }
@@ -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 }
@@ -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
}
@@ -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)
)
@@ -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
}
@@ -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"`
}
@@ -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"`
}
@@ -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"`
}
@@ -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
View File
@@ -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
View File
@@ -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))
}
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
}
@@ -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)
@@ -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
View File
@@ -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
}
@@ -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 }
@@ -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 }