/*
 *
 *  Copyright (C) 1997-2021, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 * Author: Michael Onken
 *
 * Module: dcmnet
 *
 * Purpose:
 *   User Identity Negotiation for A-ASSOCIATE (Supp. 99)
 *
 */


#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
#include "dcmtk/dcmnet/dcuserid.h"
#include "dcmtk/dcmnet/dul.h"
#include "dcmtk/dcmnet/dulstruc.h"

/* ************************************************************************* */
/*       Implementation of class UserIdentityNegotiationSubItem              */
/* ************************************************************************* */

// Constructor, sets constants
UserIdentityNegotiationSubItem::UserIdentityNegotiationSubItem(const unsigned char itemType) :
  m_itemType (itemType),
  m_reserved(0)
{
}

/* ************************************************************************* */
/*     Implementation of class UserIdentityNegotiationSubItemRQ              */
/* ************************************************************************* */

// Constructor, constructs empty Identity Negotiation structure
UserIdentityNegotiationSubItemRQ::UserIdentityNegotiationSubItemRQ() :
    UserIdentityNegotiationSubItem(DUL_TYPENEGOTIATIONOFUSERIDENTITY_REQ),
    m_userIdentityType(ASC_USER_IDENTITY_NONE)
  , m_posRspRequested(0)
  , m_primField(NULL)
  , m_primFieldLength(0)
  , m_secField(NULL)
  , m_secFieldLength(0)
{
}


// Returns that this subitem is part of a request
unsigned char UserIdentityNegotiationSubItemRQ::pduType() const
{
  return DUL_TYPEASSOCIATERQ;
}


// Clears all member variables and frees memory
void UserIdentityNegotiationSubItemRQ::clear()
{
  m_userIdentityType = ASC_USER_IDENTITY_NONE;
  m_posRspRequested = 0;
  if (m_primField != NULL)
  {
    delete[] m_primField; m_primField = NULL;
  }
  m_primFieldLength = 0;
  if (m_secField != NULL)
  {
    delete[] m_secField; m_secField = NULL;
  }
  m_secFieldLength = 0;
}


// Sets identification mode
void UserIdentityNegotiationSubItemRQ::setIdentityType(const T_ASC_UserIdentityNegotiationMode& mode)
{
  m_userIdentityType = mode;
}


// Returns identity type
T_ASC_UserIdentityNegotiationMode
UserIdentityNegotiationSubItemRQ::getIdentityType()
{
  return m_userIdentityType;
}


// Sets primary field (copied from parameter)
void UserIdentityNegotiationSubItemRQ::setPrimField(const char *buffer,
                                                    const Uint16 length)
{
  if (m_primField != NULL)
  {
    delete[] m_primField;
    m_primField = NULL;
  }
  m_primFieldLength = length;
  if ((length == 0) || (buffer == NULL))
    return;

  m_primField = new char[length];
  memcpy(m_primField, buffer, length);
}


// Sets secondary field (copied form parameter)
void UserIdentityNegotiationSubItemRQ::setSecField(const char *buffer,
                                                   const Uint16 length)
{
  if (m_secField != NULL)
  {
    delete[] m_secField;
    m_secField = NULL;
  }
  m_secFieldLength = length;
  if ((length == 0) || (buffer == NULL))
    return;

  m_secField = new char[length];
  memcpy(m_secField, buffer, length);
}


// Returns primary field. Memory must be freed by caller.
Uint16 UserIdentityNegotiationSubItemRQ::getPrimField(char*& resultBuf,
                                                      Uint16& resultLen) const
{
  if ((m_primFieldLength == 0) || (m_primField == NULL))
  {
    resultBuf = NULL;
    resultLen = 0;
    return 0;
  }
  resultBuf = new char[m_primFieldLength];
  memcpy(resultBuf, m_primField, m_primFieldLength);
  resultLen = m_primFieldLength;
  return resultLen;
}


// Returns secondary field. Memory must be freed by caller.
unsigned short UserIdentityNegotiationSubItemRQ::getSecField(char*& resultBuf,
                                                             Uint16& resultLen) const
{
  if ((m_secFieldLength == 0) || (m_secField == NULL))
  {
    resultBuf = NULL;
    resultLen = 0;
    return 0;
  }
  resultBuf = new char[m_secFieldLength];
  memcpy(resultBuf, m_secField, m_secFieldLength);
  resultLen = m_secFieldLength;
  return resultLen;
}


