/**
   * Takes a Msg and encodes it into a single byte[], in a way that is compatible with the way that
   * PyBitmessage does. This payload can then be sent to a server to be disseminated across the
   * network. The payload is stored as a Payload object.
   *
   * @param encMsg - A msg Object containing the message data used to create the payload.
   * @param powDone - A boolean value indicating whether or not POW has been done for this message
   * @param toPubkey - A Pubkey object containing the data for the Pubkey of the address that this
   *     message is being sent to
   * @return A Payload object containing the message payload
   */
  private Payload constructMsgPayloadForDissemination(
      BMObject encMsg, boolean powDone, Pubkey toPubkey) {
    // Create a new Payload object to hold the payload data
    Payload msgPayload = new Payload();
    msgPayload.setBelongsToMe(true);
    msgPayload.setPOWDone(powDone);
    msgPayload.setType(Payload.OBJECT_TYPE_MSG);

    // Encode the POW nonce, expiration time, object type, object version, and stream number values
    // into byte form
    byte[] powNonceBytes = ByteUtils.longToBytes(encMsg.getPOWNonce());
    byte[] expirationTimeBytes = ByteUtils.longToBytes(encMsg.getExpirationTime());
    byte[] objectTypeBytes = ByteUtils.intToBytes(OBJECT_TYPE_MSG);
    byte[] objectVersionBytes = VarintEncoder.encode(OBJECT_VERSION_MSG);
    byte[] streamNumberBytes = VarintEncoder.encode(encMsg.getStreamNumber());

    byte[] payload = null;
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
      if (powDone) {
        outputStream.write(powNonceBytes);
      }
      outputStream.write(expirationTimeBytes);
      outputStream.write(objectTypeBytes);
      outputStream.write(objectVersionBytes);
      outputStream.write(streamNumberBytes);
      outputStream.write(encMsg.getPayload());

      payload = outputStream.toByteArray();
      outputStream.close();
    } catch (IOException e) {
      throw new RuntimeException(
          "IOException occurred in DataProcessor.constructMsgPayloadForDissemination()", e);
    }

    msgPayload.setPayload(payload);

    // Save the Payload object to the database
    PayloadProvider payProv = PayloadProvider.get(App.getContext());
    long msgPayloadId = payProv.addPayload(msgPayload);

    // Finally, set the msg payload ID to the one generated by the SQLite database
    msgPayload.setId(msgPayloadId);

    return msgPayload;
  }
  /**
   * Takes a Msg and constructs the payload needed to do POW for it.
   *
   * @param msg - The msg Object to construct the POW payload for
   * @return The POW payload
   */
  private byte[] constructMsgPayloadForPOW(BMObject msg) {
    try {
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

      outputStream.write(
          ByteUtils.longToBytes(
              msg.getExpirationTime())); // This conversion results in a byte[] of length 8, which
      // is what we want
      outputStream.write(ByteUtils.intToBytes(OBJECT_TYPE_MSG));
      outputStream.write(VarintEncoder.encode(OBJECT_VERSION_MSG));
      outputStream.write(VarintEncoder.encode(msg.getStreamNumber()));
      outputStream.write(msg.getPayload());

      return outputStream.toByteArray();
    } catch (IOException e) {
      throw new RuntimeException(
          "IOException occurred in OutgoingMessageProcessor.constructMsgPayloadForPOW()", e);
    }
  }
  /**
   * Takes an UnencryptedMsg object and extracts only the data needed to encrypt the message,
   * discarding data that is only used by Bitseal internally, such as the ID number.
   *
   * @param inputMsgData - The UnencryptedMsg object from which the data is to be extracted
   * @return A byte[] containing the message data needed for encryption
   */
  private byte[] constructMsgPayloadForEncryption(UnencryptedMsg unencMsg) {
    byte[] msgDataForEncryption = null;
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
      outputStream.write(VarintEncoder.encode(unencMsg.getSenderAddressVersion()));
      outputStream.write(VarintEncoder.encode(unencMsg.getSenderStreamNumber()));
      outputStream.write(ByteUtils.intToBytes(unencMsg.getBehaviourBitfield()));

      // If the public signing and public encryption keys have their leading 0x04 byte in place then
      // we need to remove them
      byte[] publicSigningKey = unencMsg.getPublicSigningKey();
      if (publicSigningKey[0] == (byte) 4 && publicSigningKey.length == 65) {
        publicSigningKey = ArrayCopier.copyOfRange(publicSigningKey, 1, publicSigningKey.length);
      }
      outputStream.write(publicSigningKey);

      byte[] publicEncryptionKey = unencMsg.getPublicEncryptionKey();
      if (publicEncryptionKey[0] == (byte) 4 && publicEncryptionKey.length == 65) {
        publicEncryptionKey =
            ArrayCopier.copyOfRange(publicEncryptionKey, 1, publicEncryptionKey.length);
      }
      outputStream.write(publicEncryptionKey);

      if (unencMsg.getSenderAddressVersion()
          >= 3) // The nonceTrialsPerByte and extraBytes fields are only included when the address
      // version is >= 3
      {
        outputStream.write(VarintEncoder.encode(unencMsg.getNonceTrialsPerByte()));
        outputStream.write(VarintEncoder.encode(unencMsg.getExtraBytes()));
      }

      outputStream.write(unencMsg.getDestinationRipe());
      outputStream.write(VarintEncoder.encode(unencMsg.getEncoding()));
      outputStream.write(VarintEncoder.encode(unencMsg.getMessageLength()));
      outputStream.write(unencMsg.getMessage());
      outputStream.write(VarintEncoder.encode(unencMsg.getAckLength()));
      outputStream.write(unencMsg.getAckMsg());
      outputStream.write(VarintEncoder.encode(unencMsg.getSignatureLength()));
      outputStream.write(unencMsg.getSignature());

      msgDataForEncryption = outputStream.toByteArray();
      outputStream.close();
    } catch (IOException e) {
      throw new RuntimeException(
          "IOException occurred in DataProcessor.constructMsgPayloadForEncryption()", e);
    }

    return msgDataForEncryption;
  }
  /**
   * Calculates the acknowledgement Message for a given message. <br>
   * <br>
   * The process for this is as follows:<br>
   * <br>
   * 1) initialPayload = time || stream number || 32 bytes of random data<br>
   * <br>
   * 2) Do POW for the initialPayload<br>
   * <br>
   * 3) ackData = POWnonce || msgHeader || initialPayload<br>
   * <br>
   *
   * @param message - The original plain text Message object, provided so that its status can be
   *     updated during the process
   * @param ackData - A byte[] containing the 32 bytes of random data which is the acknowledgment
   *     data
   * @param toStreamNumber - An int representing the stream number of the destination address of the
   *     message to be sent
   * @param doPOW - A boolean indicating whether or not POW should be done for ack msgs generated
   *     during this process
   * @param timeToLive - The 'time to live' value (in seconds) to be used in processing this message
   * @return A byte[] containing the acknowledgement data for the message we wish to send
   */
  private byte[] generateFullAckMessage(
      Message message, byte[] ackData, int toStreamNumber, boolean doPOW, long timeToLive) {
    // Get the fuzzed expiration time
    long expirationTime = TimeUtils.getFuzzedExpirationTime(timeToLive);

    // Encode the expiration time, object type, object version, and stream number values into byte
    // form
    byte[] expirationTimeBytes = ByteUtils.longToBytes((expirationTime));
    byte[] objectTypeBytes = ByteUtils.intToBytes(OBJECT_TYPE_MSG);
    byte[] objectVersionBytes = VarintEncoder.encode(OBJECT_VERSION_MSG);
    byte[] streamNumberBytes = VarintEncoder.encode((long) toStreamNumber);

    // Combine the time, object type, object version, stream number, and ack data values into a
    // single byte[]
    byte[] initialPayload =
        ByteUtils.concatenateByteArrays(
            expirationTimeBytes, objectTypeBytes, objectVersionBytes, streamNumberBytes, ackData);

    // Create the payload for the ack msg
    byte[] payload = new byte[0];
    if (doPOW) {
      // Update the status of this message displayed in the UI
      String messageStatus = App.getContext().getString(R.string.message_status_doing_ack_pow);
      MessageStatusHandler.updateMessageStatus(message, messageStatus);

      // Do proof of work for the acknowledgement payload
      Log.i(
          TAG,
          "About to do POW calculations for the acknowledgment payload of a msg that we are sending");
      long powNonce =
          new POWProcessor()
              .doPOW(
                  initialPayload,
                  expirationTime,
                  POWProcessor.NETWORK_NONCE_TRIALS_PER_BYTE,
                  POWProcessor.NETWORK_EXTRA_BYTES);

      byte[] powNonceBytes = ByteUtils.longToBytes(powNonce);

      payload = ByteUtils.concatenateByteArrays(powNonceBytes, initialPayload);
    } else {
      payload = initialPayload;
    }

    byte[] headerData = new MessageProcessor().generateObjectHeader(payload);
    byte[] fullAckMsg = ByteUtils.concatenateByteArrays(headerData, payload);

    return fullAckMsg;
  }