/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.iiop.csiv2;

import java.net.InetAddress;
import java.io.UnsupportedEncodingException;

import org.omg.CORBA.Any;
import org.omg.CORBA.BAD_PARAM;
import org.omg.CORBA.MARSHAL;
import org.omg.CORBA.ORB;

import org.omg.IOP.Codec;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.IOP.TaggedComponent;

import org.omg.CSI.ITTAbsent;
import org.omg.CSI.ITTAnonymous;
import org.omg.CSI.ITTPrincipalName;
import org.omg.CSI.ITTX509CertChain;
import org.omg.CSI.ITTDistinguishedName;

import org.omg.CSIIOP.AS_ContextSec;
import org.omg.CSIIOP.CompoundSecMech;
import org.omg.CSIIOP.CompoundSecMechList;
import org.omg.CSIIOP.CompoundSecMechListHelper;
import org.omg.CSIIOP.Confidentiality;
import org.omg.CSIIOP.DetectMisordering;
import org.omg.CSIIOP.DetectReplay;
import org.omg.CSIIOP.EstablishTrustInClient;
import org.omg.CSIIOP.EstablishTrustInTarget;
import org.omg.CSIIOP.IdentityAssertion;
import org.omg.CSIIOP.Integrity;
import org.omg.CSIIOP.SAS_ContextSec;
import org.omg.CSIIOP.ServiceConfiguration;
import org.omg.CSIIOP.TAG_CSI_SEC_MECH_LIST;
import org.omg.CSIIOP.TAG_NULL_TAG;
import org.omg.CSIIOP.TAG_TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANSHelper;
import org.omg.CSIIOP.TransportAddress;

import org.omg.GSSUP.GSSUPMechOID;
import org.omg.GSSUP.InitialContextToken;
import org.omg.GSSUP.InitialContextTokenHelper;

import org.omg.PortableInterceptor.ClientRequestInfo;

import org.omg.SSLIOP.SSL;
import org.omg.SSLIOP.SSLHelper;
import org.omg.SSLIOP.TAG_SSL_SEC_TRANS;

import org.ietf.jgss.GSSException;
import org.ietf.jgss.Oid;

import org.jboss.metadata.IorSecurityConfigMetaData;
import org.jboss.metadata.IorSecurityConfigMetaData.TransportConfig;
import org.jboss.metadata.IorSecurityConfigMetaData.AsContext;
import org.jboss.metadata.IorSecurityConfigMetaData.SasContext;

import org.jboss.logging.Logger;

/**
 * Helper class
 *
 * @author  Dimitris.Andreadis@jboss.org
 * @author  Francisco Reverbel
 * @author  Stefan Neusatz Guilhen
 * @version $Revision: 57323 $
 */
public final class CSIv2Util
{
   private static final Logger log = Logger.getLogger(CSIv2Util.class);

   /** DER-encoded ASN.1 representation of the GSSUP mechanism OID. */
   private static final byte[] gssUpMechOidArray = createGSSUPMechOID();

   private CSIv2Util()
   {
      // static calls only
   }

   /**
    * Make a deep copy of an IOP:TaggedComponent
    **/
   public static TaggedComponent createCopy(TaggedComponent tc)
   {
      TaggedComponent copy = null;

      if( tc != null )
      {
         byte[] buf = new byte[tc.component_data.length];
         System.arraycopy(tc.component_data, 0, buf, 0, tc.component_data.length);

         copy = new TaggedComponent(tc.tag, buf);
      }

      return copy;
   }

   /**
    * Return a top-level IOP::TaggedComponent to be stuffed into an IOR,
    * containing an structure SSLIOP::SSL, tagged as TAG_SSL_SEC_TRANS.
    *
    * Should be called with non-null metadata, in which case we probably
    * don't want to include security info in the IOR.
    **/
   public static TaggedComponent createSSLTaggedComponent(
      IorSecurityConfigMetaData metadata,
      Codec codec,
      int sslPort,
      ORB orb)
   {
      if( metadata == null )
      {
         log.debug("createSSLTaggedComponent() called with null metadata");
         return null;
      }

      TaggedComponent tc = null;

      try
      {
         int supports = createTargetSupports(metadata.getTransportConfig());
         int requires = createTargetRequires(metadata.getTransportConfig());
         SSL ssl = new SSL((short) supports, (short) requires, (short) sslPort);
         Any any = orb.create_any();
         SSLHelper.insert(any, ssl);
         byte[] componentData = codec.encode_value(any);
         tc = new TaggedComponent(TAG_SSL_SEC_TRANS.value, componentData);
      }
      catch(InvalidTypeForEncoding e)
      {
         log.warn("Caught unexcepted exception while encoding SSL component", e);
         throw new RuntimeException(e);
      }
      return tc;
   }

