/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
 * 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/. */

#include "ICEBinding.h"
#include "ICEStunAttributes.h"
#include "ICETime.h"
#include "ICELog.h"
#include <memory>

using namespace ice;
#define LOG_SUBSYSTEM "ice"

//----------------------- AuthClientBinding -----------------

ConnectivityCheck::ConnectivityCheck()
{
  setComment("ConnectivityCheck");
  mErrorCode = 0;
}

ConnectivityCheck::~ConnectivityCheck()
{
}

ByteBuffer* ConnectivityCheck::generateData(bool force)
{
  if (!mComposed)
  {
    StunMessage msg;
    msg.setMessageClass(StunMessage::RequestClass);
    msg.setMessageType(StunMessage::Binding);
    msg.setTransactionId(mTransactionID);
    
    if (mUseCandidate)
      msg.setAttribute(new ICEUseCandidate());
    
    // Use message integrity attribute
    mMessageIntegrity = true;

    // Use fingerprint attribute
    mFingerprint = true;

    if (mEnablePriority)
      msg.icePriorityAttr().setPriority(mPriority);

    // Copy comment to message object
    msg.setComment(mComment);

    enqueueMessage(msg);

    mComposed = true;
  }

  return Transaction::generateData(force);
}

void ConnectivityCheck::confirmTransaction(NetworkAddress& mapped)
{
  // Save resolved address&ip
  mMappedAddress = mapped;

  // Mark transaction as succeeded
  mState = Transaction::Success;
  
  // Save source IP and port
  mResponseAddress = mapped;
}

bool ConnectivityCheck::processData(StunMessage& msg, NetworkAddress& address)
{
#ifdef ICE_TEST_VERYAGGRESSIVE
  return false;
#endif

  // Check if it is response
  if (msg.messageClass() != StunMessage::ErrorClass &&
      msg.messageClass() != StunMessage::SuccessClass)
    return false;
  
  if (msg.transactionId() != mTransactionID)
    return false;
  
  // Validate againgst password used to encrypt
  if (!msg.validatePacket(mPassword))
    return false;

  // Check for ErrorCode attribute
  if (msg.hasAttribute(StunAttribute::ErrorCode))
  {
    ErrorCode& ec = dynamic_cast<ErrorCode&>(msg.attribute(StunAttribute::ErrorCode));
    
    //save error code and response
    mErrorCode = ec.errorCode();
    mErrorResponse = ec.errorPhrase();
    
    //mark transaction as failed
    mState = Transaction::Failed;

    return true;
  }

  //check if received empty ErrorClass response for poor servers
  if (msg.messageClass() == StunMessage::ErrorClass)
  {
    //mark transaction as failed 
    mState = Transaction::Failed;
    
    return true;
  }

  //check for mapped address attribute
  if (msg.hasAttribute(StunAttribute::MappedAddress))
  {
    // Save resolved address&ip
    mMappedAddress  = msg.mappedAddressAttr().address();

    // Mark transaction as succeeded
    mState = Transaction::Success;

    // Save source IP and port
    mResponseAddress = address;
  }
  
  //check for xor'ed mapped address attribute
  if (msg.hasAttribute(StunAttribute::XorMappedAddress))
  {
    // Save resolved IP and port
    mMappedAddress = msg.xorMappedAddressAttr().address();
    
    // Mark transaction as succeeded
    mState = Transaction::Success;

    // Save source IP and port
    mResponseAddress = address;
  }
  
  return true;
}

NetworkAddress& ConnectivityCheck::mappedAddress()
{
  return mMappedAddress;
}
NetworkAddress& ConnectivityCheck::responseAddress()
{
  return mResponseAddress;
}
void ConnectivityCheck::addUseCandidate()
{
  mUseCandidate = true;
}
int ConnectivityCheck::errorCode()
{
  return mErrorCode;
}
int ConnectivityCheck::resultError()
{
  return mErrorCode;
}
NetworkAddress& ConnectivityCheck::resultSource()
{
  return mResponseAddress;
}
Transaction::State ConnectivityCheck::resultState()
{
  return state();
}
NetworkAddress& ConnectivityCheck::resultLocal()
{
  return mMappedAddress;
}
void ConnectivityCheck::useCandidate()
{
  addUseCandidate();
}
unsigned ConnectivityCheck::resultPriority()
{
  return priorityValue();
}
//---------------------------- ClientBindingTransaction ----------------------------

