001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.shared.kerberos.components;
021
022
023import java.nio.BufferOverflowException;
024import java.nio.ByteBuffer;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.List;
028
029import org.apache.directory.api.asn1.Asn1Object;
030import org.apache.directory.api.asn1.EncoderException;
031import org.apache.directory.api.asn1.ber.tlv.TLV;
032import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
033import org.apache.directory.api.util.Strings;
034import org.apache.directory.server.i18n.I18n;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038
039/**
040 * Store a list of ETYPE-INFO
041 * 
042 * The ASN.1 grammar is :
043 * <pre>
044 * ETYPE-INFO              ::= SEQUENCE OF &lt;ETYPE-INFO-ENTRY&gt;
045 * </pre>
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public class ETypeInfo implements Asn1Object
049{
050    /** The logger */
051    private static final Logger LOG = LoggerFactory.getLogger( ETypeInfo.class );
052
053    /** Speedup for logs */
054    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
055
056    /** List of all ETYPE-INFO-ENTRY stored */
057    private List<ETypeInfoEntry> etypeInfoEntries;
058
059    // Storage for computed lengths
060    private int etypeInfoLength;
061
062
063    /**
064     * Creates a new instance of ETypeInfo.
065     */
066    public ETypeInfo()
067    {
068        this.etypeInfoEntries = new ArrayList<>();
069    }
070
071
072    /**
073     * Creates a new instance of ETypeInfo.
074     *
075     * @param etypeInfoEntries The associated etypeInfoEntries
076     */
077    public ETypeInfo( ETypeInfoEntry[] etypeInfoEntries )
078    {
079        if ( etypeInfoEntries == null )
080        {
081            this.etypeInfoEntries = new ArrayList<>();
082        }
083        else
084        {
085            this.etypeInfoEntries = Arrays.asList( etypeInfoEntries );
086        }
087    }
088
089
090    /**
091     * Adds an {@link ETypeInfoEntry} to the list
092     * @param etypeInfoEntry The ETypeInfoEntry to add
093     */
094    public void addETypeInfoEntry( ETypeInfoEntry etypeInfoEntry )
095    {
096        etypeInfoEntries.add( etypeInfoEntry );
097    }
098
099
100    /**
101     * Returns true if this {@link ETypeInfoEntry} contains a specified {@link ETypeInfoEntry}.
102     *
103     * @param etypeInfoEntry The etypeInfoEntry we are looking for in the existing list
104     * @return true if this {@link ETypeInfoEntry} contains a specified {@link ETypeInfoEntry}.
105     */
106    public boolean contains( ETypeInfoEntry etypeInfoEntry )
107    {
108        if ( etypeInfoEntries != null )
109        {
110            return etypeInfoEntries.contains( etypeInfoEntry );
111        }
112
113        return false;
114    }
115
116
117    /**
118     * {@inheritDoc}
119     */
120    @Override
121    public int hashCode()
122    {
123        int hash = 37;
124
125        if ( etypeInfoEntries != null )
126        {
127            hash = hash * 17 + etypeInfoEntries.size();
128
129            for ( ETypeInfoEntry etypeInfoEntry : etypeInfoEntries )
130            {
131                hash = hash * 17 + etypeInfoEntry.hashCode();
132            }
133        }
134
135        return hash;
136    }
137
138
139    /**
140     * Returns true if two {@link ETypeInfo} are equal.
141     *
142     * @param that The {@link ETypeInfo} we want to compare with the current one
143     * @return true if two {@link ETypeInfo} are equal.
144     */
145    public boolean equals( ETypeInfo that )
146    {
147        if ( that == null )
148        {
149            return false;
150        }
151
152        // infoEntries can't be null after creation
153        if ( etypeInfoEntries.size() != that.etypeInfoEntries.size() )
154        {
155            return false;
156        }
157
158        for ( int i = 0; i < etypeInfoEntries.size(); i++ )
159        {
160            if ( !etypeInfoEntries.get( i ).equals( that.etypeInfoEntries.get( i ) ) )
161            {
162                return false;
163            }
164        }
165
166        return true;
167    }
168
169
170    /**
171     * Returns the contained {@link ETypeInfoEntry}s as an array.
172     *
173     * @return An array of {@link ETypeInfoEntry}s.
174     */
175    public ETypeInfoEntry[] getETypeInfoEntries()
176    {
177        return etypeInfoEntries.toArray( new ETypeInfoEntry[0] );
178    }
179
180
181    /**
182     * Compute the ETypeInfo length
183     * <pre>
184     * ETypeInfo :
185     * 
186     * 0x30 L1 ETypeInfo sequence of ETypeInfoEntry
187     *  |
188     *  +--&gt; 0x30 L2[1] ETypeInfoEntry[1]
189     *  |
190     *  +--&gt; 0x30 L2[2] ETypeInfoEntry[2]
191     *  |
192     *  ...
193     *  |
194     *  +--&gt; 0x30 L2[n] ETypeInfoEntry[n]
195     *        
196     *  where L1 = sum( L2[1], l2[2], ..., L2[n] )
197     * </pre>
198     */
199    public int computeLength()
200    {
201        // Compute the ETypeInfo length.
202        etypeInfoLength = 0;
203
204        if ( ( etypeInfoEntries != null ) && !etypeInfoEntries.isEmpty() )
205        {
206            for ( ETypeInfoEntry infoEntry : etypeInfoEntries )
207            {
208                int length = infoEntry.computeLength();
209                etypeInfoLength += length;
210            }
211        }
212
213        return 1 + TLV.getNbBytes( etypeInfoLength ) + etypeInfoLength;
214    }
215
216
217    /**
218     * Encode the ETypeInfo message to a PDU. 
219     * <pre>
220     * ETypeInfo :
221     * 
222     * 0x30 LL
223     *   0x30 LL ETypeInfoEntry[1] 
224     *   0x30 LL ETypeInfoEntry[1]
225     *   ... 
226     *   0x30 LL ETypeInfoEntry[1] 
227     * </pre>
228     * @param buffer The buffer where to put the PDU. It should have been allocated
229     * before, with the right size.
230     * @return The constructed PDU.
231     */
232    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
233    {
234        if ( buffer == null )
235        {
236            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
237        }
238
239        try
240        {
241            // The ETypeInfoEntry SEQ Tag
242            buffer.put( UniversalTag.SEQUENCE.getValue() );
243            buffer.put( TLV.getBytes( etypeInfoLength ) );
244
245            // The ETypeInfoEntry list, if it's not empty
246            if ( ( etypeInfoEntries != null ) && !etypeInfoEntries.isEmpty() )
247            {
248                for ( ETypeInfoEntry infoEntry : etypeInfoEntries )
249                {
250                    infoEntry.encode( buffer );
251                }
252            }
253        }
254        catch ( BufferOverflowException boe )
255        {
256            LOG.error( I18n.err( I18n.ERR_144, 1 + TLV.getNbBytes( etypeInfoLength )
257                + etypeInfoLength, buffer.capacity() ) );
258            throw new EncoderException( I18n.err( I18n.ERR_138 ), boe );
259        }
260
261        if ( IS_DEBUG )
262        {
263            LOG.debug( "ETYPE-INFO encoding : {}", Strings.dumpBytes( buffer.array() ) );
264            LOG.debug( "ETYPE-INFO initial value : {}", this );
265        }
266
267        return buffer;
268    }
269
270
271    /**
272     * @see Object#toString()
273     */
274    public String toString()
275    {
276        StringBuilder sb = new StringBuilder();
277        boolean isFirst = true;
278
279        for ( ETypeInfoEntry infoEntry : etypeInfoEntries )
280        {
281            if ( isFirst )
282            {
283                isFirst = false;
284            }
285            else
286            {
287                sb.append( ", " );
288            }
289
290            sb.append( infoEntry.toString() );
291        }
292
293        return sb.toString();
294    }
295}