   /**
    * Return a top-level IOP:TaggedComponent to be stuffed into an IOR,
    * containing a CSIIOP.CompoundSecMechList, tagged as TAG_CSI_SEC_MECH_LIST.
    * Only one such component can exist inside an IOR.
    *
    * Should be called with non-null metadata, in which case we probably
    * don't want to include security info in the IOR.
    **/
   public static TaggedComponent createSecurityTaggedComponent(
      IorSecurityConfigMetaData metadata,
      Codec codec,
      int sslPort,
      ORB orb
      )
   {
      if( metadata == null )
      {
         log.debug("createSecurityTaggedComponent() called with null metadata");
         return null;
      }

      TaggedComponent tc = null;
      
      // Get the the supported security mechanisms (just one :)
      CompoundSecMech[] mechList =
         createCompoundSecMechanisms(metadata, codec, sslPort, orb);
         
      // the above is wrapped into a CSIIOP.CompoundSecMechList
      // structure, which is NOT a CompoundSecMech[] !!!
      //
      //  We DONT support stateful/reusable security contexts (false)
      CompoundSecMechList csmList = new CompoundSecMechList(false,
         mechList);
      // finally, the CompoundSecMechList must be encoded as a TaggedComponent
      try
      {
         Any any = orb.create_any();

         CompoundSecMechListHelper.insert(any, csmList);
         byte[] b = codec.encode_value(any);

         tc = new TaggedComponent(TAG_CSI_SEC_MECH_LIST.value, b);
      }
      catch(InvalidTypeForEncoding e)
      {
         log.warn("Caught unexcepted exception while encoding CompoundSecMechList", e);
         throw new RuntimeException(e);
      }

      return tc;
   }

   /**
    * Create a CSIIOP.CompoundSecMechanisms which is a sequence of
    * CompoundSecMech. Here we only support one security mechanism.
    **/
   public static CompoundSecMech[] createCompoundSecMechanisms(
      IorSecurityConfigMetaData metadata,
      Codec codec,
      int sslPort,
      ORB orb
      )
   {
      // Support just 1 security mechanism for now (and ever :)
      CompoundSecMech[] csmList = new CompoundSecMech[1];
      
      // A CompoundSecMech contains:
      // target_requires, transport_mech, as_context_mech, sas_context_mech
      
      // The transport mechanism is an IOP.TaggedComponent itself
      // to allow for arbitrary transport mechanisms be specified.
      // This will configure SSL or none.
      TaggedComponent transport_mech = createTransportMech(
         metadata.getTransportConfig(), codec, sslPort, orb
      );
      
      // Create AS Context
      AS_ContextSec asContext = createAuthenticationServiceContext(metadata);
      
      // Create SAS Context
      SAS_ContextSec sasContext = createSecureAttributeServiceContext(metadata);

      // Create target_requires bit field (AssociationOption)
      // can't read directly the transport_mech TaggedComponent
      int target_requires =
         createTargetRequires(metadata.getTransportConfig()) |
         asContext.target_requires |
         sasContext.target_requires;

      // wrap it up
      CompoundSecMech csm = new CompoundSecMech((short) target_requires,
         transport_mech,
         asContext,
         sasContext);
      // store it in csmList as the only security mechanism
      csmList[0] = csm;

      return csmList;
   }