ClientBinding::ClientBinding()
:Transaction(), mErrorCode(0), mUseCandidate(false)
{
  setComment("ClientBinding");
}

ClientBinding::~ClientBinding()
{
}
    
int ClientBinding::errorCode()
{
  return mErrorCode;
}

std::string ClientBinding::errorResponse()
{
  return mErrorResponse;
}

NetworkAddress& ClientBinding::mappedAddress()
{
  return mMappedAddress;
}

NetworkAddress& ClientBinding::responseAddress()
{
  return mResponseAddress;
}

bool ClientBinding::processData(StunMessage& msg, NetworkAddress& address)
{
  // Check if it is response
  if (msg.messageClass() != StunMessage::ErrorClass &&
      msg.messageClass() != StunMessage::SuccessClass)
    return false;
  
  if (msg.transactionId() != this->mTransactionID)
    return false;
  
  // Check for ErrorCode attribute
  if (msg.hasAttribute(StunAttribute::ErrorCode))
  {
    // Save error code and response
    mErrorCode = msg.errorCodeAttr().errorCode();
    mErrorResponse = msg.errorCodeAttr().errorPhrase();
    
    // Mark transaction as failed
    mState = Transaction::Failed;

    return true;
  }

  // Check if received empty ErrorClass response for poor servers
  if (msg.messageClass() == StunMessage::ErrorClass)
  {
    // Mark transaction as failed 
    mState = Transaction::Failed;
    
    return true;
  }

  // Check for mapped address attribute
  if (msg.hasAttribute(StunAttribute::MappedAddress))
  {
    // Save resolved address&ip
    mMappedAddress = msg.mappedAddressAttr().address();

    // mMark transaction as succeeded
    mState = Transaction::Success;

    // Save source IP and port
    mResponseAddress = address;
  }
  
  //check for xor'ed mapped address attribute
  if (msg.hasAttribute(StunAttribute::XorMappedAddress))
  {
    // Save resolved IP and port
    mMappedAddress = msg.xorMappedAddressAttr().address();

    // Mark transaction as succeeded
    mState = Transaction::Success;

    // Save source IP and port
    mResponseAddress = address;
  }
  
  return true;
}

ByteBuffer* ClientBinding::generateData(bool force)
{
  if (!mComposed)
  {
    StunMessage msg;
    msg.setMessageType(ice::StunMessage::Binding);
    msg.setMessageClass(ice::StunMessage::RequestClass);
    msg.setTransactionId(mTransactionID);
    
    if (mUseCandidate)
      msg.setAttribute(new ICEUseCandidate());

    // Copy comment to message object
    msg.setComment(mComment);

    enqueueMessage(msg);
    
    mComposed = true;
  }

  return Transaction::generateData(force);
}

void ClientBinding::addUseCandidate()
{
  mUseCandidate = true;
}
  

//------------------------------ ServerBindingTransaction ---------------------------
ServerBinding::ServerBinding()
{
  setComment("ServerBinding");

  mGenerate400 = false;
  mGenerate487 = false;
  mRole = 0;
}

ServerBinding::~ServerBinding()
{
}
void ServerBinding::setLocalTieBreaker(std::string tieBreaker)
{
  mLocalTieBreaker = tieBreaker;
}

bool ServerBinding::processData(StunMessage& msg, NetworkAddress& address)
{
  if (msg.messageType() != StunMessage::Binding || msg.messageClass() != StunMessage::RequestClass)
    return false;
  ICELogDebug(<< "Received Binding request from " << address.toStdString().c_str());
  
  // Save visible address
  mSourceAddress = address;
    
  // Save transaction ID
  mTransactionID = msg.transactionId();
    
  // Save Priority value 
  if (msg.hasAttribute(StunAttribute::ICEPriority))
  {
    mEnablePriority = true;
    mPriority = msg.icePriorityAttr().priority();
  }

  // Save UseCandidate flag
  mUseCandidate = msg.hasAttribute(StunAttribute::ICEUseCandidate);

  // Check if auth credentials are needed
  mGenerate400 = false; //do not send 400 response by default
  mGenerate487 = false;
  
  if (!msg.hasAttribute(StunAttribute::Username) || !msg.hasAttribute(StunAttribute::MessageIntegrity))
  {
    ICELogError(<< "There is no Username or MessageIntegrity attributes in Binding required. Error 400 will be generated.");
    // Send 400 error
    mGenerate400 = true;

    return true;
  }

  // Check for role
  if (msg.hasAttribute(StunAttribute::ControlledAttr))
  {
    mRemoteTieBreaker = msg.iceControlledAttr().tieBreaker();
    mRole = 1; // Session::Controlled;
  }
  else
  if (msg.hasAttribute(StunAttribute::ControllingAttr))
  {
    mRemoteTieBreaker = msg.iceControllingAttr().tieBreaker();
    mRole = 2;// Session::Controlling;
  }

  return true;
}