// Enables/disables requesting of a positive identity response
void UserIdentityNegotiationSubItemRQ::setReqPosResponse(const OFBool& reqPosRsp)
{
  m_posRspRequested = reqPosRsp ? 1 : 0;
}


// Stream the package into a byte stream for network transmission
OFCondition UserIdentityNegotiationSubItemRQ::stream(unsigned char *targetBuffer,
                                                     unsigned long& lengthWritten) const
{
  // recompute the length fields
  streamedLength(lengthWritten);
  *targetBuffer++ = getItemType();
  *targetBuffer++ = getReserved();
  /* item length = total length - 1 -1 -2 */
  COPY_SHORT_BIG(OFstatic_cast(unsigned short, lengthWritten - 4), targetBuffer);
  targetBuffer += 2;

  *targetBuffer++ = OFstatic_cast(unsigned char, m_userIdentityType);
  *targetBuffer++ = m_posRspRequested;

  COPY_SHORT_BIG(m_primFieldLength, targetBuffer);
  targetBuffer += 2;

  (void) memcpy(targetBuffer, m_primField, m_primFieldLength);
  targetBuffer += m_primFieldLength;

  COPY_SHORT_BIG(m_secFieldLength, targetBuffer);
  targetBuffer += 2;

  // If length is equal to zero, nothing is copied.
  (void) memcpy(targetBuffer, m_secField, m_secFieldLength);
  targetBuffer += m_secFieldLength;

  return EC_Normal;
}


// Calculates length values and returns total length of the user item if streamed
OFCondition UserIdentityNegotiationSubItemRQ::streamedLength(unsigned long& length) const
{
  // content length = userIdentityType + positive Response Requested + primary field length field + primary field length + 2nd field length (2) (+ 2nd field)
  length = 1 + 1 + 2 + m_primFieldLength;
  length += 2;
  if (m_userIdentityType == ASC_USER_IDENTITY_USER_PASSWORD)
  {
    length += m_secFieldLength;
  }

  // sum up length of item (item type + reserved + item length field + content length) to get total length
  length += 1 + 1 + 2;

  return EC_Normal;
}


// Parse User Identity request from read buffer. Buffer must start with item type field.
OFCondition UserIdentityNegotiationSubItemRQ::parseFromBuffer(unsigned char *readBuffer,
                                                              unsigned long &bytesRead,
                                                              unsigned long availData)
{
  /* BYTE POSITIONS
   * 0       Item Type
   * 1       Reserved (=0)
   * 2-3     Item Length
   * 4       User Identity Type
   * 5       Positive Response Requested
   * 6-7     Primary-field length
   * 8-n     Primary-field
   * n+1-n+2 Secondary-field length
   * n+3-m   Secondary-field
   */

  // If n and m are both 0, this takes 10 bytes (absolute minimum)
  if (availData < 10)
  {
      char buffer[256];
      sprintf(buffer, "DUL user identity rq length %ld. Need at least 10 bytes", availData);
      return makeDcmnetCondition(DULC_ILLEGALPDULENGTH, OF_error, buffer);
  }

  bytesRead = 0;
  // Skip "item type" and "reserved" field
  readBuffer += 2;
  // Parse total item content length
  unsigned short itemLength = 0;
  EXTRACT_SHORT_BIG(readBuffer, itemLength);
  readBuffer += 2;

  // Is itemLength larger than the available data from readBuffer?
  if (availData - 4 < itemLength)
  {
      char buffer[256];
      sprintf(buffer, "DUL illegal user identify rq length %ld. Info claims to be %hd bytes.",
              availData, itemLength);
      return makeDcmnetCondition(DULC_ILLEGALPDULENGTH, OF_error, buffer);
  }

  // Do some length checking
  if (itemLength < 6 ) // at least 6 bytes are mandatory: identity type(1) pos. response(1) primLength(2), secfieldlength(2)
    return EC_CorruptedData;

  // Parse user identity type
  const unsigned char identType = *readBuffer;
  if ((identType < 1) || (identType > ASC_USER_IDENTITY_MAX_VALUE))
    m_userIdentityType = ASC_USER_IDENTITY_UNKNOWN;
  else
    m_userIdentityType = OFstatic_cast(T_ASC_UserIdentityNegotiationMode, identType);
  readBuffer++;

  // Parse Positive Response Requested field
  m_posRspRequested = *readBuffer;
  readBuffer++;

  // Parse length of primary field
  EXTRACT_SHORT_BIG(readBuffer, m_primFieldLength);
  readBuffer += 2;

  // 4 bytes read so far and 2 bytes will be read after this sub-field
  if (itemLength - 4 - 2 < m_primFieldLength)
  {
      char buffer[256];
      sprintf(buffer, "DUL illegal user identify rq length %ld. Info claims to be %hd bytes. "
              "Primary field has %hd bytes.", availData, itemLength, m_primFieldLength);
      return makeDcmnetCondition(DULC_ILLEGALPDULENGTH, OF_error, buffer);
  }
  if (m_primFieldLength > 0)
  {
    m_primField = new char[m_primFieldLength];
    memcpy(m_primField, readBuffer, m_primFieldLength);
  }

  readBuffer += m_primFieldLength;
  bytesRead = 8 + m_primFieldLength; // 2+2+1+1+2+primary field length

  EXTRACT_SHORT_BIG(readBuffer, m_secFieldLength);
  readBuffer += 2;
  bytesRead += 2;

  // 6 + m_primField bytes read so far, trying to read m_secFieldLength more bytes
  if (itemLength - 6 - m_primFieldLength < m_secFieldLength)
  {
      char buffer[256];
      sprintf(buffer, "DUL illegal user identify rq length %ld. Info claims to be %hd bytes. "
              "Primary field has %hd bytes. Secondary field has %hd bytes.",
              availData, itemLength, m_primFieldLength, m_secFieldLength);
      return makeDcmnetCondition(DULC_ILLEGALPDULENGTH, OF_error, buffer);
  }

  // only user identity type 2 (Username + password authentication) requires second value field
  if ( (m_userIdentityType == ASC_USER_IDENTITY_USER_PASSWORD) && (m_secFieldLength > 0) )
  {
    m_secField = new char[m_secFieldLength];
    memcpy(m_secField, readBuffer, m_secFieldLength);
    //m_secField.append((const char*)readBuffer, m_secFieldLength);
    readBuffer += m_secFieldLength;
    bytesRead += m_secFieldLength;
  }

  return EC_Normal;
}


