github.com/hoveychen/kafka-go@v0.4.42/message_test.go (about) 1 package kafka 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 "io" 10 "math/rand" 11 "os" 12 "testing" 13 "time" 14 15 "github.com/hoveychen/kafka-go/compress/gzip" 16 "github.com/hoveychen/kafka-go/compress/lz4" 17 "github.com/hoveychen/kafka-go/compress/snappy" 18 "github.com/hoveychen/kafka-go/compress/zstd" 19 "github.com/stretchr/testify/require" 20 ) 21 22 // This regression test covers reading messages using offsets that 23 // are at the beginning and in the middle of compressed and uncompressed 24 // v1 message sets. 25 func TestV1BatchOffsets(t *testing.T) { 26 const highWatermark = 5000 27 const topic = "test-topic" 28 var ( 29 msg0 = Message{ 30 Offset: 0, 31 Key: []byte("msg-0"), 32 Value: []byte("key-0"), 33 } 34 msg1 = Message{ 35 Offset: 1, 36 Key: []byte("msg-1"), 37 Value: []byte("key-1"), 38 } 39 msg2 = Message{ 40 Offset: 2, 41 Key: []byte("msg-2"), 42 Value: []byte("key-2"), 43 } 44 ) 45 46 for _, tc := range []struct { 47 name string 48 builder fetchResponseBuilder 49 offset int64 50 expected []Message 51 debug bool 52 }{ 53 { 54 name: "num=1 off=0", 55 offset: 0, 56 builder: fetchResponseBuilder{ 57 header: fetchResponseHeader{ 58 highWatermarkOffset: highWatermark, 59 lastStableOffset: highWatermark, 60 topic: topic, 61 }, 62 msgSets: []messageSetBuilder{ 63 v1MessageSetBuilder{ 64 msgs: []Message{msg0}, 65 }, 66 }, 67 }, 68 expected: []Message{msg0}, 69 }, 70 { 71 name: "num=1 off=0 compressed", 72 offset: 0, 73 builder: fetchResponseBuilder{ 74 header: fetchResponseHeader{ 75 highWatermarkOffset: highWatermark, 76 lastStableOffset: highWatermark, 77 topic: topic, 78 }, 79 msgSets: []messageSetBuilder{ 80 v1MessageSetBuilder{ 81 codec: new(gzip.Codec), 82 msgs: []Message{msg0}, 83 }, 84 }, 85 }, 86 expected: []Message{msg0}, 87 }, 88 { 89 name: "num=1 off=1", 90 offset: 1, 91 builder: fetchResponseBuilder{ 92 header: fetchResponseHeader{ 93 highWatermarkOffset: highWatermark, 94 lastStableOffset: highWatermark, 95 topic: topic, 96 }, 97 msgSets: []messageSetBuilder{ 98 v1MessageSetBuilder{ 99 msgs: []Message{msg1}, 100 }, 101 }, 102 }, 103 expected: []Message{msg1}, 104 }, 105 { 106 name: "num=1 off=1 compressed", 107 offset: 1, 108 builder: fetchResponseBuilder{ 109 header: fetchResponseHeader{ 110 highWatermarkOffset: highWatermark, 111 lastStableOffset: highWatermark, 112 topic: topic, 113 }, 114 msgSets: []messageSetBuilder{ 115 v1MessageSetBuilder{ 116 codec: new(gzip.Codec), 117 msgs: []Message{msg1}, 118 }, 119 }, 120 }, 121 expected: []Message{msg1}, 122 }, 123 { 124 name: "num=3 off=0", 125 offset: 0, 126 builder: fetchResponseBuilder{ 127 header: fetchResponseHeader{ 128 highWatermarkOffset: highWatermark, 129 lastStableOffset: highWatermark, 130 topic: topic, 131 }, 132 msgSets: []messageSetBuilder{ 133 v1MessageSetBuilder{ 134 msgs: []Message{msg0, msg1, msg2}, 135 }, 136 }, 137 }, 138 expected: []Message{msg0, msg1, msg2}, 139 }, 140 { 141 name: "num=3 off=0 compressed", 142 offset: 0, 143 builder: fetchResponseBuilder{ 144 header: fetchResponseHeader{ 145 highWatermarkOffset: highWatermark, 146 lastStableOffset: highWatermark, 147 topic: topic, 148 }, 149 msgSets: []messageSetBuilder{ 150 v1MessageSetBuilder{ 151 codec: new(gzip.Codec), 152 msgs: []Message{msg0, msg1, msg2}, 153 }, 154 }, 155 }, 156 expected: []Message{msg0, msg1, msg2}, 157 }, 158 { 159 name: "num=3 off=1", 160 offset: 1, 161 debug: true, 162 builder: fetchResponseBuilder{ 163 header: fetchResponseHeader{ 164 highWatermarkOffset: highWatermark, 165 lastStableOffset: highWatermark, 166 topic: topic, 167 }, 168 msgSets: []messageSetBuilder{ 169 v1MessageSetBuilder{ 170 msgs: []Message{msg0, msg1, msg2}, 171 }, 172 }, 173 }, 174 expected: []Message{msg1, msg2}, 175 }, 176 { 177 name: "num=3 off=1 compressed", 178 offset: 1, 179 debug: true, 180 builder: fetchResponseBuilder{ 181 header: fetchResponseHeader{ 182 highWatermarkOffset: highWatermark, 183 lastStableOffset: highWatermark, 184 topic: topic, 185 }, 186 msgSets: []messageSetBuilder{ 187 v1MessageSetBuilder{ 188 codec: new(gzip.Codec), 189 msgs: []Message{msg0, msg1, msg2}, 190 }, 191 }, 192 }, 193 expected: []Message{msg1, msg2}, 194 }, 195 { 196 name: "num=3 off=2 compressed", 197 offset: 2, 198 debug: true, 199 builder: fetchResponseBuilder{ 200 header: fetchResponseHeader{ 201 highWatermarkOffset: highWatermark, 202 lastStableOffset: highWatermark, 203 topic: topic, 204 }, 205 msgSets: []messageSetBuilder{ 206 v1MessageSetBuilder{ 207 codec: new(gzip.Codec), 208 msgs: []Message{msg0, msg1, msg2}, 209 }, 210 }, 211 }, 212 expected: []Message{msg2}, 213 }, 214 } { 215 t.Run(tc.name, func(t *testing.T) { 216 bs := tc.builder.bytes() 217 r, err := newReaderHelper(t, bs) 218 require.NoError(t, err) 219 r.offset = tc.offset 220 r.debug = tc.debug 221 filter := func(msg Message) (res Message) { 222 res.Offset = msg.Offset 223 res.Key = msg.Key 224 res.Value = msg.Value 225 return res 226 } 227 for _, expected := range tc.expected { 228 msg := filter(r.readMessage()) 229 require.EqualValues(t, expected, msg) 230 } 231 // finally, verify no more bytes remain 232 require.EqualValues(t, 0, r.remain) 233 _, err = r.readMessageErr() 234 require.EqualError(t, err, errShortRead.Error()) 235 }) 236 } 237 } 238 239 func TestMessageSetReader(t *testing.T) { 240 const startOffset = 1000 241 const highWatermark = 5000 242 const topic = "test-topic" 243 msgs := make([]Message, 100) 244 for i := 0; i < 100; i++ { 245 msgs[i] = Message{ 246 Time: time.Now(), 247 Offset: int64(i + startOffset), 248 Key: []byte(fmt.Sprintf("key-%d", i)), 249 Value: []byte(fmt.Sprintf("val-%d", i)), 250 Headers: []Header{ 251 { 252 Key: fmt.Sprintf("header-key-%d", i), 253 Value: []byte(fmt.Sprintf("header-value-%d", i)), 254 }, 255 }, 256 } 257 } 258 defaultHeader := fetchResponseHeader{ 259 highWatermarkOffset: highWatermark, 260 lastStableOffset: highWatermark, 261 topic: topic, 262 } 263 for _, tc := range []struct { 264 name string 265 builder fetchResponseBuilder 266 err error 267 debug bool 268 }{ 269 { 270 name: "empty", 271 builder: fetchResponseBuilder{ 272 header: defaultHeader, 273 }, 274 err: errShortRead, 275 }, 276 { 277 name: "v0", 278 builder: fetchResponseBuilder{ 279 header: defaultHeader, 280 msgSets: []messageSetBuilder{ 281 v0MessageSetBuilder{ 282 msgs: []Message{msgs[0]}, 283 }, 284 }, 285 }, 286 }, 287 { 288 name: "v0 compressed", 289 builder: fetchResponseBuilder{ 290 header: defaultHeader, 291 msgSets: []messageSetBuilder{ 292 v0MessageSetBuilder{ 293 codec: new(gzip.Codec), 294 msgs: []Message{msgs[0]}, 295 }, 296 }, 297 }, 298 }, 299 { 300 name: "v1", 301 builder: fetchResponseBuilder{ 302 header: defaultHeader, 303 msgSets: []messageSetBuilder{ 304 v1MessageSetBuilder{ 305 msgs: []Message{msgs[0]}, 306 }, 307 }, 308 }, 309 }, 310 { 311 name: "v1 compressed", 312 builder: fetchResponseBuilder{ 313 header: defaultHeader, 314 msgSets: []messageSetBuilder{ 315 v1MessageSetBuilder{ 316 codec: new(gzip.Codec), 317 msgs: []Message{msgs[0]}, 318 }, 319 }, 320 }, 321 }, 322 { 323 name: "v2", 324 builder: fetchResponseBuilder{ 325 header: defaultHeader, 326 msgSets: []messageSetBuilder{ 327 v2MessageSetBuilder{ 328 msgs: []Message{msgs[0]}, 329 }, 330 }, 331 }, 332 }, 333 { 334 name: "v2 compressed", 335 builder: fetchResponseBuilder{ 336 header: defaultHeader, 337 msgSets: []messageSetBuilder{ 338 v2MessageSetBuilder{ 339 codec: new(zstd.Codec), 340 msgs: []Message{msgs[0]}, 341 }, 342 }, 343 }, 344 }, 345 { 346 name: "v2 multiple messages", 347 builder: fetchResponseBuilder{ 348 header: defaultHeader, 349 msgSets: []messageSetBuilder{ 350 v2MessageSetBuilder{ 351 msgs: []Message{msgs[0], msgs[1], msgs[2], msgs[3], msgs[4]}, 352 }, 353 }, 354 }, 355 }, 356 { 357 name: "v2 multiple messages compressed", 358 builder: fetchResponseBuilder{ 359 header: defaultHeader, 360 msgSets: []messageSetBuilder{ 361 v2MessageSetBuilder{ 362 codec: new(snappy.Codec), 363 msgs: []Message{msgs[0], msgs[1], msgs[2], msgs[3], msgs[4]}, 364 }, 365 }, 366 }, 367 }, 368 { 369 name: "v2 mix of compressed and uncompressed message sets", 370 builder: fetchResponseBuilder{ 371 header: defaultHeader, 372 msgSets: []messageSetBuilder{ 373 v2MessageSetBuilder{ 374 codec: new(snappy.Codec), 375 msgs: []Message{msgs[0], msgs[1], msgs[2], msgs[3], msgs[4]}, 376 }, 377 v2MessageSetBuilder{ 378 msgs: []Message{msgs[5], msgs[6], msgs[7], msgs[8], msgs[9]}, 379 }, 380 v2MessageSetBuilder{ 381 codec: new(snappy.Codec), 382 msgs: []Message{msgs[10], msgs[11], msgs[12], msgs[13], msgs[14]}, 383 }, 384 v2MessageSetBuilder{ 385 msgs: []Message{msgs[15], msgs[16], msgs[17], msgs[18], msgs[19]}, 386 }, 387 }, 388 }, 389 }, 390 { 391 name: "v0 v2 v1 v2 v1 v1 v0 v2", 392 builder: fetchResponseBuilder{ 393 header: defaultHeader, 394 msgSets: []messageSetBuilder{ 395 v0MessageSetBuilder{ 396 msgs: []Message{msgs[0]}, 397 }, 398 v2MessageSetBuilder{ 399 msgs: []Message{msgs[1], msgs[2]}, 400 }, 401 v1MessageSetBuilder{ 402 msgs: []Message{msgs[3]}, 403 }, 404 v2MessageSetBuilder{ 405 msgs: []Message{msgs[4], msgs[5]}, 406 }, 407 v1MessageSetBuilder{ 408 msgs: []Message{msgs[6]}, 409 }, 410 v1MessageSetBuilder{ 411 msgs: []Message{msgs[7]}, 412 }, 413 v0MessageSetBuilder{ 414 msgs: []Message{msgs[8]}, 415 }, 416 v2MessageSetBuilder{ 417 msgs: []Message{msgs[9], msgs[10]}, 418 }, 419 }, 420 }, 421 }, 422 { 423 name: "v0 v2 v1 v2 v1 v1 v0 v2 mixed compression", 424 builder: fetchResponseBuilder{ 425 header: defaultHeader, 426 msgSets: []messageSetBuilder{ 427 v0MessageSetBuilder{ 428 codec: new(gzip.Codec), 429 msgs: []Message{msgs[0]}, 430 }, 431 v2MessageSetBuilder{ 432 codec: new(zstd.Codec), 433 msgs: []Message{msgs[1], msgs[2]}, 434 }, 435 v1MessageSetBuilder{ 436 codec: new(snappy.Codec), 437 msgs: []Message{msgs[3]}, 438 }, 439 v2MessageSetBuilder{ 440 codec: new(lz4.Codec), 441 msgs: []Message{msgs[4], msgs[5]}, 442 }, 443 v1MessageSetBuilder{ 444 codec: new(gzip.Codec), 445 msgs: []Message{msgs[6]}, 446 }, 447 v1MessageSetBuilder{ 448 codec: new(zstd.Codec), 449 msgs: []Message{msgs[7]}, 450 }, 451 v0MessageSetBuilder{ 452 codec: new(snappy.Codec), 453 msgs: []Message{msgs[8]}, 454 }, 455 v2MessageSetBuilder{ 456 codec: new(lz4.Codec), 457 msgs: []Message{msgs[9], msgs[10]}, 458 }, 459 }, 460 }, 461 }, 462 { 463 name: "v0 v2 v1 v2 v1 v1 v0 v2 mixed compression with non-compressed", 464 builder: fetchResponseBuilder{ 465 header: defaultHeader, 466 msgSets: []messageSetBuilder{ 467 v0MessageSetBuilder{ 468 codec: new(gzip.Codec), 469 msgs: []Message{msgs[0]}, 470 }, 471 v2MessageSetBuilder{ 472 msgs: []Message{msgs[1], msgs[2]}, 473 }, 474 v1MessageSetBuilder{ 475 codec: new(snappy.Codec), 476 msgs: []Message{msgs[3]}, 477 }, 478 v2MessageSetBuilder{ 479 msgs: []Message{msgs[4], msgs[5]}, 480 }, 481 v1MessageSetBuilder{ 482 msgs: []Message{msgs[6]}, 483 }, 484 v1MessageSetBuilder{ 485 codec: new(zstd.Codec), 486 msgs: []Message{msgs[7]}, 487 }, 488 v0MessageSetBuilder{ 489 msgs: []Message{msgs[8]}, 490 }, 491 v2MessageSetBuilder{ 492 codec: new(lz4.Codec), 493 msgs: []Message{msgs[9], msgs[10]}, 494 }, 495 }, 496 }, 497 }, 498 } { 499 t.Run(tc.name, func(t *testing.T) { 500 rh, err := newReaderHelper(t, tc.builder.bytes()) 501 require.Equal(t, tc.err, err) 502 if tc.err != nil { 503 return 504 } 505 rh.debug = tc.debug 506 for _, messageSet := range tc.builder.msgSets { 507 for _, expected := range messageSet.messages() { 508 msg := rh.readMessage() 509 require.Equal(t, expected.Offset, msg.Offset) 510 require.Equal(t, string(expected.Key), string(msg.Key)) 511 require.Equal(t, string(expected.Value), string(msg.Value)) 512 switch messageSet.(type) { 513 case v0MessageSetBuilder, v1MessageSetBuilder: 514 // v0 and v1 message sets do not have headers 515 require.Len(t, msg.Headers, 0) 516 case v2MessageSetBuilder: 517 // v2 message sets can have headers 518 require.EqualValues(t, expected.Headers, msg.Headers) 519 default: 520 t.Fatalf("unknown builder: %T", messageSet) 521 } 522 require.Equal(t, expected.Offset, msg.Offset) 523 } 524 } 525 // verify the reader stack is empty 526 require.EqualValues(t, 0, rh.remain) 527 require.EqualValues(t, 0, rh.count) 528 require.EqualValues(t, 0, rh.remaining()) 529 require.Nil(t, rh.readerStack.parent) 530 // any further message is a short read 531 _, err = rh.readMessageErr() 532 require.EqualError(t, err, errShortRead.Error()) 533 }) 534 } 535 536 } 537 538 func TestMessageSetReaderEmpty(t *testing.T) { 539 m := messageSetReader{empty: true} 540 541 noop := func(*bufio.Reader, int, int) (int, error) { 542 return 0, nil 543 } 544 545 offset, _, timestamp, headers, err := m.readMessage(0, noop, noop) 546 if offset != 0 { 547 t.Errorf("expected offset of 0, get %d", offset) 548 } 549 if timestamp != 0 { 550 t.Errorf("expected timestamp of 0, get %d", timestamp) 551 } 552 if headers != nil { 553 t.Errorf("expected nil headers, got %v", headers) 554 } 555 if !errors.Is(err, RequestTimedOut) { 556 t.Errorf("expected RequestTimedOut, got %v", err) 557 } 558 559 if m.remaining() != 0 { 560 t.Errorf("expected 0 remaining, got %d", m.remaining()) 561 } 562 563 if m.discard() != nil { 564 t.Errorf("unexpected error from discard(): %v", m.discard()) 565 } 566 } 567 568 func TestMessageFixtures(t *testing.T) { 569 type fixtureMessage struct { 570 key string 571 value string 572 } 573 var fixtureMessages = map[string]fixtureMessage{ 574 "a": {key: "alpha", value: `{"count":0,"filler":"aaaaaaaaaa"}`}, 575 "b": {key: "beta", value: `{"count":0,"filler":"bbbbbbbbbb"}`}, 576 "c": {key: "gamma", value: `{"count":0,"filler":"cccccccccc"}`}, 577 "d": {key: "delta", value: `{"count":0,"filler":"dddddddddd"}`}, 578 "e": {key: "epsilon", value: `{"count":0,"filler":"eeeeeeeeee"}`}, 579 "f": {key: "zeta", value: `{"count":0,"filler":"ffffffffff"}`}, 580 "g": {key: "eta", value: `{"count":0,"filler":"gggggggggg"}`}, 581 "h": {key: "theta", value: `{"count":0,"filler":"hhhhhhhhhh"}`}, 582 } 583 584 for _, tc := range []struct { 585 name string 586 file string 587 messages []string 588 }{ 589 { 590 name: "v2 followed by v1", 591 file: "fixtures/v2b-v1.hex", 592 messages: []string{"a", "b", "a", "b"}, 593 }, 594 { 595 name: "v2 compressed followed by v1 compressed", 596 file: "fixtures/v2bc-v1c.hex", 597 messages: []string{"a", "b", "a", "b"}, 598 }, 599 { 600 name: "v2 compressed followed by v1 uncompressed", 601 file: "fixtures/v2bc-v1.hex", 602 messages: []string{"a", "b", "c", "d"}, 603 }, 604 { 605 name: "v2 compressed followed by v1 uncompressed then v1 compressed", 606 file: "fixtures/v2bc-v1-v1c.hex", 607 messages: []string{"a", "b", "c", "d", "e", "f"}, 608 }, 609 { 610 name: "v2 compressed followed by v1 uncompressed then v1 compressed", 611 file: "fixtures/v2bc-v1-v1c.hex", 612 messages: []string{"a", "b", "c", "d", "e", "f"}, 613 }, 614 { 615 name: "v1 followed by v1", 616 file: "fixtures/v1-v1.hex", 617 messages: []string{"a", "b", "c", "d"}, 618 }, 619 { 620 name: "v1 compressed followed by v1 compressed", 621 file: "fixtures/v1c-v1c.hex", 622 messages: []string{"a", "b", "c", "d"}, 623 }, 624 { 625 name: "v1 compressed followed by v1 uncompressed then v1 compressed", 626 file: "fixtures/v1c-v1-v1c.hex", 627 messages: []string{"a", "b", "c", "d", "e", "f"}, 628 }, 629 { 630 name: "v2 followed by v2", 631 file: "fixtures/v2-v2.hex", 632 messages: []string{"a", "b", "c", "d"}, 633 }, 634 { 635 name: "v2 compressed followed by v2 compressed", 636 file: "fixtures/v2c-v2c.hex", 637 messages: []string{"a", "b", "c", "d"}, 638 }, 639 { 640 name: "v2 compressed followed by v2 uncompressed then v2 compressed", 641 file: "fixtures/v2c-v2-v2c.hex", 642 messages: []string{"a", "b", "c", "d", "e", "f"}, 643 }, 644 { 645 name: "v1 followed by v2 followed by v1 with mixture of compressed and uncompressed", 646 file: "fixtures/v1-v1c-v2-v2c-v2b-v2b-v2b-v2bc-v1b-v1bc.hex", 647 messages: []string{"a", "b", "a", "b", "c", "d", "c", "d", "e", "f", "e", "f", "g", "h", "g", "h", "g", "h", "g", "h"}, 648 }, 649 } { 650 t.Run(tc.name, func(t *testing.T) { 651 bs, err := os.ReadFile(tc.file) 652 require.NoError(t, err) 653 buf := new(bytes.Buffer) 654 _, err = io.Copy(buf, hex.NewDecoder(bytes.NewReader(bs))) 655 require.NoError(t, err) 656 657 // discard 4 byte len and 4 byte correlation id 658 bs = make([]byte, 8) 659 buf.Read(bs) 660 661 rh, err := newReaderHelper(t, buf.Bytes()) 662 require.NoError(t, err) 663 messageCount := 0 664 expectedMessageCount := len(tc.messages) 665 for _, expectedMessageId := range tc.messages { 666 expectedMessage := fixtureMessages[expectedMessageId] 667 msg := rh.readMessage() 668 messageCount++ 669 require.Equal(t, expectedMessage.key, string(msg.Key)) 670 require.Equal(t, expectedMessage.value, string(msg.Value)) 671 t.Logf("Message %d key & value are what we expected: %s -> %s\n", 672 messageCount, string(msg.Key), string(msg.Value)) 673 } 674 require.Equal(t, expectedMessageCount, messageCount) 675 }) 676 } 677 } 678 679 func TestMessageSize(t *testing.T) { 680 rand.Seed(time.Now().UnixNano()) 681 for i := 0; i < 20; i++ { 682 t.Run("Run", func(t *testing.T) { 683 msg := Message{ 684 Key: make([]byte, rand.Intn(200)), 685 Value: make([]byte, rand.Intn(200)), 686 Time: randate(), 687 } 688 expSize := msg.message(nil).size() 689 gotSize := msg.size() 690 if expSize != gotSize { 691 t.Errorf("Expected size %d, but got size %d", expSize, gotSize) 692 } 693 }) 694 } 695 696 } 697 698 // https://stackoverflow.com/questions/43495745/how-to-generate-random-date-in-go-lang/43497333#43497333 699 func randate() time.Time { 700 min := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC).Unix() 701 max := time.Date(2070, 1, 0, 0, 0, 0, 0, time.UTC).Unix() 702 delta := max - min 703 704 sec := rand.Int63n(delta) + min 705 return time.Unix(sec, 0) 706 } 707 708 // readerHelper composes a messageSetReader to provide convenience methods to read 709 // messages. 710 type readerHelper struct { 711 t *testing.T 712 *messageSetReader 713 offset int64 714 } 715 716 func newReaderHelper(t *testing.T, bs []byte) (r *readerHelper, err error) { 717 bufReader := bufio.NewReader(bytes.NewReader(bs)) 718 _, _, remain, err := readFetchResponseHeaderV10(bufReader, len(bs)) 719 require.NoError(t, err) 720 var msgs *messageSetReader 721 msgs, err = newMessageSetReader(bufReader, remain) 722 if err != nil { 723 return 724 } 725 r = &readerHelper{t: t, messageSetReader: msgs} 726 require.Truef(t, msgs.remaining() > 0, "remaining should be > 0 but was %d", msgs.remaining()) 727 return 728 } 729 730 func (r *readerHelper) readMessageErr() (msg Message, err error) { 731 keyFunc := func(r *bufio.Reader, size int, nbytes int) (remain int, err error) { 732 msg.Key, remain, err = readNewBytes(r, size, nbytes) 733 return 734 } 735 valueFunc := func(r *bufio.Reader, size int, nbytes int) (remain int, err error) { 736 msg.Value, remain, err = readNewBytes(r, size, nbytes) 737 return 738 } 739 var timestamp int64 740 var headers []Header 741 r.offset, _, timestamp, headers, err = r.messageSetReader.readMessage(r.offset, keyFunc, valueFunc) 742 if err != nil { 743 return 744 } 745 msg.Offset = r.offset 746 msg.Time = time.Unix(timestamp/1000, (timestamp%1000)*1000000) 747 msg.Headers = headers 748 return 749 } 750 751 func (r *readerHelper) readMessage() (msg Message) { 752 var err error 753 msg, err = r.readMessageErr() 754 require.NoError(r.t, err) 755 return 756 }