   /**
    * Create the Secure Attribute Service (SAS) context
    * included in a CompoundSecMech definition
    **/
   public static SAS_ContextSec createSecureAttributeServiceContext(
      IorSecurityConfigMetaData metadata
      )
   {
      SAS_ContextSec context = null;
      
      // context contains
      // target_supports, target_requires, privilige_authorities,
      // supported_naming_mechanisms, supported_identity_types
      int support = 0;
      int require = 0;
      ServiceConfiguration[] privilAuth = new ServiceConfiguration[0];
      byte[][] supNamMechs = {};
      int supIdenTypes = 0;     // 0 means ITTAbsent
      
      // the the SasContext metadata
      SasContext sasMeta = metadata.getSasContext();
      
      // if no SAS context metadata, or caller propagation is not
      // supported, we return with a more or less empty sas context
      if( sasMeta == null || !sasMeta.isCallerPropagationSupported() )
      {
         context = new SAS_ContextSec((short) support,
            (short) require,
            privilAuth,
            supNamMechs,
            supIdenTypes);
      }
      else
      {
         support = IdentityAssertion.value;
         
         // supporting GSSUP (username/password) naming mechanism
         byte[] upMech = createGSSUPMechOID();
         supNamMechs = new byte[1][upMech.length];
         System.arraycopy(upMech, 0, supNamMechs[0], 0, upMech.length);
         
         // since we support IdentityAssertion we need to specify
         // supported identity types. CTS says we need them all
         supIdenTypes = ITTAnonymous.value |
            ITTPrincipalName.value |
            ITTX509CertChain.value |
            ITTDistinguishedName.value;
         // wrap it up
         context = new SAS_ContextSec((short) support,
            (short) require,
            privilAuth,
            supNamMechs,
            supIdenTypes);
      }

      return context;
   }

   /**
    * Create the client Authentication Service (AS) context
    * included in a CompoundSecMech definition.
    **/
   public static AS_ContextSec createAuthenticationServiceContext(
      IorSecurityConfigMetaData metadata
      )
   {
      AS_ContextSec context = null;
      
      // the content of the context
      int support = EstablishTrustInClient.value; // per default support this
      int require = 0;
      byte[] clientAuthMech = {};
      byte[] targetName = {};
      
      // get the AsContext metadata
      AsContext asMeta = metadata.getAsContext();
      
      // if no AS context metatada exists, or authentication method
      // "none" is specified, we can produce an empty AS context
      if( asMeta == null || asMeta.getAuthMethod().equals(AsContext.AUTH_METHOD_NONE)
         || asMeta.isRequired() == false )
      {
         // TODO: check if an empty AS context may contain a target name.
         targetName = encodeGssExportedName(targetName);
         
         context = new AS_ContextSec((short) support,
            (short) require,
            clientAuthMech,
            targetName);
      }
      else
      {
         // required depends on the metadata
         if( asMeta.isRequired() )
            require = EstablishTrustInClient.value;
         
         // we only support GSSUP authentication method
         clientAuthMech = createGSSUPMechOID();
         
         // finally, encode the "realm" name as a CSI.GSS_NT_ExportedName.
         // clientAuthMech should contain the DER encoded GSSUPMechOID
         // at this point.
         String realm = asMeta.getRealm();
         targetName = createGSSExportedName(clientAuthMech, realm.getBytes());
         
         // wrap it up
         context = new AS_ContextSec((short) support,
            (short) require,
            clientAuthMech,
            targetName);
      }

      return context;
   }

   /**
    * Create a transport mechanism TaggedComponent to be stuffed into a
    * CompoundSecMech.
    *
    * If no TransportConfig metadata is specified, or ssl port is negative,
    * or the specified metadata indicates that transport config is not supported,
    * then a TAG_NULL_TAG (empty) TaggedComponent will be returned.
    *
    * Otherwise a CSIIOP.TLS_SEC_TRANS, tagged as TAG_TLS_SEC_TRANS will
    * be returned, indicating support for TLS/SSL as a CSIv2 transport
    * mechanism.
    *
    * Multiple TransportAddress may be included in the SSL info
    * (host/port pairs), but we only include one.
    **/
   public static TaggedComponent createTransportMech(
      TransportConfig tconfig,
      Codec codec,
      int sslPort,
      ORB orb
      )
   {
      TaggedComponent tc = null;
      
      // what we support and require as a target
      int support = 0;
      int require = 0;

      if( tconfig != null )
      {
         require = createTargetRequires(tconfig);
         support = createTargetSupports(tconfig);
      }

      if( tconfig == null || support == 0 || sslPort < 0 )
      {
         // no support for transport security
         tc = new TaggedComponent(TAG_NULL_TAG.value, new byte[0]);
      }
      else
      {
         // my ip address
         String host;
         try
         {
            host = InetAddress.getLocalHost().getHostAddress();
         }
         catch(java.net.UnknownHostException e)
         {
            host = "127.0.0.1";
         }
         
         // this will create only one transport address
         TransportAddress[] taList = createTransportAddress(host, sslPort);

         TLS_SEC_TRANS tst = new TLS_SEC_TRANS((short) support,
            (short) require,
            taList);

         // The tricky part, we must encode TLS_SEC_TRANS into an octet sequence 
         try
         {
            Any any = orb.create_any();

            TLS_SEC_TRANSHelper.insert(any, tst);
            byte[] b = codec.encode_value(any);

            tc = new TaggedComponent(TAG_TLS_SEC_TRANS.value, b);
         }
         catch(InvalidTypeForEncoding e)
         {
            log.warn("Caught unexcepted exception while encoding TLS_SEC_TRANS", e);
            throw new RuntimeException(e);
         }
      }

      return tc;
   }

