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     */
020    
021    package org.apache.directory.server.dns.io.decoder;
022    
023    
024    import java.io.IOException;
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    
031    import org.apache.directory.server.dns.messages.DnsMessage;
032    import org.apache.directory.server.dns.messages.DnsMessageModifier;
033    import org.apache.directory.server.dns.messages.MessageType;
034    import org.apache.directory.server.dns.messages.OpCode;
035    import org.apache.directory.server.dns.messages.QuestionRecord;
036    import org.apache.directory.server.dns.messages.RecordClass;
037    import org.apache.directory.server.dns.messages.RecordType;
038    import org.apache.directory.server.dns.messages.ResourceRecord;
039    import org.apache.directory.server.dns.messages.ResourceRecordImpl;
040    import org.apache.directory.server.dns.messages.ResponseCode;
041    import org.apache.directory.server.i18n.I18n;
042    import org.apache.mina.core.buffer.IoBuffer;
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    
047    /**
048     * A decoder for DNS messages.  The primary usage of the DnsMessageDecoder is by
049     * calling the <code>decode(ByteBuffer)</code> method which will read the 
050     * message from the incoming ByteBuffer and build a <code>DnsMessage</code>
051     * from it according to the DnsMessage encoding in RFC-1035.
052     * 
053     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
054     * @version $Rev$, $Date$
055     */
056    public class DnsMessageDecoder
057    {
058    
059        private final Logger logger = LoggerFactory.getLogger( DnsMessageDecoder.class );
060    
061        /**
062         * A Hashed Adapter mapping record types to their encoders.
063         */
064        private static final Map<RecordType, RecordDecoder> DEFAULT_DECODERS;
065    
066        static
067        {
068            Map<RecordType, RecordDecoder> map = new HashMap<RecordType, RecordDecoder>();
069    
070            map.put( RecordType.A, new AddressRecordDecoder() );
071            map.put( RecordType.NS, new NameServerRecordDecoder() );
072            map.put( RecordType.MX, new MailExchangeRecordDecoder() );
073            map.put( RecordType.AAAA, new IPv6RecordDecoder() );
074    
075            DEFAULT_DECODERS = Collections.unmodifiableMap( map );
076        }
077    
078    
079        /**
080         * Decode the {@link ByteBuffer} into a {@link DnsMessage}.
081         *
082         * @param in
083         * @return The {@link DnsMessage}.
084         * @throws IOException
085         */
086        public DnsMessage decode( IoBuffer in ) throws IOException
087        {
088            DnsMessageModifier modifier = new DnsMessageModifier();
089    
090            modifier.setTransactionId( in.getUnsignedShort() );
091    
092            byte header = in.get();
093            modifier.setMessageType( decodeMessageType( header ) );
094            modifier.setOpCode( decodeOpCode( header ) );
095            modifier.setAuthoritativeAnswer( decodeAuthoritativeAnswer( header ) );
096            modifier.setTruncated( decodeTruncated( header ) );
097            modifier.setRecursionDesired( decodeRecursionDesired( header ) );
098    
099            header = in.get();
100            modifier.setRecursionAvailable( decodeRecursionAvailable( header ) );
101            modifier.setResponseCode( decodeResponseCode( header ) );
102    
103            short questionCount = in.getShort();
104            short answerCount = in.getShort();
105            short authorityCount = in.getShort();
106            short additionalCount = in.getShort();
107    
108            logger.debug( "decoding {} question records", questionCount );
109            modifier.setQuestionRecords( getQuestions( in, questionCount ) );
110    
111            logger.debug( "decoding {} answer records", answerCount );
112            modifier.setAnswerRecords( getRecords( in, answerCount ) );
113    
114            logger.debug( "decoding {} authority records", authorityCount );
115            modifier.setAuthorityRecords( getRecords( in, authorityCount ) );
116    
117            logger.debug( "decoding {} additional records", additionalCount );
118            modifier.setAdditionalRecords( getRecords( in, additionalCount ) );
119    
120            return modifier.getDnsMessage();
121        }
122    
123    
124        private List<ResourceRecord> getRecords( IoBuffer byteBuffer, short recordCount ) throws IOException
125        {
126            List<ResourceRecord> records = new ArrayList<ResourceRecord>( recordCount );
127    
128            for ( int ii = 0; ii < recordCount; ii++ )
129            {
130                String domainName = getDomainName( byteBuffer );
131                RecordType recordType = RecordType.convert( byteBuffer.getShort() );
132                RecordClass recordClass = RecordClass.convert( byteBuffer.getShort() );
133    
134                int timeToLive = byteBuffer.getInt();
135                short dataLength = byteBuffer.getShort();
136    
137                Map<String, Object> attributes = decode( byteBuffer, recordType, dataLength );
138                records.add( new ResourceRecordImpl( domainName, recordType, recordClass, timeToLive, attributes ) );
139            }
140    
141            return records;
142        }
143    
144    
145        private Map<String, Object> decode( IoBuffer byteBuffer, RecordType type, short length ) throws IOException
146        {
147            RecordDecoder recordDecoder = DEFAULT_DECODERS.get( type );
148    
149            if ( recordDecoder == null )
150            {
151                throw new IllegalArgumentException( I18n.err(I18n.ERR_600, type ) );
152            }
153    
154            return recordDecoder.decode( byteBuffer, length );
155        }
156    
157    
158        private List<QuestionRecord> getQuestions( IoBuffer byteBuffer, short questionCount )
159        {
160            List<QuestionRecord> questions = new ArrayList<QuestionRecord>( questionCount );
161    
162            for ( int ii = 0; ii < questionCount; ii++ )
163            {
164                String domainName = getDomainName( byteBuffer );
165    
166                RecordType recordType = RecordType.convert( byteBuffer.getShort() );
167                RecordClass recordClass = RecordClass.convert( byteBuffer.getShort() );
168    
169                questions.add( new QuestionRecord( domainName, recordType, recordClass ) );
170            }
171    
172            return questions;
173        }
174    
175    
176        static String getDomainName( IoBuffer byteBuffer )
177        {
178            StringBuffer domainName = new StringBuffer();
179            recurseDomainName( byteBuffer, domainName );
180    
181            return domainName.toString();
182        }
183    
184    
185        static void recurseDomainName( IoBuffer byteBuffer, StringBuffer domainName )
186        {
187            int length = byteBuffer.getUnsigned();
188    
189            if ( isOffset( length ) )
190            {
191                int position = byteBuffer.getUnsigned();
192                int offset = length & ~( 0xc0 ) << 8;
193                int originalPosition = byteBuffer.position();
194                byteBuffer.position( position + offset );
195    
196                recurseDomainName( byteBuffer, domainName );
197    
198                byteBuffer.position( originalPosition );
199            }
200            else if ( isLabel( length ) )
201            {
202                int labelLength = length;
203                getLabel( byteBuffer, domainName, labelLength );
204                recurseDomainName( byteBuffer, domainName );
205            }
206        }
207    
208    
209        static boolean isOffset( int length )
210        {
211            return ( ( length & 0xc0 ) == 0xc0 );
212        }
213    
214    
215        static boolean isLabel( int length )
216        {
217            return ( length != 0 && ( length & 0xc0 ) == 0 );
218        }
219    
220    
221        static void getLabel( IoBuffer byteBuffer, StringBuffer domainName, int labelLength )
222        {
223            for ( int jj = 0; jj < labelLength; jj++ )
224            {
225                char character = ( char ) byteBuffer.get();
226                domainName.append( character );
227            }
228    
229            if ( byteBuffer.get( byteBuffer.position() ) != 0 )
230            {
231                domainName.append( "." );
232            }
233        }
234    
235    
236        private MessageType decodeMessageType( byte header )
237        {
238            return MessageType.convert( ( byte ) ( ( header & 0x80 ) >>> 7 ) );
239        }
240    
241    
242        private OpCode decodeOpCode( byte header )
243        {
244            return OpCode.convert( ( byte ) ( ( header & 0x78 ) >>> 3 ) );
245        }
246    
247    
248        private boolean decodeAuthoritativeAnswer( byte header )
249        {
250            return ( ( header & 0x04 ) >>> 2 ) == 1;
251        }
252    
253    
254        private boolean decodeTruncated( byte header )
255        {
256            return ( ( header & 0x02 ) >>> 1 ) == 1;
257        }
258    
259    
260        private boolean decodeRecursionDesired( byte header )
261        {
262            return ( ( header & 0x01 ) ) == 1;
263        }
264    
265    
266        private boolean decodeRecursionAvailable( byte header )
267        {
268            return ( ( header & 0x80 ) >>> 7 ) == 1;
269        }
270    
271    
272        private ResponseCode decodeResponseCode( byte header )
273        {
274            return ResponseCode.convert( ( byte ) ( header & 0x0F ) );
275        }
276    }