/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* 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.
*/
package com.tencent.tinker.lib.patch;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.SystemClock;
import com.tencent.tinker.bsdiff.BSPatch;
import com.tencent.tinker.lib.tinker.Tinker;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.TinkerRuntimeException;
import com.tencent.tinker.loader.shareutil.ShareBsDiffPatchInfo;
import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
import com.tencent.tinker.loader.shareutil.ShareSecurityCheck;
import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Created by zhangshaowen on 16/3/21.
*/
public class BsDiffPatchInternal extends BasePatchInternal {
private static final String TAG = "Tinker.BsDiffPatchInternal";
protected static boolean tryRecoverLibraryFiles(Tinker manager, ShareSecurityCheck checker, Context context,
String patchVersionDirectory, File patchFile) {
if (!manager.isEnabledForNativeLib()) {
TinkerLog.w(TAG, "patch recover, library is not enabled");
return true;
}
String libMeta = checker.getMetaContentMap().get(SO_META_FILE);
if (libMeta == null) {
TinkerLog.w(TAG, "patch recover, library is not contained");
return true;
}
long begin = SystemClock.elapsedRealtime();
boolean result = patchLibraryExtractViaBsDiff(context, patchVersionDirectory, libMeta, patchFile);
long cost = SystemClock.elapsedRealtime() - begin;
TinkerLog.i(TAG, "recover lib result:%b, cost:%d", result, cost);
return result;
}
private static boolean patchLibraryExtractViaBsDiff(Context context, String patchVersionDirectory, String meta, File patchFile) {
String dir = patchVersionDirectory + "/" + SO_PATH + "/";
return extractBsDiffInternals(context, dir, meta, patchFile, TYPE_Library);
}
private static boolean extractBsDiffInternals(Context context, String dir, String meta, File patchFile, int type) {
//parse
ArrayList<ShareBsDiffPatchInfo> patchList = new ArrayList<>();
ShareBsDiffPatchInfo.parseDiffPatchInfo(meta, patchList);
if (patchList.isEmpty()) {
TinkerLog.w(TAG, "extract patch list is empty! type:%s:", ShareTinkerInternals.getTypeString(type));
return true;
}
File directory = new File(dir);
if (!directory.exists()) {
directory.mkdirs();
}
//I think it is better to extract the raw files from apk
Tinker manager = Tinker.with(context);
ApplicationInfo applicationInfo = context.getApplicationInfo();
if (applicationInfo == null) {
// Looks like running on a test Context, so just return without patching.
TinkerLog.w(TAG, "applicationInfo == null!!!!");
return false;
}
ZipFile apk = null;
ZipFile patch = null;
try {
String apkPath = applicationInfo.sourceDir;
apk = new ZipFile(apkPath);
patch = new ZipFile(patchFile);
for (ShareBsDiffPatchInfo info : patchList) {
long start = System.currentTimeMillis();
final String infoPath = info.path;
String patchRealPath;
if (infoPath.equals("")) {
patchRealPath = info.name;
} else {
patchRealPath = info.path + "/" + info.name;
}
final String fileMd5 = info.md5;
if (!SharePatchFileUtil.checkIfMd5Valid(fileMd5)) {
TinkerLog.w(TAG, "meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.name, info.md5);
manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type));
return false;
}
String middle;
middle = info.path + "/" + info.name;
File extractedFile = new File(dir + middle);
//check file whether already exist
if (extractedFile.exists()) {
if (fileMd5.equals(SharePatchFileUtil.getMD5(extractedFile))) {
//it is ok, just continue
TinkerLog.w(TAG, "bsdiff file %s is already exist, and md5 match, just continue", extractedFile.getPath());
continue;
} else {
TinkerLog.w(TAG, "have a mismatch corrupted dex " + extractedFile.getPath());
extractedFile.delete();
}
} else {
extractedFile.getParentFile().mkdirs();
}
String patchFileMd5 = info.patchMd5;
//it is a new file, just copy
ZipEntry patchFileEntry = patch.getEntry(patchRealPath);
if (patchFileEntry == null) {
TinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type);
return false;
}
if (patchFileMd5.equals("0")) {
if (!extract(patch, patchFileEntry, extractedFile, fileMd5, false)) {
TinkerLog.w(TAG, "Failed to extract file " + extractedFile.getPath());
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type);
return false;
}
} else {
//we do not check the intermediate files' md5 to save time, use check whether it is 32 length
if (!SharePatchFileUtil.checkIfMd5Valid(patchFileMd5)) {
TinkerLog.w(TAG, "meta file md5 mismatch, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.name, patchFileMd5);
manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type));
return false;
}
ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath);
if (rawApkFileEntry == null) {
TinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type);
return false;
}
String rawApkCrc = info.rawCrc;
//check source crc instead of md5 for faster
String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc());
if (!rawEntryCrc.equals(rawApkCrc)) {
TinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, rawApkCrc, rawEntryCrc);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type);
return false;
}
InputStream oldStream = null;
InputStream newStream = null;
try {
oldStream = apk.getInputStream(rawApkFileEntry);
newStream = patch.getInputStream(patchFileEntry);
BSPatch.patchFast(oldStream, newStream, extractedFile);
} finally {
SharePatchFileUtil.closeQuietly(oldStream);
SharePatchFileUtil.closeQuietly(newStream);
}
//go go go bsdiff get the
if (!SharePatchFileUtil.verifyFileMd5(extractedFile, fileMd5)) {
TinkerLog.w(TAG, "Failed to recover diff file " + extractedFile.getPath());
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.name, type);
SharePatchFileUtil.safeDeleteFile(extractedFile);
return false;
}
TinkerLog.w(TAG, "success recover bsdiff file: %s, use time: %d",
extractedFile.getPath(), (System.currentTimeMillis() - start));
}
}
} catch (Throwable e) {
// e.printStackTrace();
throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e);
} finally {
SharePatchFileUtil.closeZip(apk);
SharePatchFileUtil.closeZip(patch);
}
return true;
}
}