/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.sync.jpake.stage;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;

import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.jpake.BigIntegerHelper;
import org.mozilla.gecko.sync.jpake.Gx3OrGx4IsZeroOrOneException;
import org.mozilla.gecko.sync.jpake.IncorrectZkpException;
import org.mozilla.gecko.sync.jpake.JPakeClient;
import org.mozilla.gecko.sync.jpake.JPakeCrypto;
import org.mozilla.gecko.sync.jpake.JPakeJson;
import org.mozilla.gecko.sync.jpake.Zkp;
import org.mozilla.gecko.sync.setup.Constants;

public class ComputeStepTwoStage extends JPakeStage {

  @Override
  public void execute(JPakeClient jClient) {
    Logger.debug(LOG_TAG, "Computing round 2.");

    // Check incoming message sender.
    if (!jClient.jIncoming.get(Constants.JSON_KEY_TYPE).equals(jClient.theirSignerId + "1")) {
      Logger.error(LOG_TAG, "Invalid round 1 message: " + jClient.jIncoming.toJSONString());
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    }

    // Check incoming message fields.
    ExtendedJSONObject iPayload;
    try {
      iPayload = jClient.jIncoming.getObject(Constants.JSON_KEY_PAYLOAD);
    } catch (NonObjectJSONException e) {
      Logger.error(LOG_TAG, "JSON object exception.", e);
      jClient.abort(Constants.JPAKE_ERROR_INVALID);
      return;
    }
    if (iPayload == null) {
      Logger.error(LOG_TAG, "Invalid round 1 message: " + jClient.jIncoming.toJSONString());
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    }
    ExtendedJSONObject zkpPayload3;
    ExtendedJSONObject zkpPayload4;
    try {
      zkpPayload3 = iPayload.getObject(Constants.ZKP_KEY_ZKP_X1);
      zkpPayload4 = iPayload.getObject(Constants.ZKP_KEY_ZKP_X2);
    } catch (NonObjectJSONException e1) {
      jClient.abort(Constants.JPAKE_ERROR_INVALID);
      return;
    }

    if (zkpPayload3 == null || zkpPayload4 == null) {
      Logger.error(LOG_TAG, "Invalid round 1 zkpPayload message");
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    }

    if (!jClient.theirSignerId.equals(zkpPayload3.get(Constants.ZKP_KEY_ID)) ||
        !jClient.theirSignerId.equals(zkpPayload4.get(Constants.ZKP_KEY_ID))) {
      Logger.error(LOG_TAG, "Invalid round 1 zkpPayload message");
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    }

    // Extract message fields.
    jClient.jParty.gx3 = new BigInteger((String) iPayload.get(Constants.ZKP_KEY_GX1), 16);
    jClient.jParty.gx4 = new BigInteger((String) iPayload.get(Constants.ZKP_KEY_GX2), 16);

    // Extract ZKPs.
    String zkp3_gr = (String) zkpPayload3.get(Constants.ZKP_KEY_GR);
    String zkp3_b  = (String) zkpPayload3.get(Constants.ZKP_KEY_B);
    String zkp3_id = (String) zkpPayload3.get(Constants.ZKP_KEY_ID);

    String zkp4_gr = (String) zkpPayload4.get(Constants.ZKP_KEY_GR);
    String zkp4_b  = (String) zkpPayload4.get(Constants.ZKP_KEY_B);
    String zkp4_id = (String) zkpPayload4.get(Constants.ZKP_KEY_ID);

    jClient.jParty.zkp3 = new Zkp(new BigInteger(zkp3_gr, 16), new BigInteger(zkp3_b, 16), zkp3_id);
    jClient.jParty.zkp4 = new Zkp(new BigInteger(zkp4_gr, 16), new BigInteger(zkp4_b, 16), zkp4_id);

    // J-PAKE round 2.
    try {
      JPakeCrypto.round2(JPakeClient.secretAsBigInteger(jClient.secret), jClient.jParty, jClient.numGen);
    } catch (Gx3OrGx4IsZeroOrOneException e) {
      Logger.error(LOG_TAG, "gx3 and gx4 cannot equal 0 or 1.");
      jClient.abort(Constants.JPAKE_ERROR_INTERNAL);
      return;
    } catch (IncorrectZkpException e) {
      Logger.error(LOG_TAG, "ZKP mismatch");
      jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
      return;
    } catch (NoSuchAlgorithmException e) {
      Logger.error(LOG_TAG, "NoSuchAlgorithmException", e);
      jClient.abort(Constants.JPAKE_ERROR_INTERNAL);
      return;
    } catch (UnsupportedEncodingException e) {
      Logger.error(LOG_TAG, "UnsupportedEncodingException", e);
      jClient.abort(Constants.JPAKE_ERROR_INTERNAL);
      return;
    }

    // Make outgoing payload.
    Zkp zkpA = jClient.jParty.thisZkpA;
    ExtendedJSONObject oPayload = new ExtendedJSONObject();
    ExtendedJSONObject jZkpA = JPakeJson.makeJZkp(zkpA.gr, zkpA.b, zkpA.id);
    oPayload.put(Constants.ZKP_KEY_A, BigIntegerHelper.toEvenLengthHex(jClient.jParty.thisA));
    oPayload.put(Constants.ZKP_KEY_ZKP_A, jZkpA);

    // Make outgoing message.
    jClient.jOutgoing = new ExtendedJSONObject();
    jClient.jOutgoing.put(Constants.JSON_KEY_TYPE, jClient.mySignerId + "2");
    jClient.jOutgoing.put(Constants.JSON_KEY_VERSION, JPakeClient.KEYEXCHANGE_VERSION);
    jClient.jOutgoing.put(Constants.JSON_KEY_PAYLOAD, oPayload);

    jClient.runNextStage();
  }
}
