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.dhcp.io;
022
023
024import java.io.UnsupportedEncodingException;
025import java.net.InetAddress;
026import java.net.UnknownHostException;
027import java.nio.ByteBuffer;
028import java.util.Arrays;
029
030import org.apache.directory.server.dhcp.DhcpException;
031import org.apache.directory.server.dhcp.messages.DhcpMessage;
032import org.apache.directory.server.dhcp.messages.HardwareAddress;
033import org.apache.directory.server.dhcp.options.DhcpOption;
034import org.apache.directory.server.dhcp.options.OptionsField;
035import org.apache.directory.server.dhcp.options.dhcp.DhcpMessageType;
036import org.apache.directory.server.dhcp.options.dhcp.UnrecognizedOption;
037import org.apache.directory.server.i18n.I18n;
038
039
040/**
041 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
042 */
043public class DhcpMessageDecoder
044{
045
046    /**
047     * Convert a byte buffer into a DhcpMessage.
048     * 
049     * @return a DhcpMessage.
050     * @param buffer ByteBuffer to convert to a DhcpMessage object
051     * @throws DhcpException 
052     */
053    public DhcpMessage decode( ByteBuffer buffer ) throws DhcpException
054    {
055        byte op = buffer.get();
056
057        short htype = ( short ) ( buffer.get() & 0xff );
058        short hlen = ( short ) ( buffer.get() & 0xff );
059        short hops = ( short ) ( buffer.get() & 0xff );
060        int xid = buffer.getInt();
061        int secs = buffer.getShort() & 0xffff;
062        short flags = buffer.getShort();
063
064        InetAddress ciaddr = decodeAddress( buffer );
065        InetAddress yiaddr = decodeAddress( buffer );
066        InetAddress siaddr = decodeAddress( buffer );
067        InetAddress giaddr = decodeAddress( buffer );
068
069        byte[] chaddr = decodeBytes( buffer, 16 );
070
071        String sname = decodeString( buffer, 64 );
072        String file = decodeString( buffer, 128 );
073
074        OptionsField options = decodeOptions( buffer );
075
076        // message type option: may be null if option isn't set (BOOTP)
077        DhcpMessageType mto = ( DhcpMessageType ) options.get( DhcpMessageType.class );
078
079        return new DhcpMessage( null != mto ? mto.getType() : null, op, new HardwareAddress( htype, hlen, chaddr ),
080            hops, xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr, sname, file, options );
081    }
082
083
084    /**
085     * @param buffer
086     * @param len
087     * @return
088     */
089    private static byte[] decodeBytes( ByteBuffer buffer, int len )
090    {
091        byte[] bytes = new byte[len];
092        buffer.get( bytes );
093        return bytes;
094    }
095
096
097    /**
098     * @param buffer
099     * @return
100     */
101    private static String decodeString( ByteBuffer buffer, int len )
102    {
103        byte[] bytes = new byte[len];
104        buffer.get( bytes );
105
106        // find zero-terminator
107        int slen = 0;
108
109        while ( bytes[slen] != 0 )
110        {
111            slen++;
112        }
113
114        try
115        {
116            return new String( bytes, 0, slen, "ASCII" );
117        }
118        catch ( UnsupportedEncodingException e )
119        {
120            throw new RuntimeException( I18n.err( I18n.ERR_635 ), e );
121        }
122    }
123
124
125    /**
126     * Read a 4-byte inet address from the buffer.
127     * 
128     * @param buffer
129     * @return
130     * @throws UnknownHostException
131     */
132    private static InetAddress decodeAddress( ByteBuffer buffer )
133    {
134        byte[] addr = new byte[4];
135        buffer.get( addr );
136
137        try
138        {
139            return InetAddress.getByAddress( addr );
140        }
141        catch ( UnknownHostException e )
142        {
143            // should not happen
144            return null;
145        }
146    }
147
148    private static final byte[] VENDOR_MAGIC_COOKIE =
149        { ( byte ) 99, ( byte ) 130, ( byte ) 83, ( byte ) 99 };
150
151
152    public OptionsField decodeOptions( ByteBuffer message ) throws DhcpException
153    {
154        byte[] magicCookie = new byte[4];
155        message.get( magicCookie );
156
157        if ( !Arrays.equals( VENDOR_MAGIC_COOKIE, magicCookie ) )
158        {
159            throw new DhcpException( "Parse exception." );
160        }
161
162        byte code;
163        byte length;
164        byte[] value;
165
166        OptionsField options = new OptionsField();
167
168        while ( true )
169        {
170            code = message.get();
171
172            if ( code == 0 ) // pad option
173            {
174                continue;
175            }
176
177            if ( code == -1 ) // end option
178            {
179                break;
180            }
181
182            length = message.get();
183            value = new byte[length];
184            message.get( value );
185
186            options.add( getOptionInstance( code, value ) );
187        }
188
189        return options;
190    }
191
192
193    private DhcpOption getOptionInstance( int tag, byte[] value ) throws DhcpException
194    {
195        try
196        {
197            Class c = DhcpOption.getClassByTag( tag );
198
199            DhcpOption o = null != c ? ( DhcpOption ) c.newInstance() : new UnrecognizedOption( ( byte ) tag );
200            o.setData( value );
201
202            return o;
203        }
204        catch ( Exception e )
205        {
206            throw new DhcpException( I18n.err( I18n.ERR_636, e.toString() ) );
207        }
208    }
209}