// Dump User Identity sub item to specified output stream
void UserIdentityNegotiationSubItemRQ::dump(STD_NAMESPACE ostream& outstream) const
{
  switch (m_userIdentityType)
  {
    case ASC_USER_IDENTITY_NONE:
       outstream << "none";
    break;
    case ASC_USER_IDENTITY_USER:
       outstream << "  Authentication mode 1: Username" << OFendl;
       if (m_primFieldLength > 0)
       {
         char* buf = new char[m_primFieldLength+1];
         strncpy(buf, OFstatic_cast(const char*, m_primField), m_primFieldLength);
         buf[m_primFieldLength] = '\0';
         outstream << "  Username: [" << buf << "]" << OFendl;
         delete[] buf;
       }
       else
         outstream << "  Username: []" << OFendl;
    break;
    case ASC_USER_IDENTITY_USER_PASSWORD:
       outstream << "  Authentication mode 2: Username/Password" << OFendl;
       if (m_primFieldLength > 0)
       {
         char* buf = new char[m_primFieldLength+1];
         strncpy(buf, OFstatic_cast(const char*, m_primField), m_primFieldLength);
         buf[m_primFieldLength] = '\0';
         outstream << "  Username: [" << buf << "]" << OFendl;
         delete[] buf;
       }
       else
         outstream << "  Username: []" << OFendl;
       if (m_secFieldLength > 0)
       {
         char* buf = new char[m_secFieldLength+1];
         strncpy(buf, OFstatic_cast(const char*, m_secField), m_secFieldLength);
         buf[m_secFieldLength] = '\0';
         outstream << "  Password: [" << buf << "]" << OFendl;
         delete[] buf;
       }
       else
         outstream << "  Password: []" << OFendl;
      break;
    case ASC_USER_IDENTITY_KERBEROS:
      outstream << "  Authentication mode 3: Kerberos" << OFendl <<
        "  Kerberos Service Ticket (not dumped) length: "<< m_primFieldLength << OFendl;
      break;
    case ASC_USER_IDENTITY_SAML:
      outstream << "  Authentication mode 4: SAML" << OFendl <<
        "  SAML Assertion (not dumped) length: " << m_primFieldLength << OFendl;
      break;
    case ASC_USER_IDENTITY_JWT:
      outstream << "  Authentication mode 5: JWT" << OFendl <<
        "  JSON Web Token (not dumped) length: " << m_primFieldLength << OFendl;
      break;
    default:
      outstream << "  Authentication mode: Unknown " << OFendl <<
        "  First value (not dumped), length: " << m_primFieldLength << OFendl <<
        "  Second Value (not dumped), length: " << m_secFieldLength << OFendl;
  }
  outstream << "  Positive Response requested: " << ((m_posRspRequested != 0) ? "Yes" : "No") << OFendl << OFStringStream_ends;
}


