/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "src/trace_processor/util/zip_reader.h" #include #include #include #include #include #include "perfetto/base/build_config.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/string_view.h" #include "perfetto/trace_processor/trace_blob.h" #include "perfetto/trace_processor/trace_blob_view.h" #include "src/base/test/status_matchers.h" #include "test/gtest_and_gmock.h" namespace perfetto::trace_processor::util { namespace { using base::gtest_matchers::IsError; // This zip file contains the following: // Zip file size: 386 bytes, number of entries: 2 // -rw-r--r-- 3.0 unx 4 tx stor 22-Jul-25 16:43 stored_file // -rw-r--r-- 3.0 unx 89 tx defN 22-Jul-25 18:34 dir/deflated_file // 2 files, 92 bytes uncompressed, 52 bytes compressed: 43.5% // // /stored_file content: "foo" // dir/deflated_file content: 2x "The quick brown fox jumps over the lazy dog\n" const uint8_t kTestZip[] = { 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x85, 0xf9, 0x54, 0xa8, 0x65, 0x32, 0x7e, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x1c, 0x00, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x54, 0x09, 0x00, 0x03, 0x17, 0xba, 0xde, 0x62, 0x44, 0xba, 0xde, 0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xce, 0x69, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x66, 0x6f, 0x6f, 0x0a, 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x47, 0x94, 0xf9, 0x54, 0xf2, 0x03, 0x92, 0x3c, 0x34, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x11, 0x00, 0x1c, 0x00, 0x64, 0x69, 0x72, 0x2f, 0x64, 0x65, 0x66, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x54, 0x09, 0x00, 0x03, 0x15, 0xd4, 0xde, 0x62, 0xf4, 0xba, 0xde, 0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xce, 0x69, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xc9, 0x48, 0x55, 0x28, 0x2c, 0xcd, 0x4c, 0xce, 0x56, 0x48, 0x2a, 0xca, 0x2f, 0xcf, 0x53, 0x48, 0xcb, 0xaf, 0x50, 0xc8, 0x2a, 0xcd, 0x2d, 0x28, 0x56, 0xc8, 0x2f, 0x4b, 0x2d, 0x52, 0x28, 0x01, 0x4a, 0xe7, 0x24, 0x56, 0x55, 0x2a, 0xa4, 0xe4, 0xa7, 0x73, 0x85, 0x10, 0xa9, 0x36, 0xad, 0x08, 0xa8, 0x18, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x85, 0xf9, 0x54, 0xa8, 0x65, 0x32, 0x7e, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x54, 0x05, 0x00, 0x03, 0x17, 0xba, 0xde, 0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xce, 0x69, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x47, 0x94, 0xf9, 0x54, 0xf2, 0x03, 0x92, 0x3c, 0x34, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x11, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x49, 0x00, 0x00, 0x00, 0x64, 0x69, 0x72, 0x2f, 0x64, 0x65, 0x66, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x54, 0x05, 0x00, 0x03, 0x15, 0xd4, 0xde, 0x62, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xce, 0x69, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00}; std::string vec2str(const std::vector& vec) { return {reinterpret_cast(vec.data()), vec.size()}; } void ValidateTestZip(ZipReader& zr) { ASSERT_EQ(zr.files().size(), 2u); std::vector dec; ASSERT_EQ(zr.files()[0].name(), "stored_file"); ASSERT_EQ(zr.files()[0].GetDatetimeStr(), "2022-07-25 16:43:20"); ASSERT_EQ(zr.files()[1].name(), "dir/deflated_file"); ASSERT_EQ(zr.files()[1].GetDatetimeStr(), "2022-07-25 18:34:14"); // This file is STORE-d and doesn't require any decompression. auto res = zr.files()[0].Decompress(&dec); ASSERT_TRUE(res.ok()) << res.message(); ASSERT_EQ(dec.size(), 4u); ASSERT_EQ(vec2str(dec), "foo\n"); // This file is DEFLATE-d and requires zlib. #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) res = zr.files()[1].Decompress(&dec); ASSERT_TRUE(res.ok()) << res.message(); ASSERT_EQ(dec.size(), 89u); ASSERT_EQ(vec2str(dec), "The quick brown fox jumps over the lazy dog\n" "The quick brown fox jumps over the lazy frog\n"); #endif } TEST(ZipReaderTest, ValidZip_OneShotParse) { ZipReader zr; ASSERT_OK( zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip))))); ValidateTestZip(zr); } TEST(ZipReaderTest, ValidZip_OneByteChunks) { ZipReader zr; for (auto i : kTestZip) { ASSERT_OK(zr.Parse(TraceBlobView(TraceBlob::CopyFrom(&i, 1)))); } ValidateTestZip(zr); } TEST(ZipReaderTest, MalformedZip_InvalidSignature) { ZipReader zr; uint8_t content[sizeof(kTestZip)]; memcpy(content, kTestZip, sizeof(kTestZip)); content[0] = 0xff; // Invalid signature ASSERT_THAT( zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip)))), IsError()); ASSERT_EQ(zr.files().size(), 0u); } TEST(ZipReaderTest, MalformedZip_VersionTooHigh) { ZipReader zr; uint8_t content[sizeof(kTestZip)]; memcpy(content, kTestZip, sizeof(kTestZip)); content[5] = 9; // Version: 9.0 ASSERT_THAT( zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip)))), IsError()); ASSERT_EQ(zr.files().size(), 0u); } TEST(ZipReaderTest, TruncatedZip) { ZipReader zr; ASSERT_OK(zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, 40)))); ASSERT_EQ(zr.files().size(), 0u); } TEST(ZipReaderTest, Find) { ZipReader zr; ASSERT_OK( zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip))))); ASSERT_EQ(zr.Find("stored_file")->name(), "stored_file"); ASSERT_EQ(zr.Find("dir/deflated_file")->name(), "dir/deflated_file"); ASSERT_EQ(nullptr, zr.Find("stored_f")); ASSERT_EQ(nullptr, zr.Find("_file*")); ASSERT_EQ(nullptr, zr.Find("dirz/deflated_file")); } // All the tests below require zlib. #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) TEST(ZipReaderTest, ValidZip_DecompressLines) { ZipReader zr; ASSERT_OK( zr.Parse(TraceBlobView(TraceBlob::CopyFrom(kTestZip, sizeof(kTestZip))))); ValidateTestZip(zr); int num_callbacks = 0; zr.files()[1].DecompressLines( [&](const std::vector& lines) { ASSERT_EQ(num_callbacks++, 0); ASSERT_TRUE(lines.size() == 2); ASSERT_EQ(lines[0].ToStdString(), "The quick brown fox jumps over the lazy dog"); ASSERT_EQ(lines[1].ToStdString(), "The quick brown fox jumps over the lazy frog"); }); ASSERT_EQ(num_callbacks, 1); } TEST(ZipReaderTest, MalformedZip_DecomprError) { ZipReader zr; uint8_t content[sizeof(kTestZip)]; memcpy(content, kTestZip, sizeof(kTestZip)); // The 2nd file header starts at 103, the payload at 30 (header) + 17 (fname) // bytes later. We start clobbering at offset=150, so the header is intanct // but decompression fails. memset(&content[150], 0, 40); ASSERT_OK( zr.Parse(TraceBlobView(TraceBlob::CopyFrom(content, sizeof(kTestZip))))); ASSERT_EQ(zr.files().size(), 2u); std::vector ignored; ASSERT_OK(zr.files()[0].Decompress(&ignored)); ASSERT_THAT(zr.files()[1].Decompress(&ignored), IsError()); } #endif // PERFETTO_BUILDFLAG(PERFETTO_ZLIB) } // namespace } // namespace perfetto::trace_processor::util