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