// Copyright 2020 Google LLC
//
// 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
//
//     https://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.google.security.cryptauth.lib.securegcm;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.SecretKey;

/**
 * Implementation of {@link D2DConnectionContext} for version 1 of the D2D protocol. In this
 * version, communication is fully duplex, as separate keys and sequence nubmers are used for
 * encoding and decoding.
 */
public class D2DConnectionContextV1 extends D2DConnectionContext {
  public static final int PROTOCOL_VERSION = 1;

  private final SecretKey encodeKey;
  private final SecretKey decodeKey;
  private int encodeSequenceNumber;
  private int decodeSequenceNumber;

  /**
   * Package private constructor. Should never be called directly except by the
   * {@link D2DHandshakeContext}
   *
   * @param encodeKey
   * @param decodeKey
   * @param initialEncodeSequenceNumber
   * @param initialDecodeSequenceNumber
   */
  D2DConnectionContextV1(
      SecretKey encodeKey,
      SecretKey decodeKey,
      int initialEncodeSequenceNumber,
      int initialDecodeSequenceNumber) {
    super(PROTOCOL_VERSION);
    this.encodeKey = encodeKey;
    this.decodeKey = decodeKey;
    this.encodeSequenceNumber = initialEncodeSequenceNumber;
    this.decodeSequenceNumber = initialDecodeSequenceNumber;
  }

  @Override
  public byte[] getSessionUnique() throws NoSuchAlgorithmException {
    if (encodeKey == null || decodeKey == null) {
      throw new IllegalStateException(
          "Connection has not been correctly initialized; encode key or decode key is null");
    }

    // Ensure that the initator and responder keys are hashed in a deterministic order, so they have
    // the same session unique code.
    byte[] encodeKeyBytes = encodeKey.getEncoded();
    byte[] decodeKeyBytes = decodeKey.getEncoded();
    int encodeKeyHash = Arrays.hashCode(encodeKeyBytes);
    int decodeKeyHash = Arrays.hashCode(decodeKeyBytes);
    byte[] firstKeyBytes = encodeKeyHash < decodeKeyHash ? encodeKeyBytes : decodeKeyBytes;
    byte[] secondKeyBytes = firstKeyBytes == encodeKeyBytes ? decodeKeyBytes : encodeKeyBytes;

    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update(D2DCryptoOps.SALT);
    md.update(firstKeyBytes);
    md.update(secondKeyBytes);
    return md.digest();
  }

  @Override
  protected void incrementSequenceNumberForEncoding() {
    encodeSequenceNumber++;
  }

  @Override
  protected void incrementSequenceNumberForDecoding() {
    decodeSequenceNumber++;
  }

  @Override
  int getSequenceNumberForEncoding() {
    return encodeSequenceNumber;
  }

  @Override
  int getSequenceNumberForDecoding() {
    return decodeSequenceNumber;
  }

  @Override
  SecretKey getEncodeKey() {
    return encodeKey;
  }

  @Override
  SecretKey getDecodeKey() {
    return decodeKey;
  }

  /**
   * Structure of saved session is:
   * +------------------------------------------------------------------------------------------+
   * |     1 Byte       | 4 Bytes (big endian) | 4 Bytes (big endian) |  32 Bytes  |  32 Bytes  |
   * +------------------------------------------------------------------------------------------+
   * | Protocol Version |   encode seq number  |   decode seq number  | encode key | decode key |
   * +------------------------------------------------------------------------------------------+
   */
  @Override
  public byte[] saveSession() {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();

    try {
      // Protocol version
      bytes.write(1);

      // Encode sequence number
      bytes.write(signedIntToBytes(encodeSequenceNumber));

      // Decode sequence number
      bytes.write(signedIntToBytes(decodeSequenceNumber));

      // Encode Key
      bytes.write(encodeKey.getEncoded());

      // Decode Key
      bytes.write(decodeKey.getEncoded());
    } catch (IOException e) {
      // should not happen
      e.printStackTrace();
      return null;
    }

    return bytes.toByteArray();
  }
}
