a40ea55693e8f2e6161b2e108e06026a8e6a9b61
[WeStealzYourDataz.git] / src / uk / ac / ntu / n0521366 / wsyd / libs / net / NetworkServerUDP.java
1 /*
2  * The MIT License
3  *
4  * Copyright 2015 TJ <hacker@iam.tj>.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 package uk.ac.ntu.n0521366.wsyd.libs.net;
25
26 import java.text.MessageFormat;
27 import java.io.IOException;
28 import java.io.ObjectStreamException;
29 import java.net.InetSocketAddress;
30 import java.net.DatagramSocket;
31 import java.net.DatagramPacket;
32 import java.net.SocketException;
33 import java.net.SocketTimeoutException;
34 import java.util.logging.Level;
35 import java.util.logging.Logger;
36 import java.util.logging.LogRecord;
37 import uk.ac.ntu.n0521366.wsyd.libs.message.MessageLogRecord;
38
39
40
41 /**
42  * Dual-use multithreading network UDP server that can be used stand-alone
43  * or in a Swing GUI application as a background worker thread.
44  *
45  * @see NetworkServerAbstract
46  * @author TJ <hacker@iam.tj>
47  */
48 public class NetworkServerUDP extends NetworkServerAbstract {
49     /**
50      * Server socket.
51      */
52     private DatagramSocket _datagramSocket = null;
53     
54     /**
55      * Maximum size of UDP packet payload
56      */
57     public static final int UDP_PAYLOAD_SIZE_MAX =  65507;
58     
59     /**
60      * Construct the server with a Logger.
61      * 
62      * No socket is opened.
63      * 
64      * @param socketAddress The socket to listen on
65      * @param title source identifier for use in log messages and sent NetworkMessage objects
66      * @param logger An instance of Logger to be used by all objects of this class
67      */
68     public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title, Logger logger) {
69         super(socketAddress, title, logger);
70     }
71
72     /**
73      * Construct the server without a Logger.
74      * 
75      * No socket is opened.
76      * 
77      * @param socketAddress The socket to listen on
78      * @param title source identifier for use in log messages and sent NetworkMessage objects
79      */
80     public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title) {
81         super(socketAddress, title);
82     }
83
84     /**
85      * Get the DatagramSocket for this service.
86      * 
87      * @return the socket
88      */
89     public DatagramSocket getSocket() {
90         return this._datagramSocket;
91     }
92
93     /**
94      * Open the socket ready for accepting packets.
95      * 
96      * It should also set a reasonable socket timeout with a call to setSoTimeout()
97      * to prevent unnecessary blocking.
98      * 
99      * @throws SocketException 
100      */
101     @Override
102     public  void serverOpen() throws SocketException {
103         _datagramSocket = new DatagramSocket(_socketAddress.getPort(), _socketAddress.getAddress());
104         _datagramSocket.setSoTimeout(100); // 1/10th second blocking timeout on receive() 
105     }
106     
107     /**
108      * Close the socket.
109      * 
110      * @throws SocketException
111      */
112     @Override
113     public void serverClose() throws SocketException {
114         // use 'this' to ensure sub-classes refer to their own '_datagramSocket' when inheriting this method
115         if (this._datagramSocket != null)
116             this._datagramSocket.close();
117     }
118
119     /**
120      * Accept packet from remote hosts.
121      * 
122      * 
123      * 
124      * @return true if the server should continue listening
125      */
126     @Override
127     public boolean serverListen() {
128         boolean result = false;
129         byte[] dataReceive = new byte[UDP_PAYLOAD_SIZE_MAX];
130         DatagramPacket packetReceive = new DatagramPacket(dataReceive, dataReceive.length);
131         NetworkMessage messageReceived;
132         try {
133             /* blocks waiting for packet until socket timeout expires, when SocketTimeOut
134              * exception is thrown.
135             */
136             if (this.getSocket() != null)
137                 this.getSocket().receive(packetReceive);
138             else
139                 throw new SocketTimeoutException("No socket available!");
140             
141             // packet was received
142             messageReceived = NetworkMessage.deserialize(packetReceive.getData());
143             
144             // prevent loopbacks
145             if (!messageReceived.getSender().equals(_title)) {
146             
147                 // add or update the last-seen time of the Sender host in the known services map
148                 LastSeenHost host = new LastSeenHost((InetSocketAddress)packetReceive.getSocketAddress());
149                 this._serviceToHostMap.replace(messageReceived.getSender(), host);
150
151                 /* TODO: serverListen() message may be an ACKnowledgement which shouldn't be pubished...
152                  * But it might be useful to keep track of ACKs in a separate queue that the owner thread can query to check
153                  * a message was delivered
154                  */
155                 // pass the message to the process() method in the Owner Thread
156                 publish(messageReceived);
157
158                 // create and send an acknowledgement of receipt
159                 NetworkMessage ack = NetworkMessage.createNetworkMessage("ACK", messageReceived.getSender(), null);
160                 serverSend(ack);
161
162                 // FIXME: serverListen() can this block be deleted now its handled by serverSend() ?
163                 /*
164                  try {
165                     byte[] dataAck = NetworkMessage.serialize(ack);
166                     DatagramPacket packetAck = new DatagramPacket(dataAck, dataAck.length);
167
168                     // extract remote host address and port for sending acknowledgement
169                     packetAck.setAddress(packetReceive.getAddress());
170                     packetAck.setPort(packetReceive.getPort());
171
172                     // acknowledge receipt
173                     this.getSocket().send(packetAck);
174
175
176                 } catch (IOException e) {
177                     // TODO: serverListen() fill in IOException blank log message
178                     log(Level.WARNING, _title, "", null);
179                 }
180                 */
181
182                 result = true; // successful
183             }
184
185         } catch (SocketTimeoutException e) {
186             result = false; // no packet received
187             if (this._simulate) {
188                 LogRecord record = new LogRecord(Level.FINEST, "Simulated received message");
189                 record.setSourceClassName("Simulator");
190                 record.setMillis(System.currentTimeMillis());
191                 MessageLogRecord m = new MessageLogRecord(record);
192                 publish(new NetworkMessage("Log","Simulator",m));
193                 result = true;
194             }
195             
196         } catch (ObjectStreamException e) {
197             /* order of these Exception catches is important
198              * Deeper sub-classes must be listed before their super classes
199              */
200             // TODO: serverListen() add ObjectStreamException handler
201
202         } catch (IOException e) {
203             // TODO: serverListen() add IOException handler
204
205         } catch (ClassNotFoundException e) {
206             // TODO: serverListen() add ClassNotFoundException handler
207         }
208
209         return result;
210     }
211
212     /**
213      * Send an unsolicited message to a remote service.
214      * 
215      * This method is called by the main worker loop if there is a message to
216      * be sent.
217      * 
218      * @param message must have its _serviceTarget parameter set
219      * @return true if the message was sent
220      */
221     @Override
222     protected boolean serverSend(NetworkMessage message) {
223         boolean result = false;
224
225         if (message != null) {
226             LastSeenHost host = _serviceToHostMap.get(message.getTarget());
227             if (host != null) {
228                 InetSocketAddress address = host.address;
229                 if (address != null) {
230                     message.setSender(_title);
231                     try {
232                         byte[] dataSend = NetworkMessage.serialize(message);
233                         DatagramPacket packetSend = new DatagramPacket(dataSend, dataSend.length);
234                         // set target's remote host address and port
235                         packetSend.setAddress(address.getAddress());
236                         packetSend.setPort(address.getPort());
237
238                         // acknowledge receipt
239                         this.getSocket().send(packetSend);
240
241                         result = true; // successful
242                     } catch (IOException e) {
243                         // TODO: serverSend() add IOException handler
244                         e.printStackTrace();
245                     }
246                 }
247             } else {
248                 log(Level.WARNING, _title, MessageFormat.format("Unable to send message for \"{0}\" to unknown target \"{1}\"", message.getIntent(), message.getTarget()));
249             }
250         }
251         return result;
252     }
253
254     /* XXX: Methods below here all execute on the GUI Event Dispatch Thread */
255     
256     /**
257      * Clean up after doInBackground() has returned.
258      * 
259      * This method will run on the Owner Thread so must complete quickly.
260      */
261     @Override
262     protected  void done() {
263         // TODO: done() implement any clean-up after doInBackground() has returned
264     }
265 }