// -*- C++ -*-
//
// CDR_Encaps_Codec.cpp,v 1.17 2003/11/04 05:21:33 dhinton Exp

#include "CDR_Encaps_Codec.h"

#include "CDR.h"
#include "OctetSeqC.h"
#include "Any.h"
#include "Any_Impl.h"
#include "Typecode.h"
#include "Marshal.h"
#include "Any_Unknown_IDL_Type.h"
#include "ORB_Constants.h"
#include "ace/OS_NS_string.h"

ACE_RCSID (TAO_CodecFactory,
           CDR_Encaps_Codec,
           "CDR_Encaps_Codec.cpp,v 1.17 2003/11/04 05:21:33 dhinton Exp")


TAO_CDR_Encaps_Codec::TAO_CDR_Encaps_Codec (CORBA::Octet major,
                                            CORBA::Octet minor,
                                            TAO_ORB_Core * orb_core)
  : major_ (major),
    minor_ (minor),
    orb_core_ (orb_core)
{
}

TAO_CDR_Encaps_Codec::~TAO_CDR_Encaps_Codec (void)
{
}

CORBA::OctetSeq *
TAO_CDR_Encaps_Codec::encode (const CORBA::Any & data
                              ACE_ENV_ARG_DECL)
  ACE_THROW_SPEC ((CORBA::SystemException,
                   IOP::Codec::InvalidTypeForEncoding))
{
  this->check_type_for_encoding (data
                                 ACE_ENV_ARG_PARAMETER);
  ACE_CHECK_RETURN (0);

  // ----------------------------------------------------------------

  TAO_OutputCDR cdr ((size_t) 0,            // size
                     (int) TAO_ENCAP_BYTE_ORDER,
                     (ACE_Allocator *) 0,   // buffer_allocator
                     (ACE_Allocator *) 0,   // data_block_allocator
                     (ACE_Allocator *) 0,   // message_block_allocator
                     0,                     // memcpy_tradeoff
                     this->major_,
                     this->minor_);

  if ((cdr << TAO_OutputCDR::from_boolean (TAO_ENCAP_BYTE_ORDER))
      && (cdr << data))
    {
      CORBA::OctetSeq * octet_seq = 0;

      ACE_NEW_THROW_EX (octet_seq,
                        CORBA::OctetSeq,
                        CORBA::NO_MEMORY (
                          CORBA::SystemException::_tao_minor_code (
                            TAO_DEFAULT_MINOR_CODE,
                            ENOMEM),
                          CORBA::COMPLETED_NO));
      ACE_CHECK_RETURN (0);

      CORBA::OctetSeq_var safe_octet_seq = octet_seq;

      octet_seq->length (ACE_static_cast (CORBA::ULong, cdr.total_length ()));
      CORBA::Octet *buf = octet_seq->get_buffer ();

      for (const ACE_Message_Block *i = cdr.begin ();
           i != 0;
           i = i->cont ())
        {
          size_t len = i->length ();
          ACE_OS::memcpy (buf, i->rd_ptr (), len);
          buf += len;
        }

      return safe_octet_seq._retn ();
    }

  ACE_THROW_RETURN (CORBA::MARSHAL (), 0);
}

CORBA::Any *
TAO_CDR_Encaps_Codec::decode (const CORBA::OctetSeq & data
                              ACE_ENV_ARG_DECL)
  ACE_THROW_SPEC ((CORBA::SystemException,
                   IOP::Codec::FormatMismatch))
{
  // @todo How do we check for a format mismatch so that we can throw
  //       a IOP::Codec::FormatMismatch exception?
  // @todo Is this the best way to extract the Any from the OctetSeq?

  // Notice that we need to extract the TypeCode and the value from
  // the octet sequence, and place them into the Any.  We can't just
  // insert the octet sequence into the Any.

  ACE_Message_Block mb (data.length () + 2 * ACE_CDR::MAX_ALIGNMENT);
  ACE_CDR::mb_align (&mb);

  ACE_OS::memcpy (mb.rd_ptr (), data.get_buffer (), data.length ());

  size_t rd_pos = mb.rd_ptr () - mb.base ();
  size_t wr_pos = mb.wr_ptr () - mb.base () + data.length ();

  TAO_InputCDR cdr (mb.data_block (),
                    ACE_Message_Block::DONT_DELETE,
                    rd_pos,
                    wr_pos,
                    ACE_CDR_BYTE_ORDER,
                    this->major_,
                    this->minor_,
                    this->orb_core_);

  CORBA::Boolean byte_order;
  if (cdr >> TAO_InputCDR::to_boolean (byte_order))
    {
      cdr.reset_byte_order (ACE_static_cast (int, byte_order));

      CORBA::Any * any = 0;
      ACE_NEW_THROW_EX (any,
                        CORBA::Any,
                        CORBA::NO_MEMORY (
                          CORBA::SystemException::_tao_minor_code (
                            TAO_DEFAULT_MINOR_CODE,
                            ENOMEM),
                          CORBA::COMPLETED_NO));
      ACE_CHECK_RETURN (0);

      CORBA::Any_var safe_any = any;

      if (cdr >> (*any))
        return safe_any._retn ();
    }

  ACE_THROW_RETURN (IOP::Codec::FormatMismatch (),
                    0);
}