// Assignment operator
UserIdentityNegotiationSubItemRQ& UserIdentityNegotiationSubItemRQ::operator= (const UserIdentityNegotiationSubItemRQ& rhs)
{
  this->clear();
  m_userIdentityType = rhs.m_userIdentityType;
  m_posRspRequested = rhs.m_posRspRequested;
  if (rhs.m_primFieldLength > 0)
  {
    if (rhs.getPrimField(m_primField, m_primFieldLength) != rhs.m_primFieldLength)
    {
      m_primField = NULL; m_primFieldLength = 0;
    }
  } else m_primFieldLength = 0;
  if ((rhs.m_secFieldLength > 0) && (rhs.m_userIdentityType == ASC_USER_IDENTITY_USER_PASSWORD))
  {
    if (rhs.getSecField(m_secField, m_secFieldLength) != rhs.m_secFieldLength)
    {
      m_secField = NULL; m_secFieldLength = 0;
    }
  } else m_secFieldLength = 0;
  return *this;
}


// Copy Constructor
UserIdentityNegotiationSubItemRQ::UserIdentityNegotiationSubItemRQ(const UserIdentityNegotiationSubItemRQ& rhs) :
  UserIdentityNegotiationSubItem(rhs),
  m_userIdentityType(ASC_USER_IDENTITY_NONE),
  m_posRspRequested(0),
  m_primField(NULL),
  m_primFieldLength(0),
  m_secField(NULL),
  m_secFieldLength(0)
{
  *this = rhs;
}


// Destructor, frees memory of primary field
UserIdentityNegotiationSubItemRQ::~UserIdentityNegotiationSubItemRQ()
{
  if ((m_primField != NULL) && (m_primFieldLength > 0))
  {
    delete[] m_primField; m_primField = NULL;
  }
  m_primFieldLength = 0;
  if ((m_secField != NULL) && (m_secFieldLength > 0))
  {
    delete[] m_secField; m_secField = NULL;
  }
  m_secFieldLength = 0;
}


/* ************************************************************************* */
/*     Implementation of class UserIdentityNegotiationSubItemAC              */
/* ************************************************************************* */

// Constructor
UserIdentityNegotiationSubItemAC::UserIdentityNegotiationSubItemAC() :
  UserIdentityNegotiationSubItem(DUL_TYPENEGOTIATIONOFUSERIDENTITY_ACK),
  m_serverRsp(NULL),
  m_rspLength(0)
{
}


// Returns that this subitem is part of an acknowledge message
unsigned char UserIdentityNegotiationSubItemAC::pduType() const
{
  return DUL_TYPEASSOCIATEAC;
}

// Clear all data and free memory
void UserIdentityNegotiationSubItemAC::clear()
{
  if (m_serverRsp != NULL)
  {
    delete[] m_serverRsp; m_serverRsp = NULL;
  }
   m_rspLength = 0;
}


// Returns server response. Memory must be freed by caller.
Uint16
UserIdentityNegotiationSubItemAC::getServerResponse(char*& targetBuffer,
                                                    Uint16& resultLen) const
{
  if ((m_rspLength == 0) || (m_serverRsp == NULL))
  {
    targetBuffer = NULL;
    resultLen = 0;
    return 0;
  }
  targetBuffer = new char[m_rspLength];
  memcpy(targetBuffer, m_serverRsp, m_rspLength);
  resultLen = m_rspLength;
  return resultLen;
}

// Sets server response. Buffer from "rsp" is copied.
void
UserIdentityNegotiationSubItemAC::setServerResponse(const char* rsp,
                                                    const Uint16& rspLen)
{
  if (m_serverRsp != NULL)
  {
    delete[] m_serverRsp;
    m_serverRsp = NULL;
  }
  m_rspLength = rspLen;
  if ((rspLen == 0) || (rsp == NULL))
    return;

  m_serverRsp = new char[rspLen];
  memcpy(m_serverRsp, rsp, rspLen);
}


// Computes and returns total length of the user item if streamed
OFCondition UserIdentityNegotiationSubItemAC::streamedLength(unsigned long& length) const
{
  // content length: server response length field (2) + length of server response field
  length = 2 + m_rspLength;
   // total length: (item type + reserved + item length field + item content length)
  length = 1 + 1 + 2 + length;
  return EC_Normal;
}