ByteBuffer* ServerBinding::generateData(bool force)
{
  // Restart timer
  mRTOTimer.stop();
  mRTOTimer.start();
  
  // See if remote password / username are set
  if (mPassword.empty() || mUsername.empty())
    return NULL;

  StunMessage msg;
  
  // Set transaction ID the same as processed incoming message
  msg.setTransactionId(mTransactionID);
  msg.setMessageType(StunMessage::Binding);
  
  if (mGenerate400)
  {
    // Generate bad request error
    msg.setMessageClass(StunMessage::ErrorClass);
    
    msg.errorCodeAttr().setErrorCode(400);
    msg.errorCodeAttr().setErrorPhrase("Bad request");
  }
  else
  if (mGenerate487)
  {
    // Generate 487 error
    msg.setMessageClass(StunMessage::ErrorClass);
    msg.errorCodeAttr().setErrorCode(487);
    msg.errorCodeAttr().setErrorPhrase("Role conflict");
  }
  else
  {
    msg.setMessageClass(StunMessage::SuccessClass);
    
    msg.mappedAddressAttr().address() = mSourceAddress;
    msg.xorMappedAddressAttr().address() = mSourceAddress;
  }

  // Build message
    
  // Clear outgoing buffer
  mOutgoingData.clear();
  
  // Check if message should be secured by message integrity
  //std::string password;
  if (!mGenerate400 && !mGenerate487)
  {
    // Do not create username attribute here - response does not need it
    // msg.usernameAttr().setValue(mUsername);
    
    //ICELogMedia(<< "Using password " << mPassword);

    // Add message integrity attribute
    msg.setAttribute(new MessageIntegrity());
    
    if (mFingerprint)
      msg.setAttribute(new Fingerprint());
  
    // Add ICE-CONTROLLED attribute if needed
    if (mEnableControlled)
      msg.iceControlledAttr().setTieBreaker(mLocalTieBreaker);

    // Add ICE-CONTROLLING attribute if needed
    if (mEnableControlling)
      msg.iceControllingAttr().setTieBreaker(mLocalTieBreaker);

    // Add ICE-PRIORITY attribute if needed
    if (mEnablePriority)
      msg.icePriorityAttr().setPriority(mPriority);
  }

  // Build packet
  msg.buildPacket(mOutgoingData, mPassword);
  
  // Copy comment
  msg.setComment(mComment);

  mComposed = true;
  
  return new ByteBuffer(mOutgoingData);
}

bool ServerBinding::hasUseCandidate()
{
  return mUseCandidate;
}

bool ServerBinding::gotRequest()
{
  return !mGenerate400 && !mGenerate487;
}

int ServerBinding::role()
{
  return mRole;
}

void ServerBinding::generate487()
{
  mGenerate487 = true;
}

std::string ServerBinding::remoteTieBreaker()
{
  return mRemoteTieBreaker;
}

void ServerBinding::restart()
{
}

//-------------------- BindingIndication ---------------
BindingIndication::BindingIndication(unsigned int interval)
:mInterval(interval)
{
  setComment("BindingIndication");
}

BindingIndication::~BindingIndication()
{
}

bool BindingIndication::processData(StunMessage& msg, NetworkAddress& address)
{
  return (msg.messageClass() == StunMessage::IndicationClass &&
          msg.messageType() == StunMessage::Binding);
}


ByteBuffer* BindingIndication::generateData(bool force)
{
  if (!mComposed)
  {
    StunMessage msg;
    msg.setMessageClass(StunMessage::IndicationClass);
    msg.setMessageType(StunMessage::Binding);
    msg.setTransactionId(mTransactionID);
    msg.setComment(mComment);
    enqueueMessage(msg);
    mComposed = true;
  }

  return Transaction::generateData(force);
}

