@@ -647,3 +647,300 @@ func TestNewGRPCStorageClient_NoGlobalTimeout(t *testing.T) {
647647 verifyTimeoutIsZero ("BidiWriteObject" , opts .BidiWriteObject )
648648 verifyTimeoutIsZero ("CancelResumableWrite" , opts .CancelResumableWrite )
649649}
650+
651+ func TestVerifyChecksums (t * testing.T ) {
652+ helloWorldCRC := crc32 .Checksum ([]byte ("hello world" ), crc32cTable )
653+
654+ tests := []struct {
655+ desc string
656+ msg * storagepb.BidiReadObjectResponse
657+ databufs mem.BufferSlice
658+ dataOffsets map [int64 ]bufferSliceOffsets
659+ initialErrs map [int64 ]error
660+ wantErrs map [int64 ]string // readID -> substring of error message, or empty if no error expected
661+ }{
662+ {
663+ desc : "nil msg returns early and does not initialize crcErrs" ,
664+ msg : nil ,
665+ databufs : mem.BufferSlice {
666+ mem .SliceBuffer ([]byte ("hello world" )),
667+ },
668+ wantErrs : nil , // stays nil
669+ },
670+ {
671+ desc : "empty databufs with checksum return error" ,
672+ msg : & storagepb.BidiReadObjectResponse {
673+ ObjectDataRanges : []* storagepb.ObjectRangeData {
674+ {
675+ ChecksummedData : & storagepb.ChecksummedData {
676+ Crc32C : & helloWorldCRC ,
677+ },
678+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
679+ },
680+ },
681+ },
682+ databufs : mem.BufferSlice {},
683+ wantErrs : map [int64 ]string {
684+ 1 : "storage: bad CRC on chunk read: got" ,
685+ },
686+ },
687+ {
688+ desc : "missing checksummed data is skipped" ,
689+ msg : & storagepb.BidiReadObjectResponse {
690+ ObjectDataRanges : []* storagepb.ObjectRangeData {
691+ {
692+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
693+ },
694+ },
695+ },
696+ databufs : mem.BufferSlice {
697+ mem .SliceBuffer ([]byte ("hello world" )),
698+ },
699+ dataOffsets : map [int64 ]bufferSliceOffsets {
700+ 1 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
701+ },
702+ wantErrs : map [int64 ]string {}, // initialized but empty
703+ },
704+ {
705+ desc : "missing Crc32C pointer is skipped" ,
706+ msg : & storagepb.BidiReadObjectResponse {
707+ ObjectDataRanges : []* storagepb.ObjectRangeData {
708+ {
709+ ChecksummedData : & storagepb.ChecksummedData {},
710+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
711+ },
712+ },
713+ },
714+ databufs : mem.BufferSlice {
715+ mem .SliceBuffer ([]byte ("hello world" )),
716+ },
717+ dataOffsets : map [int64 ]bufferSliceOffsets {
718+ 1 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
719+ },
720+ wantErrs : map [int64 ]string {},
721+ },
722+ {
723+ desc : "single buffer correct checksum" ,
724+ msg : & storagepb.BidiReadObjectResponse {
725+ ObjectDataRanges : []* storagepb.ObjectRangeData {
726+ {
727+ ChecksummedData : & storagepb.ChecksummedData {
728+ Crc32C : & helloWorldCRC ,
729+ },
730+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
731+ },
732+ },
733+ },
734+ databufs : mem.BufferSlice {
735+ mem .SliceBuffer ([]byte ("hello world" )),
736+ },
737+ dataOffsets : map [int64 ]bufferSliceOffsets {
738+ 1 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
739+ },
740+ wantErrs : map [int64 ]string {},
741+ },
742+ {
743+ desc : "single buffer incorrect checksum" ,
744+ msg : & storagepb.BidiReadObjectResponse {
745+ ObjectDataRanges : []* storagepb.ObjectRangeData {
746+ {
747+ ChecksummedData : & storagepb.ChecksummedData {
748+ Crc32C : proto .Uint32 (12345 ),
749+ },
750+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
751+ },
752+ },
753+ },
754+ databufs : mem.BufferSlice {
755+ mem .SliceBuffer ([]byte ("hello world" )),
756+ },
757+ dataOffsets : map [int64 ]bufferSliceOffsets {
758+ 1 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
759+ },
760+ wantErrs : map [int64 ]string {
761+ 1 : "storage: bad CRC on chunk read: got" ,
762+ },
763+ },
764+ {
765+ desc : "multiple buffers correct checksum" ,
766+ msg : & storagepb.BidiReadObjectResponse {
767+ ObjectDataRanges : []* storagepb.ObjectRangeData {
768+ {
769+ ChecksummedData : & storagepb.ChecksummedData {
770+ Crc32C : & helloWorldCRC ,
771+ },
772+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
773+ },
774+ },
775+ },
776+ databufs : mem.BufferSlice {
777+ mem .SliceBuffer ([]byte ("prefix hello" )),
778+ mem .SliceBuffer ([]byte (" world diff message" )),
779+ mem .SliceBuffer ([]byte ("suffix" )),
780+ },
781+ dataOffsets : map [int64 ]bufferSliceOffsets {
782+ 1 : {startBuf : 0 , endBuf : 1 , startOff : 7 , endOff : 6 },
783+ },
784+ wantErrs : map [int64 ]string {},
785+ },
786+ {
787+ desc : "multiple buffers incorrect checksum" ,
788+ msg : & storagepb.BidiReadObjectResponse {
789+ ObjectDataRanges : []* storagepb.ObjectRangeData {
790+ {
791+ ChecksummedData : & storagepb.ChecksummedData {
792+ Crc32C : proto .Uint32 (12345 ),
793+ },
794+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
795+ },
796+ },
797+ },
798+ databufs : mem.BufferSlice {
799+ mem .SliceBuffer ([]byte ("prefix_hello" )),
800+ mem .SliceBuffer ([]byte ("_world_" )),
801+ mem .SliceBuffer ([]byte ("suffix" )),
802+ },
803+ dataOffsets : map [int64 ]bufferSliceOffsets {
804+ 1 : {startBuf : 0 , endBuf : 1 , startOff : 7 , endOff : 6 },
805+ },
806+ wantErrs : map [int64 ]string {
807+ 1 : "storage: bad CRC on chunk read: got" ,
808+ },
809+ },
810+ {
811+ desc : "startOff >= endOff empty segment" ,
812+ msg : & storagepb.BidiReadObjectResponse {
813+ ObjectDataRanges : []* storagepb.ObjectRangeData {
814+ {
815+ ChecksummedData : & storagepb.ChecksummedData {
816+ Crc32C : proto .Uint32 (0 ),
817+ },
818+ ReadRange : & storagepb.ReadRange {ReadId : 1 },
819+ },
820+ },
821+ },
822+ databufs : mem.BufferSlice {
823+ mem .SliceBuffer ([]byte ("hello world" )),
824+ },
825+ dataOffsets : map [int64 ]bufferSliceOffsets {
826+ 1 : {startBuf : 0 , endBuf : 0 , startOff : 5 , endOff : 5 },
827+ },
828+ wantErrs : map [int64 ]string {},
829+ },
830+ {
831+ desc : "multiple ranges mix of results" ,
832+ msg : & storagepb.BidiReadObjectResponse {
833+ ObjectDataRanges : []* storagepb.ObjectRangeData {
834+ {
835+ ChecksummedData : & storagepb.ChecksummedData {
836+ Crc32C : & helloWorldCRC ,
837+ },
838+ ReadRange : & storagepb.ReadRange {ReadId : 10 },
839+ },
840+ {
841+ ChecksummedData : & storagepb.ChecksummedData {
842+ Crc32C : proto .Uint32 (9999 ),
843+ },
844+ ReadRange : & storagepb.ReadRange {ReadId : 20 },
845+ },
846+ {
847+ ReadRange : & storagepb.ReadRange {ReadId : 30 },
848+ },
849+ },
850+ },
851+ databufs : mem.BufferSlice {
852+ mem .SliceBuffer ([]byte ("hello world" )),
853+ },
854+ dataOffsets : map [int64 ]bufferSliceOffsets {
855+ 10 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
856+ 20 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
857+ 30 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
858+ },
859+ wantErrs : map [int64 ]string {
860+ 20 : "storage: bad CRC on chunk read: got" ,
861+ },
862+ },
863+ {
864+ desc : "pre-existing errors are preserved" ,
865+ msg : & storagepb.BidiReadObjectResponse {
866+ ObjectDataRanges : []* storagepb.ObjectRangeData {
867+ {
868+ ChecksummedData : & storagepb.ChecksummedData {
869+ Crc32C : & helloWorldCRC ,
870+ },
871+ ReadRange : & storagepb.ReadRange {ReadId : 10 },
872+ },
873+ },
874+ },
875+ databufs : mem.BufferSlice {
876+ mem .SliceBuffer ([]byte ("hello world" )),
877+ },
878+ dataOffsets : map [int64 ]bufferSliceOffsets {
879+ 10 : {startBuf : 0 , endBuf : 0 , startOff : 0 , endOff : 11 },
880+ },
881+ initialErrs : map [int64 ]error {
882+ 99 : fmt .Errorf ("some other error" ),
883+ },
884+ wantErrs : map [int64 ]string {
885+ 99 : "some other error" ,
886+ 10 : "" ,
887+ },
888+ },
889+ }
890+
891+ for _ , tc := range tests {
892+ t .Run (tc .desc , func (t * testing.T ) {
893+ decoder := & readResponseDecoder {
894+ msg : tc .msg ,
895+ databufs : tc .databufs ,
896+ dataOffsets : tc .dataOffsets ,
897+ crcErrs : tc .initialErrs ,
898+ }
899+
900+ decoder .verifyChecksums ()
901+
902+ if tc .wantErrs == nil {
903+ if decoder .crcErrs != nil {
904+ t .Errorf ("expected crcErrs to be nil, got: %v" , decoder .crcErrs )
905+ }
906+ return
907+ }
908+
909+ // Count expected errors (we don't expect errors for empty strings in wantErrs, i.e. 10 is correct)
910+ var expectedCount int
911+ for _ , wantSubstr := range tc .wantErrs {
912+ if wantSubstr != "" {
913+ expectedCount ++
914+ }
915+ }
916+
917+ var gotCount int
918+ for _ , err := range decoder .crcErrs {
919+ if err != nil {
920+ gotCount ++
921+ }
922+ }
923+
924+ if gotCount != expectedCount {
925+ t .Errorf ("mismatched error count: got %v, want %v" , decoder .crcErrs , tc .wantErrs )
926+ }
927+
928+ for readID , wantSubstr := range tc .wantErrs {
929+ err , ok := decoder .crcErrs [readID ]
930+ if wantSubstr == "" {
931+ if ok && err != nil {
932+ t .Errorf ("unexpected error for read ID %d: %v" , readID , err )
933+ }
934+ continue
935+ }
936+ if ! ok {
937+ t .Errorf ("expected error for read ID %d, but none was found" , readID )
938+ continue
939+ }
940+ if ! strings .Contains (err .Error (), wantSubstr ) {
941+ t .Errorf ("read ID %d error message: got %q, want it to contain %q" , readID , err .Error (), wantSubstr )
942+ }
943+ }
944+ })
945+ }
946+ }
0 commit comments