/* * Copyright (C) 2019 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 "deploy_patch_generator.h" #include #include #include #include #include #include #include #include #include #include #include "adb_unique_fd.h" #include "adb_utils.h" #include "android-base/file.h" #include "patch_utils.h" #include "sysdeps.h" using namespace com::android::fastdeploy; void DeployPatchGenerator::Log(const char* fmt, ...) { va_list ap; va_start(ap, fmt); vprintf(fmt, ap); printf("\n"); va_end(ap); } static std::string HexEncode(const void* in_buffer, unsigned int size) { static const char kHexChars[] = "0123456789ABCDEF"; // Each input byte creates two output hex characters. std::string out_buffer(size * 2, '\0'); for (unsigned int i = 0; i < size; ++i) { char byte = ((const uint8_t*)in_buffer)[i]; out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf]; out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf]; } return out_buffer; } void DeployPatchGenerator::APKEntryToLog(const APKEntry& entry) { if (!is_verbose_) { return; } Log("MD5: %s", HexEncode(entry.md5().data(), entry.md5().size()).c_str()); Log("Data Offset: %" PRId64, entry.dataoffset()); Log("Data Size: %" PRId64, entry.datasize()); } void DeployPatchGenerator::APKMetaDataToLog(const APKMetaData& metadata) { if (!is_verbose_) { return; } Log("APK Metadata: %s", metadata.absolute_path().c_str()); for (int i = 0; i < metadata.entries_size(); i++) { const APKEntry& entry = metadata.entries(i); APKEntryToLog(entry); } } void DeployPatchGenerator::ReportSavings(const std::vector& identicalEntries, uint64_t totalSize) { uint64_t totalEqualBytes = 0; uint64_t totalEqualFiles = 0; for (size_t i = 0; i < identicalEntries.size(); i++) { if (identicalEntries[i].deviceEntry != nullptr) { totalEqualBytes += identicalEntries[i].localEntry->datasize(); totalEqualFiles++; } } double savingPercent = (totalEqualBytes * 100.0f) / totalSize; fprintf(stderr, "Detected %" PRIu64 " equal APK entries\n", totalEqualFiles); fprintf(stderr, "%" PRIu64 " bytes are equal out of %" PRIu64 " (%.2f%%)\n", totalEqualBytes, totalSize, savingPercent); } struct PatchEntry { int64_t deltaFromDeviceDataStart = 0; int64_t deviceDataOffset = 0; int64_t deviceDataLength = 0; }; static void WritePatchEntry(const PatchEntry& patchEntry, borrowed_fd input, borrowed_fd output, size_t* realSizeOut) { if (!(patchEntry.deltaFromDeviceDataStart | patchEntry.deviceDataOffset | patchEntry.deviceDataLength)) { return; } PatchUtils::WriteLong(patchEntry.deltaFromDeviceDataStart, output); if (patchEntry.deltaFromDeviceDataStart > 0) { PatchUtils::Pipe(input, output, patchEntry.deltaFromDeviceDataStart); } auto hostDataLength = patchEntry.deviceDataLength; adb_lseek(input, hostDataLength, SEEK_CUR); PatchUtils::WriteLong(patchEntry.deviceDataOffset, output); PatchUtils::WriteLong(patchEntry.deviceDataLength, output); *realSizeOut += patchEntry.deltaFromDeviceDataStart + hostDataLength; } void DeployPatchGenerator::GeneratePatch(const std::vector& entriesToUseOnDevice, const std::string& localApkPath, const std::string& deviceApkPath, borrowed_fd output) { unique_fd input(adb_open(localApkPath.c_str(), O_RDONLY | O_CLOEXEC)); size_t newApkSize = adb_lseek(input, 0L, SEEK_END); adb_lseek(input, 0L, SEEK_SET); // Header. PatchUtils::WriteSignature(output); PatchUtils::WriteLong(newApkSize, output); PatchUtils::WriteString(deviceApkPath, output); size_t currentSizeOut = 0; size_t realSizeOut = 0; // Write data from the host upto the first entry we have that matches a device entry. Then write // the metadata about the device entry and repeat for all entries that match on device. Finally // write out any data left. If the device and host APKs are exactly the same this ends up // writing out zip metadata from the local APK followed by offsets to the data to use from the // device APK. PatchEntry patchEntry; for (size_t i = 0, size = entriesToUseOnDevice.size(); i < size; ++i) { auto&& entry = entriesToUseOnDevice[i]; int64_t hostDataOffset = entry.localEntry->dataoffset(); int64_t hostDataLength = entry.localEntry->datasize(); int64_t deviceDataOffset = entry.deviceEntry->dataoffset(); // Both entries are the same, using host data length. int64_t deviceDataLength = hostDataLength; int64_t deltaFromDeviceDataStart = hostDataOffset - currentSizeOut; if (deltaFromDeviceDataStart > 0) { WritePatchEntry(patchEntry, input, output, &realSizeOut); patchEntry.deltaFromDeviceDataStart = deltaFromDeviceDataStart; patchEntry.deviceDataOffset = deviceDataOffset; patchEntry.deviceDataLength = deviceDataLength; } else { patchEntry.deviceDataLength += deviceDataLength; } currentSizeOut += deltaFromDeviceDataStart + hostDataLength; } WritePatchEntry(patchEntry, input, output, &realSizeOut); if (realSizeOut != currentSizeOut) { fprintf(stderr, "Size mismatch current %lld vs real %lld\n", static_cast(currentSizeOut), static_cast(realSizeOut)); error_exit("Aborting"); } if (newApkSize > currentSizeOut) { PatchUtils::WriteLong(newApkSize - currentSizeOut, output); PatchUtils::Pipe(input, output, newApkSize - currentSizeOut); PatchUtils::WriteLong(0, output); PatchUtils::WriteLong(0, output); } } bool DeployPatchGenerator::CreatePatch(const char* localApkPath, APKMetaData deviceApkMetadata, android::base::borrowed_fd output) { return CreatePatch(PatchUtils::GetHostAPKMetaData(localApkPath), std::move(deviceApkMetadata), output); } bool DeployPatchGenerator::CreatePatch(APKMetaData localApkMetadata, APKMetaData deviceApkMetadata, borrowed_fd output) { // Log metadata info. APKMetaDataToLog(deviceApkMetadata); APKMetaDataToLog(localApkMetadata); const std::string localApkPath = localApkMetadata.absolute_path(); const std::string deviceApkPath = deviceApkMetadata.absolute_path(); std::vector identicalEntries; uint64_t totalSize = BuildIdenticalEntries(identicalEntries, localApkMetadata, deviceApkMetadata); ReportSavings(identicalEntries, totalSize); GeneratePatch(identicalEntries, localApkPath, deviceApkPath, output); return true; } uint64_t DeployPatchGenerator::BuildIdenticalEntries(std::vector& outIdenticalEntries, const APKMetaData& localApkMetadata, const APKMetaData& deviceApkMetadata) { outIdenticalEntries.reserve( std::min(localApkMetadata.entries_size(), deviceApkMetadata.entries_size())); using md5Digest = std::pair; struct md5Hash { size_t operator()(const md5Digest& digest) const { std::hash hasher; size_t seed = 0; seed ^= hasher(digest.first) + 0x9e3779b9 + (seed << 6) + (seed >> 2); seed ^= hasher(digest.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); return seed; } }; static_assert(sizeof(md5Digest) == MD5_DIGEST_LENGTH); std::unordered_map, md5Hash> deviceEntries; for (const auto& deviceEntry : deviceApkMetadata.entries()) { md5Digest md5; memcpy(&md5, deviceEntry.md5().data(), deviceEntry.md5().size()); deviceEntries[md5].push_back(&deviceEntry); } uint64_t totalSize = 0; for (const auto& localEntry : localApkMetadata.entries()) { totalSize += localEntry.datasize(); md5Digest md5; memcpy(&md5, localEntry.md5().data(), localEntry.md5().size()); auto deviceEntriesIt = deviceEntries.find(md5); if (deviceEntriesIt == deviceEntries.end()) { continue; } for (const auto* deviceEntry : deviceEntriesIt->second) { if (deviceEntry->md5() == localEntry.md5()) { SimpleEntry simpleEntry; simpleEntry.localEntry = &localEntry; simpleEntry.deviceEntry = deviceEntry; APKEntryToLog(localEntry); outIdenticalEntries.push_back(simpleEntry); break; } } } std::sort(outIdenticalEntries.begin(), outIdenticalEntries.end(), [](const SimpleEntry& lhs, const SimpleEntry& rhs) { return lhs.localEntry->dataoffset() < rhs.localEntry->dataoffset(); }); return totalSize; }