blob: 8aa7da72fcde74777f65d9a74dace814f30aeb6d [file] [log] [blame]
/*
* 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 <inttypes.h>
#include <stdio.h>
#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <openssl/md5.h>
#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<SimpleEntry>& 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<SimpleEntry>& 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<long long>(currentSizeOut), static_cast<long long>(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<SimpleEntry> identicalEntries;
uint64_t totalSize =
BuildIdenticalEntries(identicalEntries, localApkMetadata, deviceApkMetadata);
ReportSavings(identicalEntries, totalSize);
GeneratePatch(identicalEntries, localApkPath, deviceApkPath, output);
return true;
}
uint64_t DeployPatchGenerator::BuildIdenticalEntries(std::vector<SimpleEntry>& outIdenticalEntries,
const APKMetaData& localApkMetadata,
const APKMetaData& deviceApkMetadata) {
outIdenticalEntries.reserve(
std::min(localApkMetadata.entries_size(), deviceApkMetadata.entries_size()));
using md5Digest = std::pair<uint64_t, uint64_t>;
struct md5Hash {
size_t operator()(const md5Digest& digest) const {
std::hash<uint64_t> 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<md5Digest, std::vector<const APKEntry*>, 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;
}