@@ -573,6 +573,27 @@ where
573
573
use flate2:: read:: GzDecoder ;
574
574
Self :: read_records ( & mut GzDecoder :: new ( reader) , is_control, n_records) ?
575
575
}
576
+ #[ cfg( feature = "compression-lz4" ) ]
577
+ RecordBatchCompression :: Lz4 => {
578
+ use lz4:: Decoder ;
579
+ let mut decoder = Decoder :: new ( reader) ?;
580
+ let records = Self :: read_records ( & mut decoder, is_control, n_records) ?;
581
+
582
+ // the lz4 decoder requires us to consume the whole inner stream until we reach EOF
583
+ let mut buf = [ 0u8 ; 1 ] ;
584
+ match decoder. read ( & mut buf) {
585
+ Ok ( _) => { }
586
+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof => { }
587
+ Err ( e) => {
588
+ return Err ( ReadError :: IO ( e) ) ;
589
+ }
590
+ }
591
+
592
+ let ( _reader, res) = decoder. finish ( ) ;
593
+ res?;
594
+
595
+ records
596
+ }
576
597
#[ allow( unreachable_patterns) ]
577
598
_ => {
578
599
return Err ( ReadError :: Malformed (
@@ -709,10 +730,22 @@ where
709
730
#[ cfg( feature = "compression-gzip" ) ]
710
731
RecordBatchCompression :: Gzip => {
711
732
use flate2:: { write:: GzEncoder , Compression } ;
712
- Self :: write_records (
713
- & mut GzEncoder :: new ( writer, Compression :: default ( ) ) ,
714
- self . records ,
715
- ) ?;
733
+ let mut encoder = GzEncoder :: new ( writer, Compression :: default ( ) ) ;
734
+ Self :: write_records ( & mut encoder, self . records ) ?;
735
+ encoder. finish ( ) ?;
736
+ }
737
+ #[ cfg( feature = "compression-lz4" ) ]
738
+ RecordBatchCompression :: Lz4 => {
739
+ use lz4:: { liblz4:: BlockMode , EncoderBuilder } ;
740
+ let mut encoder = EncoderBuilder :: new ( )
741
+ . block_mode (
742
+ // the only one supported by Kafka
743
+ BlockMode :: Independent ,
744
+ )
745
+ . build ( writer) ?;
746
+ Self :: write_records ( & mut encoder, self . records ) ?;
747
+ let ( _writer, res) = encoder. finish ( ) ;
748
+ res?;
716
749
}
717
750
#[ allow( unreachable_patterns) ]
718
751
_ => {
@@ -869,4 +902,54 @@ mod tests {
869
902
let actual2 = RecordBatch :: read ( & mut Cursor :: new ( data2) ) . unwrap ( ) ;
870
903
assert_eq ! ( actual2, expected) ;
871
904
}
905
+
906
+ #[ cfg( feature = "compression-lz4" ) ]
907
+ #[ test]
908
+ fn test_decode_fixture_lz4 ( ) {
909
+ // This data was obtained by watching rdkafka.
910
+ let data = [
911
+ b"\x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x63 \x00 \x00 \x00 \x00 " . to_vec ( ) ,
912
+ b"\x02 \x1b \xa5 \x92 \x35 \x00 \x03 \x00 \x00 \x00 \x00 \x00 \x00 \x01 \x7e \xb1 " . to_vec ( ) ,
913
+ b"\x1f \xc7 \x24 \x00 \x00 \x01 \x7e \xb1 \x1f \xc7 \x24 \xff \xff \xff \xff \xff " . to_vec ( ) ,
914
+ b"\xff \xff \xff \xff \xff \xff \xff \xff \xff \x00 \x00 \x00 \x01 \x04 \x22 \x4d " . to_vec ( ) ,
915
+ b"\x18 \x60 \x40 \x82 \x23 \x00 \x00 \x00 \x8f \xfc \x01 \x00 \x00 \x00 \xc8 \x01 " . to_vec ( ) ,
916
+ b"\x78 \x01 \x00 \x50 \xf0 \x06 \x16 \x68 \x65 \x6c \x6c \x6f \x20 \x6b \x61 \x66 " . to_vec ( ) ,
917
+ b"\x6b \x61 \x02 \x06 \x66 \x6f \x6f \x06 \x62 \x61 \x72 \x00 \x00 \x00 \x00 " . to_vec ( ) ,
918
+ ]
919
+ . concat ( ) ;
920
+
921
+ let actual = RecordBatch :: read ( & mut Cursor :: new ( data) ) . unwrap ( ) ;
922
+ let expected = RecordBatch {
923
+ base_offset : 0 ,
924
+ partition_leader_epoch : 0 ,
925
+ last_offset_delta : 0 ,
926
+ first_timestamp : 1643649156900 ,
927
+ max_timestamp : 1643649156900 ,
928
+ producer_id : -1 ,
929
+ producer_epoch : -1 ,
930
+ base_sequence : -1 ,
931
+ records : ControlBatchOrRecords :: Records ( vec ! [ Record {
932
+ timestamp_delta: 0 ,
933
+ offset_delta: 0 ,
934
+ key: vec![ b'x' ; 100 ] ,
935
+ value: b"hello kafka" . to_vec( ) ,
936
+ headers: vec![ RecordHeader {
937
+ key: "foo" . to_owned( ) ,
938
+ value: b"bar" . to_vec( ) ,
939
+ } ] ,
940
+ } ] ) ,
941
+ compression : RecordBatchCompression :: Lz4 ,
942
+ is_transactional : false ,
943
+ timestamp_type : RecordBatchTimestampType :: CreateTime ,
944
+ } ;
945
+ assert_eq ! ( actual, expected) ;
946
+
947
+ let mut data2 = vec ! [ ] ;
948
+ actual. write ( & mut data2) . unwrap ( ) ;
949
+
950
+ // don't compare if the data is equal because compression encoder might work slightly differently, use another
951
+ // roundtrip instead
952
+ let actual2 = RecordBatch :: read ( & mut Cursor :: new ( data2) ) . unwrap ( ) ;
953
+ assert_eq ! ( actual2, expected) ;
954
+ }
872
955
}
0 commit comments