256 lines
9.7 KiB
C++
256 lines
9.7 KiB
C++
/*
|
|
* 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;
|
|
}
|