/* * Copyright 2013-present Facebook, Inc. * * 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. */ package com.facebook.buck.dalvik; import com.facebook.buck.jvm.java.classes.AbstractFileLike; import com.facebook.buck.jvm.java.classes.FileLike; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; /** Helper to create a "canary" class for the secondary DEX. See {@link #create}. */ public class CanaryFactory { static final String CANARY_PATH_FORMAT = "%s/dex%02d/Canary.class"; /** * Produced by compiling the following Java file with JDK 7 with "-target 6 -source 6". * * <pre> * package secondary.dex01; * public interface Canary {} * </pre> */ private static final byte[] CANARY_TEMPLATE = { -54, -2, -70, -66, 0x00, 0x00, 0x00, 0x32, 0x00, 0x05, 0x07, 0x00, 0x03, 0x07, 0x00, 0x04, 0x01, 0x00, 0x16, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x64, 0x65, 0x78, 0x30, 0x31, 0x2f, 0x43, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x01, 0x00, 0x10, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x06, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** * Offset into {@link #CANARY_TEMPLATE} where we find the 2-byte UTF-8 index that must be updated * for each canary class to change the package. */ private static final int CANARY_INDEX_OFFSET = 32; private static final int CANARY_STORE_OFFSET = 19; /** Utility class: do not instantiate */ private CanaryFactory() {} /** * Adds a "canary" class to a secondary dex that can be safely loaded on any system. This avoids * an issue where, during secondary dex loading, we attempt to verify a secondary dex by loading * an arbitrary class, but the class we try to load isn't valid on that system (e.g., it depends * on Google Maps, but we are on AOSP). * * @param store dex store name of the current zip (to ensure unique names). * @param index Index of the current zip (to ensure unique names). */ public static FileLike create(final String store, final int index) { final byte[] canaryClass = Arrays.copyOf(CANARY_TEMPLATE, CANARY_TEMPLATE.length); final String canaryIndexStr = String.format("%02d", index); byte[] canaryIndexBytes = canaryIndexStr.getBytes(Charsets.UTF_8); Preconditions.checkState( canaryIndexBytes.length == 2, "Formatted index string \"" + canaryIndexStr + "\" is not exactly 2 bytes."); System.arraycopy(canaryIndexBytes, 0, canaryClass, CANARY_INDEX_OFFSET, 2); byte[] canaryStoreBytes = store.getBytes(Charsets.UTF_8); Preconditions.checkState( canaryStoreBytes.length == 9, "Formatted store string \"" + store + "\" is not exactly 9 bytes.\""); System.arraycopy(canaryStoreBytes, 0, canaryClass, CANARY_STORE_OFFSET, 9); String relativePath = String.format(CANARY_PATH_FORMAT, store, index); return getCanaryClass(relativePath, canaryClass); } private static FileLike getCanaryClass(final String relativePath, final byte[] canaryClass) { return new AbstractFileLike() { @Override public Path getContainer() { return Paths.get(":memory:"); } @Override public String getRelativePath() { return relativePath; } @Override public long getSize() { return canaryClass.length; } @Override public InputStream getInput() { return new ByteArrayInputStream(canaryClass); } }; } }