CORBA::OctetSeq *
TAO_CDR_Encaps_Codec::encode_value (const CORBA::Any & data
                                    ACE_ENV_ARG_DECL)
  ACE_THROW_SPEC ((CORBA::SystemException,
                   IOP::Codec::InvalidTypeForEncoding))
{
  this->check_type_for_encoding (data
                                 ACE_ENV_ARG_PARAMETER);
  ACE_CHECK_RETURN (0);

  // ----------------------------------------------------------------
  TAO_OutputCDR cdr ((size_t) 0,            // size
                     (int) TAO_ENCAP_BYTE_ORDER,
                     (ACE_Allocator *) 0,   // buffer_allocator
                     (ACE_Allocator *) 0,   // data_block_allocator
                     (ACE_Allocator *) 0,   // message_block_allocator
                     0,                     // memcpy_tradeoff
                     this->major_,
                     this->minor_);

  if ((cdr << TAO_OutputCDR::from_boolean (TAO_ENCAP_BYTE_ORDER)))
    {
      ACE_Message_Block * mb = data._tao_get_cdr ();

      if (mb == 0)
        {
          ACE_NEW_THROW_EX (mb,
                            ACE_Message_Block,
                            CORBA::NO_MEMORY ());
          ACE_CHECK_RETURN (0);

          TAO_OutputCDR out;
          CORBA::Any any (data);
          any.impl ()->marshal_value (out);

          ACE_CDR::consolidate (mb, out.begin ());
        }

      TAO_InputCDR input (mb,
                          data._tao_byte_order (),
                          this->major_,
                          this->minor_,
                          this->orb_core_);

      TAO_Marshal_Object::perform_append (data._tao_get_typecode (),
                                          &input,
                                          &cdr
                                          ACE_ENV_ARG_PARAMETER);
      ACE_CHECK_RETURN (0);

      // TAO extension: replace the contents of the octet sequence with
      // the CDR stream.
      CORBA::OctetSeq * octet_seq = 0;

      ACE_NEW_THROW_EX (octet_seq,
                        CORBA::OctetSeq,
                        CORBA::NO_MEMORY (
                            CORBA::SystemException::_tao_minor_code (
                                TAO_DEFAULT_MINOR_CODE,
                                ENOMEM
                              ),
                            CORBA::COMPLETED_NO
                          ));
      ACE_CHECK_RETURN (0);

      CORBA::OctetSeq_var safe_octet_seq = octet_seq;

      octet_seq->length (ACE_static_cast (CORBA::ULong, cdr.total_length ()));
      CORBA::Octet *buf = octet_seq->get_buffer ();

      for (const ACE_Message_Block *i = cdr.begin ();
           i != 0;
           i = i->cont ())
        {
          size_t len = i->length ();
          ACE_OS::memcpy (buf,
                          i->rd_ptr (),
                          len);
          buf += len;
        }

      return safe_octet_seq._retn ();
    }

  ACE_THROW_RETURN (CORBA::MARSHAL (),
                    0);
}