/// Stream the package into a byte stream for network transmission
OFCondition UserIdentityNegotiationSubItemAC::stream(unsigned char *targetBuffer,
                                                     unsigned long& lengthWritten) const
{
  if (OFstatic_cast(unsigned long, m_rspLength) + 2 > (unsigned long)65535 /* for response length field */)
  {
    char errbuf[500];
    sprintf(errbuf, "Length of User Identity response (%lu bytes) exceeds upper limit of 65535 bytes", (unsigned long)m_rspLength + 2);
    return makeDcmnetCondition(ASCC_CODINGERROR, OF_error, errbuf);
  }
  // compute total length of item when streamed
  streamedLength(lengthWritten);

  *targetBuffer++ = getItemType();
  *targetBuffer++ = getReserved();
  // value for item length field is = length of response value + length of length field (2)
  COPY_SHORT_BIG(OFstatic_cast(unsigned short, 2 + m_rspLength), targetBuffer);
  targetBuffer += 2;

  COPY_SHORT_BIG(m_rspLength, targetBuffer);
  targetBuffer += 2;

  (void) memcpy(targetBuffer, m_serverRsp, m_rspLength);
  targetBuffer += m_rspLength;

  return EC_Normal;
}


// Parase User Identity sub item from buffer. Buffer has to start with item type field.
OFCondition UserIdentityNegotiationSubItemAC::parseFromBuffer(unsigned char *readBuffer,
                                                              unsigned long &bytesRead,
                                                              unsigned long availData)
{
  // We need at least 6 bytes for the fields that we always read
  if (availData < 6)
  {
      char buffer[256];
      sprintf(buffer, "DUL illegal user identify ac length %ld. Need at least 6 bytes for"
              " user identify ac", availData);
      return makeDcmnetCondition(DULC_ILLEGALPDULENGTH, OF_error, buffer);
  }

  bytesRead = 0;
  readBuffer += 2; // Skip "item type" and "reserved" field
  unsigned short itemLength = 0;
  EXTRACT_SHORT_BIG(readBuffer, itemLength);
  readBuffer += 2; // Skip "item length

  EXTRACT_SHORT_BIG(readBuffer, m_rspLength);
  readBuffer += 2;

  // Check if itemLength and m_rspLength are valid and we don't read past the
  // end of any buffer
  if (availData - 4 < itemLength || itemLength < 2 || itemLength - 2 < m_rspLength)
  {
      char buffer[256];
      sprintf(buffer, "DUL illegal user identify ac length %ld. Info claims to be %hd bytes. "
              "Response claims to be %hd bytes.", availData, itemLength, m_rspLength);
      return makeDcmnetCondition(DULC_ILLEGALPDULENGTH, OF_error, buffer);
  }

  if (m_rspLength > 0)
  {
    m_serverRsp = new char[m_rspLength];
    memcpy(m_serverRsp, readBuffer, m_rspLength);
    readBuffer += m_rspLength;
  }

  bytesRead = 6 + m_rspLength; // 2+2+2+ response length
  return EC_Normal;
}


// Dumps contents of this User Identity sub item to output stream
void UserIdentityNegotiationSubItemAC::dump(STD_NAMESPACE ostream& outstream) const
{
  outstream << "  Server Response (not dumped) length: " << m_rspLength << OFendl;
}


// Assignment operator
UserIdentityNegotiationSubItemAC& UserIdentityNegotiationSubItemAC::operator= (const UserIdentityNegotiationSubItemAC& rhs)
{
  this->clear();
  if (rhs.m_rspLength > 0)
  {
    if (rhs.getServerResponse(m_serverRsp, m_rspLength) != rhs.m_rspLength)
    {
      m_serverRsp = NULL; m_rspLength = 0;
    }
  } else m_rspLength = 0;
  return *this;
}


// Copy constructor
UserIdentityNegotiationSubItemAC::UserIdentityNegotiationSubItemAC(const UserIdentityNegotiationSubItemAC& rhs) :
  UserIdentityNegotiationSubItem(rhs),
  m_serverRsp(NULL),
  m_rspLength(0)
{
  *this = rhs;
}

// Destructor, frees some memory
UserIdentityNegotiationSubItemAC::~UserIdentityNegotiationSubItemAC()
{
  if ( (m_serverRsp != NULL) && (m_rspLength > 0) )
  {
    delete[] m_serverRsp; m_serverRsp = NULL;
  }
  m_rspLength = 0;
}
