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