sync with linuxport @ 18013
[xbmc:xbmc-antiquated.git] / tools / EventClients / lib / java / src / org / xbmc / eventclient / Packet.java
1 package org.xbmc.eventclient;
2 import java.io.IOException;
3 import java.net.DatagramPacket;
4 import java.net.DatagramSocket;
5 import java.net.InetAddress;
6
7 /**
8  * XBMC Event Client Class
9  * <p>
10  * Implementation of XBMC's UDP based input system.
11  * A set of classes that abstract the various packets that the event server
12  * currently supports. In addition, there's also a class, XBMCClient, that
13  * provides functions that sends the various packets. Use XBMCClient if you
14  * don't need complete control over packet structure.
15  * </p>
16  * <p>
17  * The basic workflow involves:
18  * <ol>
19  * <li>Send a HELO packet</li>
20  * <li>Send x number of valid packets</li>
21  * <li>Send a BYE packet</li>
22  * </ol>
23  * </p>
24  * <p>
25  * IMPORTANT NOTE ABOUT TIMEOUTS:
26  * A client is considered to be timed out if XBMC doesn't received a packet
27  * at least once every 60 seconds. To "ping" XBMC with an empty packet use
28  * PacketPING or XBMCClient.ping(). See the documentation for details.
29  * </p>
30  * <p>
31  * Base class that implements a single event packet.
32  * - Generic packet structure (maximum 1024 bytes per packet)
33  * - Header is 32 bytes long, so 992 bytes available for payload
34  * - large payloads can be split into multiple packets using H4 and H5
35  *   H5 should contain total no. of packets in such a case
36  * - H6 contains length of P1, which is limited to 992 bytes
37  * - if H5 is 0 or 1, then H4 will be ignored (single packet msg)
38  * - H7 must be set to zeros for now
39  * </p>
40  * <pre>
41  *   -----------------------------
42  *   | -H1 Signature ("XBMC")    | - 4  x CHAR                4B
43  *   | -H2 Version (eg. 2.0)     | - 2  x UNSIGNED CHAR       2B
44  *   | -H3 PacketType            | - 1  x UNSIGNED SHORT      2B
45  *   | -H4 Sequence number       | - 1  x UNSIGNED LONG       4B
46  *   | -H5 No. of packets in msg | - 1  x UNSIGNED LONG       4B
47  *   | -H6 Payloadsize of packet | - 1  x UNSIGNED SHORT      2B
48  *   | -H7 Client's unique token | - 1  x UNSIGNED LONG       4B
49  *   | -H8 Reserved              | - 10 x UNSIGNED CHAR      10B
50  *   |---------------------------|
51  *   | -P1 payload               | -
52  *   -----------------------------
53  * </pre>
54  * @author Stefan Agner
55  *
56  */
57 public abstract class Packet {
58         
59         private byte[] sig;
60         private byte[] payload = new byte[0];
61         private byte minver;
62         private byte majver;
63         
64         private short packettype; 
65         
66         
67         private final static short MAX_PACKET_SIZE  = 1024;
68         private final static short HEADER_SIZE      = 32;
69         private final static short MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
70
71         protected final static byte PT_HELO          = 0x01;
72         protected final static byte PT_BYE           = 0x02;
73         protected final static byte PT_BUTTON        = 0x03;
74         protected final static byte PT_MOUSE         = 0x04;
75         protected final static byte PT_PING          = 0x05;
76         protected final static byte PT_BROADCAST     = 0x06;
77         protected final static byte PT_NOTIFICATION  = 0x07;
78         protected final static byte PT_BLOB          = 0x08;
79         protected final static byte PT_LOG           = 0x09;
80         protected final static byte PT_ACTION        = 0x0A;
81         protected final static byte PT_DEBUG         = (byte)0xFF;
82
83         public final static byte ICON_NONE = 0x00;
84         public final static byte ICON_JPEG = 0x01;
85         public final static byte ICON_PNG  = 0x02;
86         public final static byte ICON_GIF  = 0x03;
87         
88         private static int uid = (int)(Math.random()*Integer.MAX_VALUE);
89         
90         /**
91          * This is an Abstract class and cannot be instanced. Please use one of the Packet implementation Classes
92          * (PacketXXX). 
93          * 
94          * Implements an XBMC Event Client Packet. Type is to be specified at creation time, Payload can be added
95          * with the various appendPayload methods. Packet can be sent through UDP-Socket with method "send".
96      * @param packettype Type of Packet (PT_XXX) 
97          */
98         protected Packet(short packettype)
99         {
100                 sig = new byte[] {'X', 'B', 'M', 'C' };
101         minver = 0;
102         majver = 2;
103         this.packettype = packettype;
104         }
105
106         /**
107          * Appends a String to the payload (terminated with 0x00) 
108          * @param payload Payload as String
109          */
110         protected void appendPayload(String payload)
111         {
112                 byte[] payloadarr = payload.getBytes();
113                 int oldpayloadsize = this.payload.length;
114                 byte[] oldpayload = this.payload;
115                 this.payload = new byte[oldpayloadsize+payloadarr.length+1]; // Create new Array with more place (+1 for string terminator)
116                 System.arraycopy(oldpayload, 0, this.payload, 0, oldpayloadsize);
117                 System.arraycopy(payloadarr, 0, this.payload, oldpayloadsize, payloadarr.length);
118         }
119
120         /**
121          * Appends a single Byte to the payload
122          * @param payload Payload
123          */
124         protected void appendPayload(byte payload)
125         {
126                 appendPayload(new byte[] { payload });
127         }
128
129         /**
130          * Appends a Byte-Array to the payload
131          * @param payloadarr Payload
132          */
133         protected void appendPayload(byte[] payloadarr)
134         {
135                 int oldpayloadsize = this.payload.length;
136                 byte[] oldpayload = this.payload;
137                 this.payload = new byte[oldpayloadsize+payloadarr.length];
138                 System.arraycopy(oldpayload, 0, this.payload, 0, oldpayloadsize);
139                 System.arraycopy(payloadarr, 0, this.payload, oldpayloadsize, payloadarr.length);
140         }
141
142         /**
143          * Appends an integer to the payload
144          * @param i Payload
145          */
146         protected void appendPayload(int i) {
147                 appendPayload(intToByteArray(i));
148         }
149
150         /**
151          * Appends a short to the payload
152          * @param s Payload
153          */
154         protected void appendPayload(short s) {
155                 appendPayload(shortToByteArray(s));
156         }
157         
158         /**
159          * Get Number of Packets which will be sent with current Payload...
160          * @return Number of Packets
161          */
162         public int getNumPackets()
163         {
164                 return (int)((payload.length + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE);
165         }
166         
167         /**
168          * Get Header for a specific Packet in this sequence...
169          * @param seq Current sequence number
170          * @param maxseq Maximal sequence number
171          * @param actpayloadsize Payloadsize of this packet
172          * @return Byte-Array with Header information (currently 32-Byte long, see HEADER_SIZE)
173          */
174         private byte[] getHeader(int seq, int maxseq, short actpayloadsize)
175         {
176                 byte[] header = new byte[HEADER_SIZE];
177                 System.arraycopy(sig, 0, header, 0, 4);
178                 header[4] = majver;
179                 header[5] = minver;
180                 byte[] packettypearr = shortToByteArray(this.packettype);
181                 System.arraycopy(packettypearr, 0, header, 6, 2);
182                 byte[] seqarr = intToByteArray(seq);
183                 System.arraycopy(seqarr, 0, header, 8, 4);
184                 byte[] maxseqarr = intToByteArray(maxseq);
185                 System.arraycopy(maxseqarr, 0, header, 12, 4);
186                 byte[] payloadsize = shortToByteArray(actpayloadsize);
187                 System.arraycopy(payloadsize, 0, header, 16, 2);
188                 byte[] uid = intToByteArray(Packet.uid);
189                 System.arraycopy(uid, 0, header, 18, 4);
190                 byte[] reserved = new byte[10];
191                 System.arraycopy(reserved, 0, header, 22, 10);
192                 
193                 return header;
194         }
195         
196         /**
197          * Generates the whole UDP-Message with Header and Payload of a specific Packet in sequence
198          * @param seq Current sequence number
199          * @return Byte-Array with UDP-Message
200          */
201         private byte[] getUDPMessage(int seq)
202         {
203                 int maxseq = (int)((payload.length + (MAX_PAYLOAD_SIZE - 1)) / MAX_PAYLOAD_SIZE);
204                 if(seq > maxseq)
205                         return null;
206                 
207                 short actpayloadsize;
208                 
209                 if(seq == maxseq)
210                         actpayloadsize = (short)(payload.length%MAX_PAYLOAD_SIZE);
211                         
212                 else
213                         actpayloadsize = (short)MAX_PAYLOAD_SIZE;
214
215                 byte[] pack = new byte[HEADER_SIZE+actpayloadsize];
216                 
217                 System.arraycopy(getHeader(seq, maxseq, actpayloadsize), 0, pack, 0, HEADER_SIZE);
218                 System.arraycopy(payload, (seq-1)*MAX_PAYLOAD_SIZE, pack, HEADER_SIZE, actpayloadsize);
219                 
220                 return pack;
221         }
222         
223         /**
224          * Sends this packet to the EventServer
225          * @param adr Address of the EventServer
226          * @param port Port of the EventServer
227          * @throws IOException
228          */
229         public void send(InetAddress adr, int port) throws IOException
230         {
231                 int maxseq = getNumPackets();
232                 DatagramSocket s = new DatagramSocket();
233                 
234                 // For each Packet in Sequence...
235                 for(int seq=1;seq<=maxseq;seq++)
236                 {
237                         // Get Message and send them...
238                         byte[] pack = getUDPMessage(seq);
239                         DatagramPacket p = new DatagramPacket(pack, pack.length);
240                         p.setAddress(adr);
241                         p.setPort(port);
242                         s.send(p);
243                 }
244         }
245         
246         /**
247          * Helper Method to convert an integer to a Byte array
248          * @param value
249          * @return Byte-Array
250          */
251         private static final byte[] intToByteArray(int value) {
252                   return new byte[] {
253                           (byte)(value >>> 24),
254                           (byte)(value >>> 16),
255                           (byte)(value >>> 8),
256                           (byte)value};
257         }
258
259         /**
260          * Helper Method to convert an short to a Byte array
261          * @param value
262          * @return Byte-Array
263          */
264         private static final byte[] shortToByteArray(short value) {
265         return new byte[] {
266                 (byte)(value >>> 8),
267                 (byte)value};
268         }
269
270         
271 }