   /**
    * Create a TransportAddress[] with a single TransportAddress
    **/
   public static TransportAddress[] createTransportAddress(
      String host, int port
      )
   {
      // idl type is unsighned sort, so we need this trick
      short short_port = (port > 32767) ? (short) (port - 65536) : (short) port;

      TransportAddress ta = new TransportAddress(host, short_port);
      TransportAddress[] taList = new TransportAddress[1];
      taList[0] = ta;

      return taList;
   }

   /**
    * Create the AssociationOption for CompoundSecMech - target_requires
    **/
   public static int createTargetRequires(TransportConfig tc)
   {
      int requires = 0;

      if( tc != null )
      {
         if( tc.getIntegrity().equals(TransportConfig.INTEGRITY_REQUIRED) )
            requires = requires | Integrity.value;

         if( tc.getConfidentiality().equals(TransportConfig.CONFIDENTIALITY_REQUIRED) )
            requires = requires | Confidentiality.value;

         if( tc.getDetectMisordering().equalsIgnoreCase(TransportConfig.DETECT_MISORDERING_REQUIRED) )
            requires = requires | DetectMisordering.value;

         if( tc.getDetectReplay().equalsIgnoreCase(TransportConfig.DETECT_REPLAY_REQUIRED) )
            requires = requires | DetectReplay.value;

         // no EstablishTrustInTarget required - client decides
         
         if( tc.getEstablishTrustInClient().equals(TransportConfig.ESTABLISH_TRUST_IN_CLIENT_REQUIRED) )
            requires = requires | EstablishTrustInClient.value;
      }

      return requires;
   }

   /**
    * Create bitmask of what the target supports
    **/
   public static int createTargetSupports(TransportConfig tc)
   {
      int supports = 0;

      if( tc != null )
      {
         if( !tc.getIntegrity().equals(TransportConfig.INTEGRITY_NONE) )
            supports = supports | Integrity.value;

         if( !tc.getConfidentiality().equals(TransportConfig.CONFIDENTIALITY_NONE) )
            supports = supports | Confidentiality.value;

         if( !tc.getDetectMisordering().equalsIgnoreCase(TransportConfig.DETECT_MISORDERING_NONE) )
            supports = supports | DetectMisordering.value;

         if( !tc.getDetectReplay().equalsIgnoreCase(TransportConfig.DETECT_REPLAY_NONE) )
            supports = supports | DetectReplay.value;

         if( !tc.getEstablishTrustInTarget().equals(TransportConfig.ESTABLISH_TRUST_IN_TARGET_NONE) )
            supports = supports | EstablishTrustInTarget.value;

         if( !tc.getEstablishTrustInClient().equals(TransportConfig.ESTABLISH_TRUST_IN_CLIENT_NONE) )
            supports = supports | EstablishTrustInClient.value;
      }

      return supports;
   }

   /**
    * Create an ASN.1, DER encoded representation for
    * the GSSUP OID mechanism
    **/
   public static byte[] createGSSUPMechOID()
   {
      // kudos to org.ietf.jgss.Oid for the Oid utility
      // need to strip the "oid:" part of the GSSUPMechOID first      

      byte[] retval = {};
      try
      {
         Oid oid = new Oid(GSSUPMechOID.value.substring(4));
         retval = oid.getDER();
      }
      catch(GSSException e)
      {
         log.warn("Caught exception while encoding GSSUPMechOID", e);
      }
      return retval;
   }

   /**
    * Return an ASN.1, DER encoded representation for the GSSUP OID mechanism.
    **/
   public static byte[] gssUpMechOid()
   {
      return (byte[])gssUpMechOidArray.clone();
   }
   
