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 }