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