   /**
    * Generate an exported name as specified in [RFC 2743], section 3.2
    * copied below:
    * 
    * 3.2: Mechanism-Independent Exported Name Object Format
    *
    * This section specifies a mechanism-independent level of encapsulating
    * representation for names exported via the GSS_Export_name() call,
    * including an object identifier representing the exporting mechanism.
    * The format of names encapsulated via this representation shall be
    * defined within individual mechanism drafts.  The Object Identifier
    * value to indicate names of this type is defined in Section 4.7 of
    * this document.
    * 
    * No name type OID is included in this mechanism-independent level of
    * format definition, since (depending on individual mechanism
    * specifications) the enclosed name may be implicitly typed or may be
    * explicitly typed using a means other than OID encoding.
    * 
    * The bytes within MECH_OID_LEN and NAME_LEN elements are represented
    * most significant byte first (equivalently, in IP network byte order).
    * 
    * Length          Name            Description
    * 
    * 2               TOK_ID          Token Identifier
    *                                 For exported name objects, this
    *                                 must be hex 04 01.
    * 2               MECH_OID_LEN    Length of the Mechanism OID
    * MECH_OID_LEN    MECH_OID        Mechanism OID, in DER
    * 4               NAME_LEN        Length of name
    * NAME_LEN        NAME            Exported name; format defined in
    *                                 applicable mechanism draft.
    * 
    * A concrete example of the contents of an exported name object,
    * derived from the Kerberos Version 5 mechanism, is as follows:
    *
    * 04 01 00 0B 06 09 2A 86 48 86 F7 12 01 02 02 hx xx xx xl pp qq ... zz
    *
    * ...
    *
    * @param oid the DER encoded OID
    * @param name the name to be converted to GSSExportedName
    **/
   public static byte[] createGSSExportedName(byte[] oid, byte[] name)
   {
      int olen = oid.length;
      int nlen = name.length;
      
      // size according to spec
      int size = 2 + 2 + olen + 4 + nlen;
    
      // allocate space for the exported name
      byte[] buf = new byte[size];
      // index
      int i = 0;
      
      // standard header
      buf[i++] = 0x04;
      buf[i++] = 0x01;
      
      // encode oid length
      buf[i++] = (byte) (olen & 0xFF00);
      buf[i++] = (byte) (olen & 0x00FF);
      
      // copy the oid in the exported name buffer
      System.arraycopy(oid, 0, buf, i, olen);
      i += olen;
      
      // encode the name length in the exported buffer
      buf[i++] = (byte) (nlen & 0xFF000000);
      buf[i++] = (byte) (nlen & 0x00FF0000);
      buf[i++] = (byte) (nlen & 0x0000FF00);
      buf[i++] = (byte) (nlen & 0x000000FF);

      // finally, copy the name bytes
      System.arraycopy(name, 0, buf, i, nlen);

      // done
      return buf;
   }

   /**
    * ASN.1-encode an InitialContextToken as defined in RFC 2743, Section 3.1,
    * "Mechanism-Independent Token Format", pp. 81-82. The encoded token 
    * contains the ASN.1 tag 0x60, followed by a token length (which is itself
    * stored in a variable-lenght format and takes 1 to 5 bytes), the GSSUP
    * mechanism identifier, and a mechanism-specific token, which in this
    * case is a CDR encapsulation of the GSSUP InitialContextToken in the
    * authToken parameter.
    */
   public static byte[] encodeInitialContextToken(InitialContextToken authToken, 
                                                  Codec codec)
   {
      byte[] out = null;
      Any any = ORB.init().create_any();
      InitialContextTokenHelper.insert(any, authToken);
      try
      {
         out = codec.encode_value(any);
      }
      catch (Exception e)
      {
         // logger.error("Error encoding for GSSNameSpi: " + e);
         return new byte[0];
      }
      
      int length = out.length + gssUpMechOidArray.length;
      int n;
      
      if (length < (1 << 7))
         n = 0;
      else if (length < (1 << 8))
         n = 1;                 
      else if (length < (1 << 16))
         n = 2;                 
      else if (length < (1 << 24))
         n = 3;                 
      else // if (length < (1 << 32))
         n = 4;
      
      byte[] encodedToken = new byte[2 + n + length];
      encodedToken[0] = 0x60;
      
      if (n == 0)
         encodedToken[1] = (byte)length;
      else 
      {
         encodedToken[1] = (byte)(n | 0x80); 
         switch (n) 
         {
         case 1:
            encodedToken[2] = (byte)length;
            break;
         case 2:
            encodedToken[2] = (byte)(length >> 8);            
            encodedToken[3] = (byte)length;
            break;
         case 3:
            encodedToken[2] = (byte)(length >> 16);            
            encodedToken[3] = (byte)(length >> 8);            
            encodedToken[4] = (byte)length;
            break;
         default: // case 4:
            encodedToken[2] = (byte)(length >> 24);            
            encodedToken[3] = (byte)(length >> 16);            
            encodedToken[4] = (byte)(length >> 8);            
            encodedToken[5] = (byte)length;
         }
      }
      System.arraycopy(gssUpMechOidArray, 0,
                       encodedToken, 2 + n,
                       gssUpMechOidArray.length);
      System.arraycopy(out, 0,
                       encodedToken, 2 + n + gssUpMechOidArray.length,
                       out.length);
      
      return encodedToken;
   }
   
