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.List;
027
028import org.apache.directory.api.asn1.Asn1Object;
029import org.apache.directory.api.asn1.EncoderException;
030import org.apache.directory.api.asn1.ber.tlv.TLV;
031import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
032import org.apache.directory.api.util.Strings;
033import org.apache.directory.server.i18n.I18n;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037
038/**
039 * Store a list of METHOD-DATA
040 * 
041 * The ASN.1 grammar is :
042 * <pre>
043 * METHOD-DATA     ::= SEQUENCE OF &lt;PA-DATA&gt;
044 * </pre>
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public class MethodData implements Asn1Object
048{
049    /** The logger */
050    private static final Logger LOG = LoggerFactory.getLogger( MethodData.class );
051
052    /** Speedup for logs */
053    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
054
055    /** List of all PA-DATA stored */
056    private List<PaData> paDatas;
057
058    // Storage for computed lengths
059    private int methodDataLength;
060
061
062    /**
063     * Creates a new instance of MethodData.
064     */
065    public MethodData()
066    {
067        this.paDatas = new ArrayList<>();
068    }
069
070
071    /**
072     * Adds an {@link PaData} to the list
073     * @param paData The PaData to add
074     */
075    public void addPaData( PaData paData )
076    {
077        paDatas.add( paData );
078    }
079
080
081    /**
082     * Returns true if this {@link PaData} contains a specified {@link PaData}.
083     *
084     * @param paData The paData we are looking for in the existing list
085     * @return true if this {@link PaData} contains a specified {@link PaData}.
086     */
087    public boolean contains( PaData paData )
088    {
089        if ( paDatas != null )
090        {
091            return paDatas.contains( paData );
092        }
093
094        return false;
095    }
096
097
098    /**
099     * {@inheritDoc}
100     */
101    @Override
102    public int hashCode()
103    {
104        int hash = 37;
105
106        if ( paDatas != null )
107        {
108            hash = hash * 17 + paDatas.size();
109
110            for ( PaData paData : paDatas )
111            {
112                hash = hash * 17 + paData.hashCode();
113            }
114        }
115
116        return hash;
117    }
118
119
120    /**
121     * Returns true if two {@link MethodData} are equal.
122     *
123     * @param that The {@link MethodData} we want to compare with the current one
124     * @return true if two {@link MethodData} are equal.
125     */
126    public boolean equals( MethodData that )
127    {
128        if ( that == null )
129        {
130            return false;
131        }
132
133        // infoEntries can't be null after creation
134        if ( paDatas.size() != that.paDatas.size() )
135        {
136            return false;
137        }
138
139        for ( int i = 0; i < paDatas.size(); i++ )
140        {
141            if ( !paDatas.get( i ).equals( that.paDatas.get( i ) ) )
142            {
143                return false;
144            }
145        }
146
147        return true;
148    }
149
150
151    /**
152     * Returns the contained {@link PaData}s as an array.
153     *
154     * @return An array of {@link PaData}s.
155     */
156    public PaData[] getPaDatas()
157    {
158        return paDatas.toArray( new PaData[0] );
159    }
160
161
162    /**
163     * Compute the METHOD-DATA length
164     * <pre>
165     * METHOD-DATA :
166     * 
167     * 0x30 L1 METHOD-DATA sequence of PA-DATA
168     *  |
169     *  +--&gt; 0x30 L2[1] PA-DATA[1]
170     *  |
171     *  +--&gt; 0x30 L2[2] PA-DATA[2]
172     *  |
173     *  ...
174     *  |
175     *  +--&gt; 0x30 L2[n] PA-DATA[n]
176     *        
177     *  where L1 = sum( L2[1], l2[2], ..., L2[n] )
178     * </pre>
179     */
180    public int computeLength()
181    {
182        // Compute the PA-DATA length.
183        methodDataLength = 0;
184
185        if ( ( paDatas != null ) && !paDatas.isEmpty() )
186        {
187            for ( PaData paData : paDatas )
188            {
189                int length = paData.computeLength();
190                methodDataLength += length;
191            }
192        }
193
194        return 1 + TLV.getNbBytes( methodDataLength ) + methodDataLength;
195    }
196
197
198    /**
199     * Encode the METHOD-DATA message to a PDU. 
200     * <pre>
201     * METHOD-DATA :
202     * 
203     * 0x30 LL
204     *   0x30 LL PA-DATA[1] 
205     *   0x30 LL PA-DATA[1]
206     *   ... 
207     *   0x30 LL PA-DATA[1] 
208     * </pre>
209     * @param buffer The buffer where to put the PDU. It should have been allocated
210     * before, with the right size.
211     * @return The constructed PDU.
212     */
213    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
214    {
215        if ( buffer == null )
216        {
217            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
218        }
219
220        try
221        {
222            // The METHOD-DATA SEQ Tag
223            buffer.put( UniversalTag.SEQUENCE.getValue() );
224            buffer.put( TLV.getBytes( methodDataLength ) );
225
226            // The PA-DATA list, if it's not empty
227            if ( ( paDatas != null ) && !paDatas.isEmpty() )
228            {
229                for ( PaData paData : paDatas )
230                {
231                    paData.encode( buffer );
232                }
233            }
234        }
235        catch ( BufferOverflowException boe )
236        {
237            LOG.error( I18n.err( I18n.ERR_144, 1 + TLV.getNbBytes( methodDataLength )
238                + methodDataLength, buffer.capacity() ) );
239            throw new EncoderException( I18n.err( I18n.ERR_138 ), boe );
240        }
241
242        if ( IS_DEBUG )
243        {
244            LOG.debug( "METHOD-DATA encoding : {}", Strings.dumpBytes( buffer.array() ) );
245            LOG.debug( "METHOD-DATA initial value : {}", this );
246        }
247
248        return buffer;
249    }
250
251
252    /**
253     * @see Object#toString()
254     */
255    public String toString()
256    {
257        StringBuilder sb = new StringBuilder();
258        boolean isFirst = true;
259
260        sb.append( "METHOD-DATA : " );
261
262        for ( PaData paData : paDatas )
263        {
264            if ( isFirst )
265            {
266                isFirst = false;
267            }
268            else
269            {
270                sb.append( ", " );
271            }
272
273            sb.append( paData.toString() );
274        }
275
276        return sb.toString();
277    }
278}