1 /*
2  * Created on Feb 19, 2005
3  * Created by Alon Rohter
4  * Copyright (C) Azureus Software, Inc, All Rights Reserved.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  *
18  */
19 
20 package com.aelitis.azureus.core.peermanager.messaging.azureus;
21 
22 import java.util.HashMap;
23 import java.util.Map;
24 
25 import org.gudy.azureus2.core3.util.DirectByteBuffer;
26 import org.gudy.azureus2.core3.util.DirectByteBufferPool;
27 import org.gudy.azureus2.core3.util.RandomUtils;
28 
29 import com.aelitis.azureus.core.networkmanager.RawMessage;
30 import com.aelitis.azureus.core.networkmanager.impl.RawMessageImpl;
31 import com.aelitis.azureus.core.peermanager.messaging.Message;
32 import com.aelitis.azureus.core.peermanager.messaging.MessageException;
33 import com.aelitis.azureus.core.peermanager.messaging.MessageManager;
34 import com.aelitis.azureus.core.peermanager.messaging.bittorrent.*;
35 
36 
37 
38 
39 /**
40  * Factory for handling AZ message creation.
41  * NOTE: wire format: [total message length] + [id length] + [id bytes] + [version byte] + [payload bytes]
42  */
43 public class AZMessageFactory {
44   public static final byte MESSAGE_VERSION_INITIAL				= BTMessageFactory.MESSAGE_VERSION_INITIAL;
45   public static final byte MESSAGE_VERSION_SUPPORTS_PADDING		= BTMessageFactory.MESSAGE_VERSION_SUPPORTS_PADDING;
46 
47   public static final int AZ_HANDSHAKE_PAD_MAX		= 64;
48   public static final int SMALL_PAD_MAX				= 8;
49   public static final int BIG_PAD_MAX				= 20;
50 
51   private static final byte bss = DirectByteBuffer.SS_MSG;
52 
53 
54 
55   private static final Map<String,LegacyData> legacy_data = new HashMap<String,LegacyData>();
56   static {
legacy_data.put( BTMessage.ID_BT_CHOKE, new LegacyData( RawMessage.PRIORITY_HIGH, true, new Message[]{new BTUnchoke((byte)0)}))57     legacy_data.put( BTMessage.ID_BT_CHOKE, new LegacyData( RawMessage.PRIORITY_HIGH, true, new Message[]{new BTUnchoke((byte)0)})); // with support for fast extension we don't cancel outstanding piece data, new BTPiece(-1, -1, null,(byte)0 )} ) );
legacy_data.put( BTMessage.ID_BT_UNCHOKE, new LegacyData( RawMessage.PRIORITY_NORMAL, true, new Message[]{new BTChoke((byte)0)} ) )58     legacy_data.put( BTMessage.ID_BT_UNCHOKE, new LegacyData( RawMessage.PRIORITY_NORMAL, true, new Message[]{new BTChoke((byte)0)} ) );
legacy_data.put( BTMessage.ID_BT_INTERESTED, new LegacyData( RawMessage.PRIORITY_HIGH, true, new Message[]{new BTUninterested((byte)0)} ) )59     legacy_data.put( BTMessage.ID_BT_INTERESTED, new LegacyData( RawMessage.PRIORITY_HIGH, true, new Message[]{new BTUninterested((byte)0)} ) );
legacy_data.put( BTMessage.ID_BT_UNINTERESTED, new LegacyData( RawMessage.PRIORITY_NORMAL, false, new Message[]{new BTInterested((byte)0)} ) )60     legacy_data.put( BTMessage.ID_BT_UNINTERESTED, new LegacyData( RawMessage.PRIORITY_NORMAL, false, new Message[]{new BTInterested((byte)0)} ) );
legacy_data.put( BTMessage.ID_BT_HAVE, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) )61     legacy_data.put( BTMessage.ID_BT_HAVE, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) );
legacy_data.put( BTMessage.ID_BT_BITFIELD, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) )62     legacy_data.put( BTMessage.ID_BT_BITFIELD, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) );
legacy_data.put( BTMessage.ID_BT_HAVE_ALL, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) )63     legacy_data.put( BTMessage.ID_BT_HAVE_ALL, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) );
legacy_data.put( BTMessage.ID_BT_HAVE_NONE, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) )64     legacy_data.put( BTMessage.ID_BT_HAVE_NONE, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) );
legacy_data.put( BTMessage.ID_BT_REQUEST, new LegacyData( RawMessage.PRIORITY_NORMAL, true, null ) )65     legacy_data.put( BTMessage.ID_BT_REQUEST, new LegacyData( RawMessage.PRIORITY_NORMAL, true, null ) );
legacy_data.put( BTMessage.ID_BT_REJECT_REQUEST, new LegacyData( RawMessage.PRIORITY_NORMAL, true, null ) )66     legacy_data.put( BTMessage.ID_BT_REJECT_REQUEST, new LegacyData( RawMessage.PRIORITY_NORMAL, true, null ) );
legacy_data.put( BTMessage.ID_BT_PIECE, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) )67     legacy_data.put( BTMessage.ID_BT_PIECE, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) );
legacy_data.put( BTMessage.ID_BT_CANCEL, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) )68     legacy_data.put( BTMessage.ID_BT_CANCEL, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) );
legacy_data.put( BTMessage.ID_BT_HANDSHAKE, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) )69     legacy_data.put( BTMessage.ID_BT_HANDSHAKE, new LegacyData( RawMessage.PRIORITY_HIGH, true, null ) );
legacy_data.put( BTMessage.ID_BT_KEEP_ALIVE, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) )70     legacy_data.put( BTMessage.ID_BT_KEEP_ALIVE, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) );
legacy_data.put( BTMessage.ID_BT_DHT_PORT, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) )71     legacy_data.put( BTMessage.ID_BT_DHT_PORT, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) );
legacy_data.put( BTMessage.ID_BT_SUGGEST_PIECE, new LegacyData( RawMessage.PRIORITY_NORMAL, true, null ) )72     legacy_data.put( BTMessage.ID_BT_SUGGEST_PIECE, new LegacyData( RawMessage.PRIORITY_NORMAL, true, null ) );
legacy_data.put( BTMessage.ID_BT_ALLOWED_FAST, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) )73     legacy_data.put( BTMessage.ID_BT_ALLOWED_FAST, new LegacyData( RawMessage.PRIORITY_LOW, false, null ) );
74   }
75 
76 
77 
78   /**
79    * Initialize the factory, i.e. register the messages with the message manager.
80    */
init()81   public static void init() {
82     try {
83       MessageManager.getSingleton().registerMessageType( new AZHandshake( new byte[20], null, null, "", "", 0, 0, 0, null, 0, new String[0], new byte[0], 0, MESSAGE_VERSION_SUPPORTS_PADDING,false ) );
84       MessageManager.getSingleton().registerMessageType( new AZPeerExchange( new byte[20], null, null, MESSAGE_VERSION_SUPPORTS_PADDING ));
85       MessageManager.getSingleton().registerMessageType( new AZRequestHint( -1, -1, -1, -1, MESSAGE_VERSION_SUPPORTS_PADDING ));
86       MessageManager.getSingleton().registerMessageType( new AZHave( new int[0], MESSAGE_VERSION_SUPPORTS_PADDING ));
87       MessageManager.getSingleton().registerMessageType( new AZBadPiece( -1, MESSAGE_VERSION_SUPPORTS_PADDING ));
88       MessageManager.getSingleton().registerMessageType( new AZStatRequest( null, MESSAGE_VERSION_SUPPORTS_PADDING ));
89       MessageManager.getSingleton().registerMessageType( new AZStatReply( null, MESSAGE_VERSION_SUPPORTS_PADDING ));
90       MessageManager.getSingleton().registerMessageType( new AZMetaData( null, null, MESSAGE_VERSION_SUPPORTS_PADDING ));
91 
92       /*
93       MessageManager.getSingleton().registerMessageType( new AZSessionSyn( new byte[20], -1, null) );
94       MessageManager.getSingleton().registerMessageType( new AZSessionAck( new byte[20], -1, null) );
95       MessageManager.getSingleton().registerMessageType( new AZSessionEnd( new byte[20], "" ) );
96       MessageManager.getSingleton().registerMessageType( new AZSessionBitfield( -1, null ) );
97       MessageManager.getSingleton().registerMessageType( new AZSessionCancel( -1, -1, -1, -1 ) );
98       MessageManager.getSingleton().registerMessageType( new AZSessionHave( -1, new int[]{-1} ) );
99       MessageManager.getSingleton().registerMessageType( new AZSessionPiece( -1, -1, -1, null ) );
100       MessageManager.getSingleton().registerMessageType( new AZSessionRequest( -1, (byte)-1, -1, -1, -1 ) );
101       */
102     }
103     catch( MessageException me ) {  me.printStackTrace();  }
104   }
105 
106 
107   /**
108    * Register a generic map payload type with the factory.
109    * @param type_id to register
110    * @throws MessageException on registration error
111    */
registerGenericMapPayloadMessageType( String type_id )112   public static void registerGenericMapPayloadMessageType( String type_id ) throws MessageException {
113   	MessageManager.getSingleton().registerMessageType( new AZGenericMapPayload( type_id, null, MESSAGE_VERSION_INITIAL ) );
114   }
115 
116 
117 
118   /**
119    * Construct a new AZ message instance from the given message raw byte stream.
120    * @param stream_payload data
121    * @return decoded/deserialized AZ message
122    * @throws MessageException if message creation failed.
123    * NOTE: Does not auto-return to buffer pool the given direct buffer on thrown exception.
124    */
createAZMessage( DirectByteBuffer stream_payload )125   public static Message createAZMessage( DirectByteBuffer stream_payload ) throws MessageException {
126     int id_length = stream_payload.getInt( bss );
127 
128     if( id_length < 1 || id_length > 1024 || id_length > stream_payload.remaining( bss ) - 1 ) {
129       byte bt_id = stream_payload.get( (byte)0, 0 );
130       throw new MessageException( "invalid AZ id length given: " +id_length+ ", stream_payload.remaining(): " +stream_payload.remaining( bss )+ ", BT id?=" +bt_id );
131     }
132 
133     byte[] id_bytes = new byte[ id_length ];
134 
135     stream_payload.get( bss, id_bytes );
136 
137     	// if only the version came first we could save a lot of space by changing the id length + id....
138 
139     	// in the meantime we overload the version byte to have a version number and flags
140     	// flags = top 4 bits, version = bottom 4 bits
141 
142     byte version_and_flags = stream_payload.get( bss );
143 
144     byte version = (byte)( version_and_flags & 0x0f );
145 
146     if ( version >= MESSAGE_VERSION_SUPPORTS_PADDING ){
147 
148     	byte	flags =  (byte)(( version_and_flags >> 4 ) & 0x0f );
149 
150     	if ( ( flags & 0x01 ) != 0 ){
151 
152     		short padding_length = stream_payload.getShort( bss );
153 
154     		byte[]	padding = new byte[padding_length];
155 
156     		stream_payload.get( bss, padding );
157     	}
158     }
159 
160     return MessageManager.getSingleton().createMessage( id_bytes, stream_payload, version );
161   }
162 
163 
164 
165 
166   /**
167    * Create the proper AZ raw message from the given base message.
168    * @param base_message to create from
169    * @return AZ raw message
170    */
createAZRawMessage( Message base_message, int padding_mode )171   public static RawMessage createAZRawMessage( Message base_message, int padding_mode ) {
172     byte[] id_bytes = base_message.getIDBytes();
173     byte version = base_message.getVersion();
174 
175     DirectByteBuffer[] payload = base_message.getData();
176 
177     int payload_size = 0;
178     for( int i=0; i < payload.length; i++ ) {
179       payload_size += payload[i].remaining( bss );
180     }
181 
182     //create and fill header buffer
183 
184     DirectByteBuffer header;
185 
186     if ( version >= MESSAGE_VERSION_SUPPORTS_PADDING ){
187 
188     	boolean enable_padding = padding_mode != AZMessageEncoder.PADDING_MODE_NONE;
189 
190     	short 	padding_length;
191 
192     	if ( enable_padding ){
193 
194     		if ( padding_mode == AZMessageEncoder.PADDING_MODE_MINIMAL ){
195 
196        			padding_length = (short)( RandomUtils.nextInt( SMALL_PAD_MAX  ));
197 
198     		}else{
199 
200     			padding_length = (short)( RandomUtils.nextInt( payload_size>256?SMALL_PAD_MAX:BIG_PAD_MAX ));
201     		}
202 
203     		if ( padding_length == 0 ){
204 
205     			enable_padding = false;
206     		}
207     	}else{
208 
209     		padding_length = 0;
210     	}
211 
212     	byte	flags = enable_padding?(byte)0x01:(byte)0x00;
213 
214     	int	header_size = 4 + 4 + id_bytes.length + 1 + (enable_padding?(2+padding_length):0);
215 
216         header = DirectByteBufferPool.getBuffer( DirectByteBuffer.AL_MSG_AZ_HEADER, header_size );
217 
218 	    header.putInt( bss, header_size - 4 + payload_size );
219 	    header.putInt( bss, id_bytes.length );
220 	    header.put( bss, id_bytes );
221 
222 	    byte version_and_flags = (byte)( ( flags << 4 ) | version );
223 
224 	    header.put( bss, version_and_flags );
225 
226 	    if ( enable_padding ){
227 
228 	    	byte[]	padding = new byte[padding_length];
229 
230 	    	header.putShort( bss, padding_length );
231 	    	header.put( bss, padding );
232 	    }
233     }else{
234 
235 	   	int	header_size = 4 + 4 + id_bytes.length + 1;
236 
237 	    header = DirectByteBufferPool.getBuffer( DirectByteBuffer.AL_MSG_AZ_HEADER, header_size );
238 
239 	    header.putInt( bss, header_size - 4 + payload_size );
240 	    header.putInt( bss, id_bytes.length );
241 	    header.put( bss, id_bytes );
242 	    header.put( bss, version );
243     }
244 
245     header.flip( bss );
246 
247     DirectByteBuffer[] raw_buffs = new DirectByteBuffer[ payload.length + 1 ];
248     raw_buffs[0] = header;
249     for( int i=0; i < payload.length; i++ ) {
250       raw_buffs[i+1] = payload[i];
251     }
252 
253     String message_id = base_message.getID();
254 
255     LegacyData ld = (LegacyData)legacy_data.get( message_id );  //determine if a legacy BT message
256 
257     if( ld != null ) {  //legacy message, use pre-configured values
258       return new RawMessageImpl( base_message, raw_buffs, ld.priority, ld.is_no_delay, ld.to_remove );
259     }
260 
261     	// these should really be properties of the message...
262 
263     int 	priority;
264     boolean	no_delay	= true;
265 
266     if ( message_id == AZMessage.ID_AZ_HANDSHAKE ){
267 
268     		// handshake needs to go out first - if not high then bitfield can get in front of it...
269 
270     	priority = RawMessage.PRIORITY_HIGH;
271 
272     }else if ( message_id == AZMessage.ID_AZ_HAVE ){
273 
274     	priority 	= RawMessage.PRIORITY_LOW;
275     	no_delay	= false;
276 
277     }else{
278 
279     	   //standard message, ensure that protocol messages have wire priority over data payload messages
280 
281     	priority = base_message.getType() == Message.TYPE_DATA_PAYLOAD ? RawMessage.PRIORITY_LOW : RawMessage.PRIORITY_NORMAL;
282     }
283 
284     return new RawMessageImpl( base_message, raw_buffs, priority, no_delay, null );
285   }
286 
287 
288 
289 
290 
291 
292   protected static class LegacyData {
293   	protected final int priority;
294     protected final boolean is_no_delay;
295     protected final Message[] to_remove;
296 
LegacyData( int prio, boolean no_delay, Message[] remove )297     protected LegacyData( int prio, boolean no_delay, Message[] remove ) {
298       this.priority = prio;
299       this.is_no_delay = no_delay;
300       this.to_remove = remove;
301     }
302   }
303 
304 }
305