   /**
    * Decodes an ASN.1-encoded InitialContextToken. 
    * See encodeInitialContextToken for a description of the encoded token
    * format.
    */
   public static InitialContextToken decodeInitialContextToken(
                                                         byte[] encodedToken, 
                                                         Codec codec)
   {
      if(encodedToken[0] != 0x60)
         return null;
      
      int encodedLength = 0;
      int n = 0;
      
      if(encodedToken[1] >= 0)
         encodedLength = encodedToken[1];
      else 
      {
         n = encodedToken[1] & 0x7F;
         for(int i = 1; i <= n; i++)
            encodedLength += (encodedToken[1 + i] & 0xFF) << (n-i)*8;
      }
        
      int  length = encodedLength - gssUpMechOidArray.length;
      byte[] encodedInitialContextToken = new byte[length];
      
      System.arraycopy(encodedToken, 2 + n + gssUpMechOidArray.length,
                       encodedInitialContextToken, 0,
                       length);
      Any any = null;
      try
      {
         any = codec.decode_value(encodedInitialContextToken,
                                  InitialContextTokenHelper.type());
      }
      catch(Exception e)
      {
         return null;
      }
      
      InitialContextToken contextToken = 
         InitialContextTokenHelper.extract(any);
      
      return contextToken;
      
   }

   /**
    * ASN.1-encodes a GSS exported name with the GSSUP mechanism OID.
    * See createGSSExportedName for a description of the encoding format.
    */   
   public static byte[] encodeGssExportedName(byte[] name)
   {
      return createGSSExportedName(gssUpMechOidArray, name);
   }

   /**
    * Decodes a GSS exported name that has been encoded with the GSSUP 
    * mechanism OID. See createGSSExportedName for a description of the
    * encoding format.
    */   
   public static byte[] decodeGssExportedName(byte[] encodedName)
   {
      if(encodedName[0] != 0x04 || encodedName[1] != 0x01)
         return null;
      
      int mechOidLength = (encodedName[2] & 0xFF) << 8; //MECH_OID_LEN
      mechOidLength +=    (encodedName[3] & 0xFF);      // MECH_OID_LEN
      
      byte[] oidArray = new byte[mechOidLength];
      System.arraycopy(encodedName, 4,
                       oidArray, 0,
                       mechOidLength);
      
      for(int i = 0; i < mechOidLength; i++)
      {
         if(gssUpMechOidArray[i] != oidArray[i])
            return null;
      }
      
      int offset = 4 + mechOidLength;
      int nameLength = (encodedName[  offset] & 0xFF) << 24;
      nameLength +=    (encodedName[++offset] & 0xFF) << 16;
      nameLength +=    (encodedName[++offset] & 0xFF) << 8;
      nameLength +=    (encodedName[++offset] & 0xFF);
      
      byte[] name = new byte[nameLength];
      System.arraycopy(encodedName, ++offset,
                       name, 0,
                       nameLength);
      
      return name;
   }
   
