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.encoder;
022    
023    
024    import java.io.IOException;
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.Iterator;
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.MessageType;
033    import org.apache.directory.server.dns.messages.OpCode;
034    import org.apache.directory.server.dns.messages.QuestionRecord;
035    import org.apache.directory.server.dns.messages.RecordType;
036    import org.apache.directory.server.dns.messages.ResourceRecord;
037    import org.apache.directory.server.dns.messages.ResponseCode;
038    import org.apache.directory.server.i18n.I18n;
039    import org.apache.mina.core.buffer.IoBuffer;
040    import org.slf4j.Logger;
041    import org.slf4j.LoggerFactory;
042    
043    
044    /**
045     * An encoder for DNS messages.  The primary usage of the DnsMessageEncoder is 
046     * to call the <code>encode(ByteBuffer, DnsMessage)</code> method which will 
047     * write the message to the outgoing ByteBuffer according to the DnsMessage 
048     * encoding in RFC-1035.
049     * 
050     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
051     * @version $Rev$, $Date$
052     */
053    public class DnsMessageEncoder
054    {
055        /** the log for this class */
056        private static final Logger log = LoggerFactory.getLogger( DnsMessageEncoder.class );
057    
058        /**
059         * A Hashed Adapter mapping record types to their encoders.
060         */
061        private static final Map<RecordType, RecordEncoder> DEFAULT_ENCODERS;
062    
063        static
064        {
065            Map<RecordType, RecordEncoder> map = new HashMap<RecordType, RecordEncoder>();
066    
067            map.put( RecordType.SOA, new StartOfAuthorityRecordEncoder() );
068            map.put( RecordType.A, new AddressRecordEncoder() );
069            map.put( RecordType.NS, new NameServerRecordEncoder() );
070            map.put( RecordType.CNAME, new CanonicalNameRecordEncoder() );
071            map.put( RecordType.PTR, new PointerRecordEncoder() );
072            map.put( RecordType.MX, new MailExchangeRecordEncoder() );
073            map.put( RecordType.SRV, new ServerSelectionRecordEncoder() );
074            map.put( RecordType.TXT, new TextRecordEncoder() );
075    
076            DEFAULT_ENCODERS = Collections.unmodifiableMap( map );
077        }
078    
079    
080        /**
081         * Encodes the {@link DnsMessage} into the {@link ByteBuffer}.
082         *
083         * @param byteBuffer
084         * @param message
085         */
086        public void encode( IoBuffer byteBuffer, DnsMessage message )
087        {
088            byteBuffer.putShort( ( short ) message.getTransactionId() );
089    
090            byte header = ( byte ) 0x00;
091            header |= encodeMessageType( message.getMessageType() );
092            header |= encodeOpCode( message.getOpCode() );
093            header |= encodeAuthoritativeAnswer( message.isAuthoritativeAnswer() );
094            header |= encodeTruncated( message.isTruncated() );
095            header |= encodeRecursionDesired( message.isRecursionDesired() );
096            byteBuffer.put( header );
097    
098            header = ( byte ) 0x00;
099            header |= encodeRecursionAvailable( message.isRecursionAvailable() );
100            header |= encodeResponseCode( message.getResponseCode() );
101            byteBuffer.put( header );
102    
103            byteBuffer
104                .putShort( ( short ) ( message.getQuestionRecords() != null ? message.getQuestionRecords().size() : 0 ) );
105            byteBuffer.putShort( ( short ) ( message.getAnswerRecords() != null ? message.getAnswerRecords().size() : 0 ) );
106            byteBuffer.putShort( ( short ) ( message.getAuthorityRecords() != null ? message.getAuthorityRecords().size()
107                : 0 ) );
108            byteBuffer.putShort( ( short ) ( message.getAdditionalRecords() != null ? message.getAdditionalRecords().size()
109                : 0 ) );
110    
111            putQuestionRecords( byteBuffer, message.getQuestionRecords() );
112            putResourceRecords( byteBuffer, message.getAnswerRecords() );
113            putResourceRecords( byteBuffer, message.getAuthorityRecords() );
114            putResourceRecords( byteBuffer, message.getAdditionalRecords() );
115        }
116    
117    
118        private void putQuestionRecords( IoBuffer byteBuffer, List<QuestionRecord> questions )
119        {
120            if ( questions == null )
121            {
122                return;
123            }
124    
125            QuestionRecordEncoder encoder = new QuestionRecordEncoder();
126    
127            Iterator<QuestionRecord> it = questions.iterator();
128    
129            while ( it.hasNext() )
130            {
131                QuestionRecord question = it.next();
132                encoder.put( byteBuffer, question );
133            }
134        }
135    
136    
137        private void putResourceRecords( IoBuffer byteBuffer, List<ResourceRecord> records )
138        {
139            if ( records == null )
140            {
141                return;
142            }
143    
144            Iterator<ResourceRecord> it = records.iterator();
145    
146            while ( it.hasNext() )
147            {
148                ResourceRecord record = it.next();
149    
150                try
151                {
152                    put( byteBuffer, record );
153                }
154                catch ( IOException ioe )
155                {
156                    log.error( ioe.getLocalizedMessage(), ioe );
157                }
158            }
159        }
160    
161    
162        private void put( IoBuffer byteBuffer, ResourceRecord record ) throws IOException
163        {
164            RecordType type = record.getRecordType();
165    
166            RecordEncoder encoder = DEFAULT_ENCODERS.get( type );
167    
168            if ( encoder == null )
169            {
170                throw new IOException( I18n.err( I18n.ERR_597, type ) );
171            }
172    
173            encoder.put( byteBuffer, record );
174        }
175    
176    
177        private byte encodeMessageType( MessageType messageType )
178        {
179            byte oneBit = ( byte ) ( messageType.convert() & 0x01 );
180            return ( byte ) ( oneBit << 7 );
181        }
182    
183    
184        private byte encodeOpCode( OpCode opCode )
185        {
186            byte fourBits = ( byte ) ( opCode.convert() & 0x0F );
187            return ( byte ) ( fourBits << 3 );
188        }
189    
190    
191        private byte encodeAuthoritativeAnswer( boolean authoritative )
192        {
193            if ( authoritative )
194            {
195                return ( byte ) ( ( byte ) 0x01 << 2 );
196            }
197            return ( byte ) 0;
198        }
199    
200    
201        private byte encodeTruncated( boolean truncated )
202        {
203            if ( truncated )
204            {
205                return ( byte ) ( ( byte ) 0x01 << 1 );
206            }
207            return 0;
208        }
209    
210    
211        private byte encodeRecursionDesired( boolean recursionDesired )
212        {
213            if ( recursionDesired )
214            {
215                return ( byte ) 0x01;
216            }
217            return 0;
218        }
219    
220    
221        private byte encodeRecursionAvailable( boolean recursionAvailable )
222        {
223            if ( recursionAvailable )
224            {
225                return ( byte ) ( ( byte ) 0x01 << 7 );
226            }
227            return 0;
228        }
229    
230    
231        private byte encodeResponseCode( ResponseCode responseCode )
232        {
233            return ( byte ) ( responseCode.convert() & 0x0F );
234        }
235    }