/*
 * Copyright (C) 2017 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.
 */
/*
 * Copyright (c) 2017, The Linux Foundation.
 */

/*
 * Contributed by: Giesecke & Devrient GmbH.
 */

package com.android.se.internal;

import android.content.Context;
import android.content.pm.PackageManager;

import java.security.AccessControlException;

/** Util class for byte[] operations */
public class Util {

    public static final byte END = -1;

    /** Returns a new array containing both the arrays appended */
    public static byte[] mergeBytes(byte[] array1, byte[] array2) {
        byte[] data = new byte[array1.length + array2.length];
        System.arraycopy(array1, 0, data, 0, array1.length);
        System.arraycopy(array2, 0, data, array1.length, array2.length);
        return data;
    }

    /** Extracts the required bytes from the array */
    public static byte[] getMid(byte[] array, int start, int length) {
        byte[] data = new byte[length];
        System.arraycopy(array, start, data, 0, length);
        return data;
    }

    /**
     * Returns a concatenated response.
     *
     * @param r1     the first part of the response.
     * @param r2     the second part of the response.
     * @param length the number of bytes of the second part to be appended.
     * @return a concatenated response.
     */
    public static byte[] appendResponse(byte[] r1, byte[] r2, int length) {
        byte[] rsp = new byte[r1.length + length];
        System.arraycopy(r1, 0, rsp, 0, r1.length);
        System.arraycopy(r2, 0, rsp, r1.length, length);
        return rsp;
    }

    /**
     * Creates a formatted exception message.
     *
     * @param commandName the name of the command. <code>null</code> if not specified.
     * @param sw          the response status word.
     * @return a formatted exception message.
     */
    public static String createMessage(String commandName, int sw) {
        StringBuilder message = new StringBuilder();
        if (commandName != null) {
            message.append(commandName).append(" ");
        }
        message.append("SW1/2 error: ");
        message.append(Integer.toHexString(sw | 0x10000).substring(1));
        return message.toString();
    }

    /**
     * Creates a formatted exception message.
     *
     * @param commandName the name of the command. <code>null</code> if not specified.
     * @param message     the message to be formatted.
     * @return a formatted exception message.
     */
    public static String createMessage(String commandName, String message) {
        if (commandName == null) {
            return message;
        }
        return commandName + " " + message;
    }

    /**
     * Get package name from the user id.
     *
     * <p>This shall fix the problem the issue that process name != package name due to
     * anndroid:process attribute in manifest file.
     *
     * <p>But this call is not really secure either since a uid can be shared between one and more
     * apks
     *
     * @return The first package name associated with this uid.
     */
    public static String getPackageNameFromCallingUid(Context context, int uid) {
        PackageManager packageManager = context.getPackageManager();
        if (packageManager != null) {
            String[] packageName = packageManager.getPackagesForUid(uid);
            if (packageName != null && packageName.length > 0) {
                return packageName[0];
            }
        }
        throw new AccessControlException("Caller PackageName can not be determined");
    }

    /**
     * Returns a copy of the given CLA byte where the channel number bits are set as specified by
     * the
     * given channel number See GlobalPlatform Card Specification 2.2.0.7: 11.1.4 Class Byte
     * Coding.
     *
     * @param cla           the CLA byte. Won't be modified
     * @param channelNumber within [0..3] (for first interindustry class byte coding) or [4..19]
     *                      (for
     *                      further interindustry class byte coding)
     * @return the CLA byte with set channel number bits. The seventh bit indicating the used coding
     * (first/further interindustry class byte coding) might be modified
     */
    public static byte setChannelToClassByte(byte cla, int channelNumber) {
        if (channelNumber < 4) {
            // b7 = 0 indicates the first interindustry class byte coding
            cla = (byte) ((cla & 0xBC) | channelNumber);
        } else if (channelNumber < 20) {
            // b7 = 1 indicates the further interindustry class byte coding
            boolean isSM = (((cla & 0x40) == 0x00) && ((cla & 0x0C) != 0));
            cla = (byte) ((cla & 0xB0) | 0x40 | (channelNumber - 4));
            if (isSM) {
                cla |= 0x20;
            }
        } else {
            throw new IllegalArgumentException("Channel number must be within [0..19]");
        }
        return cla;
    }

    /**
     * Clear the channel number.
     *
     * @return the cla without channel number
     */
    public static byte clearChannelNumber(byte cla) {
        // bit 7 determines which standard is used
        boolean isFirstInterindustryClassByteCoding = (cla & 0x40) == 0x00;

        if (isFirstInterindustryClassByteCoding) {
            // First Interindustry Class Byte Coding
            // see 11.1.4.1: channel number is encoded in the 2 rightmost bits
            return (byte) (cla & 0xFC);
        } else {
            // Further Interindustry Class Byte Coding
            // see 11.1.4.2: channel number is encoded in the 4 rightmost bits
            return (byte) (cla & 0xF0);
        }
    }

    /**
     * Extracts the channel number from a CLA byte. Specified in GlobalPlatform Card Specification
     * 2.2.0.7: 11.1.4 Class Byte Coding.
     *
     * @param cla the command's CLA byte
     * @return the channel number within [0x00..0x0F]
     */
    public static int parseChannelNumber(byte cla) {
        // bit 7 determines which standard is used
        boolean isFirstInterindustryClassByteCoding = (cla & 0x40) == 0x00;

        if (isFirstInterindustryClassByteCoding) {
            // First Interindustry Class Byte Coding
            // see 11.1.4.1: channel number is encoded in the 2 rightmost bits
            return cla & 0x03;
        } else {
            // Further Interindustry Class Byte Coding
            // see 11.1.4.2: channel number is encoded in the 4 rightmost bits
            return (cla & 0x0F) + 4;
        }
    }
}