   /**
    * Helper method to be called from a client request interceptor.
    * The <code>ri</code> parameter refers to the current request.
    * This method returns the first <code>CompoundSecMech</code>
    * found in the target IOR such that 
    * <ul>
    * <li>all <code>CompoundSecMech</code> requirements are satisfied 
    *     by the options in the <code>clientSupports</code> parameter, 
    *     and</li>
    * <li>every requirement in the <code>clientRequires</code> parameter
    *     is satisfied by the <code>CompoundSecMech</code>.</li>
    * </ul>
    * The method returns null if the target IOR contains no 
    * <code>CompoundSecMech</code>s or if no 
    * matching <code>CompoundSecMech</code> is found. 
    * 
    * Since this method is intended to be called from a client request
    * interceptor, it converts unexpected exceptions into <code>MARSHAL</code>
    * exceptions.
    */
   public static CompoundSecMech getMatchingSecurityMech(ClientRequestInfo ri,
      Codec codec,
      short clientSupports,
      short clientRequires)
   {
      CompoundSecMechList csmList = null;
      try
      {
         TaggedComponent tc =
            ri.get_effective_component(TAG_CSI_SEC_MECH_LIST.value);

         Any any = codec.decode_value(tc.component_data,
            CompoundSecMechListHelper.type());

         csmList = CompoundSecMechListHelper.extract(any);
         
         // look for the first matching security mech
         for(int i = 0; i < csmList.mechanism_list.length; i++)
         {
            CompoundSecMech securityMech = csmList.mechanism_list[i];
            AS_ContextSec authConfig = securityMech.as_context_mech;

            if( (EstablishTrustInTarget.value
               & (clientRequires ^ authConfig.target_supports)
               & ~authConfig.target_supports) != 0 )
            {
               // client requires EstablishTrustInTarget, 
               // but target does not support it:
               continue; // skip this securityMech
            }

            if( (EstablishTrustInClient.value
               & (authConfig.target_requires ^ clientSupports)
               & ~clientSupports) != 0 )
            {
               // target requires EstablishTrustInClient,
               // but client does not support it:
               continue; // skip this securityMech
            }

            SAS_ContextSec identityConfig = securityMech.sas_context_mech;

            if( (IdentityAssertion.value
               & (identityConfig.target_requires ^ clientSupports)
               & ~clientSupports) != 0 )
            {
               // target requires IdentityAssertion,
               // but client does not support it:
               continue; // skip this securityMech
            }

            // found matching securityMech
            return securityMech;
         }
         // no matching securityMech was found
         return null;
      }
      catch(BAD_PARAM e)
      {
         // no component with TAG_CSI_SEC_MECH_LIST was found
         return null;
      }
      catch(org.omg.IOP.CodecPackage.TypeMismatch e)
      {
         // unexpected exception in codec.decode_value
         throw new MARSHAL("Unexpected exception: " + e);
      }
      catch(org.omg.IOP.CodecPackage.FormatMismatch e)
      {
         // unexpected exception in codec.decode_value
         throw new MARSHAL("Unexpected exception: " + e);
      }
   }

   /** Generate a string representation of the CompoundSecMech
    * @param securityMech - the CompoundSecMech to create the string for
    * @param buffer - the buffer to write to
    */ 
   public static void toString(CompoundSecMech securityMech, StringBuffer buffer)
   {
      AS_ContextSec asMech = securityMech != null ? securityMech.as_context_mech : null;
      SAS_ContextSec sasMech = securityMech != null ? securityMech.sas_context_mech : null;
      if( securityMech != null )
      {
         buffer.append("CompoundSecMech[");
         buffer.append("target_requires: ");
         buffer.append(securityMech.target_requires);
         if( asMech != null )
         {
            buffer.append("AS_ContextSec[");
            
            buffer.append("client_authentication_mech: ");
            try
            {
               buffer.append(new String(asMech.client_authentication_mech, "UTF-8"));
            }
            catch(UnsupportedEncodingException e)
            {
               buffer.append(e.getMessage());
            }
            buffer.append(", target_name: ");
            try
            {
               buffer.append(new String(asMech.target_name, "UTF-8"));
            }
            catch(UnsupportedEncodingException e)
            {
               buffer.append(e.getMessage());
            }
            buffer.append(", target_requires: ");
            buffer.append(asMech.target_requires);
            buffer.append(", target_supports: ");
            buffer.append(asMech.target_supports);
            buffer.append("]");
         }
         if( sasMech != null )
         {
            buffer.append("SAS_ContextSec[");
            buffer.append("supported_identity_types: ");
            buffer.append(sasMech.supported_identity_types);
            buffer.append(", target_requires: ");
            buffer.append(sasMech.target_requires);
            buffer.append(", target_supports: ");
            buffer.append(sasMech.target_supports);
            buffer.append("]");
         }
         buffer.append("]");
      }
   }

}
