/*
 * Copyright(C) 2021 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.
 */

package com.android.javacard.keymaster;

import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

/**
 * KMMap represents an array of a KMType key and a KMType value. Map is the sequence of pairs. Each
 * pair is one or more sub-types of KMType. The KMMap instance maps to the CBOR type map. KMMap is a
 * KMType and it further extends the value field in TLV_HEADER as MAP_HEADER struct{ short
 * subType;short length;} followed by a sequence of pairs. Each pair contains a key and a value as
 * short pointers to KMType instances.
 */
public class KMMap extends KMType {

  public static final short ANY_MAP_LENGTH = 0x1000;
  private static final byte MAP_HEADER_SIZE = 4;
  private static KMMap prototype;

  private KMMap() {}

  private static KMMap proto(short ptr) {
    if (prototype == null) {
      prototype = new KMMap();
    }
    instanceTable[KM_MAP_OFFSET] = ptr;
    return prototype;
  }

  public static short exp() {
    short ptr = instance(MAP_TYPE, (short) MAP_HEADER_SIZE);
    Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0);
    Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_MAP_LENGTH);
    return ptr;
  }

  public static short instance(short length) {
    short ptr = KMType.instance(MAP_TYPE, (short) (MAP_HEADER_SIZE + (length * 4)));
    Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0);
    Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length);
    return ptr;
  }

  public static short instance(short length, byte type) {
    short ptr = instance(length);
    Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type);
    return ptr;
  }

  public static KMMap cast(short ptr) {
    if (heap[ptr] != MAP_TYPE) {
      ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }
    return proto(ptr);
  }

  public void add(short index, short keyPtr, short valPtr) {
    short len = length();
    if (index >= len) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    }
    short keyIndex =
        (short)
            (instanceTable[KM_MAP_OFFSET]
                + TLV_HEADER_SIZE
                + MAP_HEADER_SIZE
                + (short) (index * 4));
    Util.setShort(heap, keyIndex, keyPtr);
    Util.setShort(heap, (short) (keyIndex + 2), valPtr);
  }

  public short getKey(short index) {
    short len = length();
    if (index >= len) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    }
    return Util.getShort(
        heap,
        (short)
            (instanceTable[KM_MAP_OFFSET]
                + TLV_HEADER_SIZE
                + MAP_HEADER_SIZE
                + (short) (index * 4)));
  }

  public short getKeyValue(short index) {
    short len = length();
    if (index >= len) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    }
    return Util.getShort(
        heap,
        (short)
            (instanceTable[KM_MAP_OFFSET]
                + TLV_HEADER_SIZE
                + MAP_HEADER_SIZE
                + (short) (index * 4 + 2)));
  }

  public void swap(short index1, short index2) {
    short len = length();
    if (index1 >= len || index2 >= len) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    }
    // Swap keys
    short indexPtr1 =
        Util.getShort(
            heap,
            (short)
                (instanceTable[KM_MAP_OFFSET]
                    + TLV_HEADER_SIZE
                    + MAP_HEADER_SIZE
                    + (short) (index1 * 4)));
    short indexPtr2 =
        Util.getShort(
            heap,
            (short)
                (instanceTable[KM_MAP_OFFSET]
                    + TLV_HEADER_SIZE
                    + MAP_HEADER_SIZE
                    + (short) (index2 * 4)));
    Util.setShort(
        heap,
        (short)
            (instanceTable[KM_MAP_OFFSET]
                + TLV_HEADER_SIZE
                + MAP_HEADER_SIZE
                + (short) (index1 * 4)),
        indexPtr2);
    Util.setShort(
        heap,
        (short)
            (instanceTable[KM_MAP_OFFSET]
                + TLV_HEADER_SIZE
                + MAP_HEADER_SIZE
                + (short) (index2 * 4)),
        indexPtr1);

    // Swap Values
    indexPtr1 =
        Util.getShort(
            heap,
            (short)
                (instanceTable[KM_MAP_OFFSET]
                    + TLV_HEADER_SIZE
                    + MAP_HEADER_SIZE
                    + (short) (index1 * 4 + 2)));
    indexPtr2 =
        Util.getShort(
            heap,
            (short)
                (instanceTable[KM_MAP_OFFSET]
                    + TLV_HEADER_SIZE
                    + MAP_HEADER_SIZE
                    + (short) (index2 * 4 + 2)));
    Util.setShort(
        heap,
        (short)
            (instanceTable[KM_MAP_OFFSET]
                + TLV_HEADER_SIZE
                + MAP_HEADER_SIZE
                + (short) (index1 * 4 + 2)),
        indexPtr2);
    Util.setShort(
        heap,
        (short)
            (instanceTable[KM_MAP_OFFSET]
                + TLV_HEADER_SIZE
                + MAP_HEADER_SIZE
                + (short) (index2 * 4 + 2)),
        indexPtr1);
  }

  public void canonicalize() {
    KMCoseMap.canonicalize(instanceTable[KM_MAP_OFFSET], length());
  }

  public short getStartOff() {
    return (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + MAP_HEADER_SIZE);
  }

  public short length() {
    return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + 2));
  }

  public byte[] getBuffer() {
    return heap;
  }
}