CORBA::Any *
TAO_CDR_Encaps_Codec::decode_value (const CORBA::OctetSeq & data,
                                    CORBA::TypeCode_ptr tc
                                    ACE_ENV_ARG_DECL)
  ACE_THROW_SPEC ((CORBA::SystemException,
                   IOP::Codec::FormatMismatch,
                   IOP::Codec::TypeMismatch))
{
  // The ACE_CDR::mb_align() call can shift the rd_ptr by up
  // to ACE_CDR::MAX_ALIGNMENT-1 bytes. Similarly, the offset
  // adjustment can move the rd_ptr by up to the same amount.
  // We accommodate this by including
  // 2 * ACE_CDR::MAX_ALIGNMENT bytes of additional space in
  // the message block.
  ACE_Message_Block mb (data.length () + 2 * ACE_CDR::MAX_ALIGNMENT);
  ACE_CDR::mb_align (&mb);

  ACE_OS::memcpy (mb.rd_ptr (),
                  data.get_buffer (),
                  data.length ());

  // @todo How do we check for a type mismatch so that we can
  //       throw a IOP::Codec::TypeMismatch exception?
  //       @@ I added a check below.  See the comment.  I'm not sure
  //          if it is a valid check.
  //             -Ossama

  // @todo Most of this code was copied from
  //         operator>> (TAO_InputCDR &cdr, CORBA::Any &x)
  //       in Any.cpp.  Rather than copy the code, the code should be
  //       refactored to make it possible to use the given TypeCode
  //       rather than attempt to extract it from the CDR
  //       encapsulation.

  CORBA::ULong sequence_length = data.length ();

  size_t rd_pos = mb.rd_ptr () - mb.base ();
  size_t wr_pos = mb.wr_ptr () - mb.base () + data.length ();

  TAO_InputCDR cdr (mb.data_block (),
                    ACE_Message_Block::DONT_DELETE,
                    rd_pos,
                    wr_pos,
                    ACE_CDR_BYTE_ORDER,
                    this->major_,
                    this->minor_,
                    this->orb_core_);

  CORBA::Boolean byte_order;

  if (cdr >> TAO_InputCDR::to_boolean (byte_order))
    {
      cdr.reset_byte_order (ACE_static_cast (int, byte_order));

      // @@ (JP) The following code depends on the fact that
      //         TAO_InputCDR does not contain chained message blocks,
      //         otherwise <begin> and <end> could be part of
      //         different buffers!

      // This will be the start of a new message block.
      char *begin = cdr.rd_ptr ();

      // Skip over the next argument.
      TAO::traverse_status status =
        TAO_Marshal_Object::perform_skip (tc,
                                          &cdr
                                          ACE_ENV_ARG_PARAMETER);
      ACE_CHECK_RETURN (0);  // @@ Should we throw a
                             //    IOP::Codec::TypeMismatch exception
                             //    here if this fails?

      if (status == TAO::TRAVERSE_CONTINUE)
        {
          // This will be the end of the new message block.
          char *end = cdr.rd_ptr ();
          size_t size = end - begin;

          // @@ I added the following check, but I'm not sure if it is
          //    a valid check.  Can someone verify this?
          //          -Ossama

          // If the unaligned buffer size is not equal to the octet
          // sequence length (minus the "byte order byte") then the
          // TypeCode does not correspond to the data in the CDR
          // encapsulation.  However, even if they do match it is
          // still uncertain if the TypeCode corresponds to the data
          // in the octet sequence.  With this test, it is only
          // possible to determine if the TypeCode does *not* match
          // the data, not if it does match.
          if (size != sequence_length - 1)
            {
              ACE_THROW_RETURN (IOP::Codec::TypeMismatch (),
                                0);
            }

          ptrdiff_t offset =
            ptrdiff_t (begin) % ACE_CDR::MAX_ALIGNMENT;
          if (offset < 0)
            offset += ACE_CDR::MAX_ALIGNMENT;

          mb.rd_ptr (offset);
          mb.wr_ptr (offset + size);

          CORBA::Any * any = 0;
          ACE_NEW_THROW_EX (any,
                            CORBA::Any,
                            CORBA::NO_MEMORY (
                                CORBA::SystemException::_tao_minor_code (
                                    TAO_DEFAULT_MINOR_CODE,
                                    ENOMEM
                                  ),
                                CORBA::COMPLETED_NO
                              ));
          ACE_CHECK_RETURN (0);

          CORBA::Any_var safe_any = any;

          // Stick it into the Any.
          TAO::Unknown_IDL_Type *unk = 0;
          ACE_NEW_RETURN (unk,
                          TAO::Unknown_IDL_Type (tc,
                                                 &mb,
                                                 cdr.byte_order ()),
                          0);
          any->replace (unk);
          return safe_any._retn ();
        }
      else
        {
          ACE_THROW_RETURN (IOP::Codec::TypeMismatch (),
                            0);
        }
    }

  ACE_THROW_RETURN (IOP::Codec::FormatMismatch (),
                    0);
}

void
TAO_CDR_Encaps_Codec::check_type_for_encoding (
    const CORBA::Any & data
    ACE_ENV_ARG_DECL
  )
{
  // @@ TODO: Are there any other conditions we need to check?

  CORBA::TypeCode_var typecode = data.type ();
  if (this->major_ == 1
      && this->minor_ == 0
      && typecode->equivalent (CORBA::_tc_wstring))
    ACE_THROW (IOP::Codec::InvalidTypeForEncoding ());
}
