b514fa8f5d0737d23b053e113906ccd285b7354e
[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 import uk.ac.ntu.n0521366.wsyd.libs.net.ServiceAddressMap;
39
40
41
42 /**
43  * Dual-use multithreading network UDP server that can be used stand-alone
44  * or in a Swing GUI application as a background worker thread.
45  *
46  * @see NetworkServerAbstract
47  * @author TJ <hacker@iam.tj>
48  */
49 public class NetworkServerUDP extends NetworkServerAbstract {
50     /**
51      * Server socket.
52      */
53     private DatagramSocket _datagramSocket = null;
54     
55     /**
56      * Maximum size of UDP packet payload
57      */
58     public static final int UDP_PAYLOAD_SIZE_MAX =  65507;
59     
60     /**
61      * Construct the server with a Logger.
62      * 
63      * No socket is opened.
64      * 
65      * @param socketAddress The socket to listen on
66      * @param title source identifier for use in log messages and sent NetworkMessage objects
67      * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
68      * @param logger An instance of Logger to be used by all objects of this class
69      */
70     public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap, Logger logger) {
71         super(socketAddress, title, serviceToHostMap, logger);
72     }
73
74     /**
75      * Construct the server without a Logger.
76      * 
77      * No socket is opened.
78      * 
79      * @param socketAddress The socket to listen on
80      * @param title source identifier for use in log messages and sent NetworkMessage objects
81      * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
82      */
83     public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap) {
84         super(socketAddress, title, serviceToHostMap);
85     }
86
87     /**
88      * Get the DatagramSocket for this service.
89      * 
90      * @return the socket
91      */
92     public DatagramSocket getSocket() {
93         return this._datagramSocket;
94     }
95
96     /**
97      * Open the socket ready for accepting packets.
98      * 
99      * It should also set a reasonable socket timeout with a call to setSoTimeout()
100      * to prevent unnecessary blocking.
101      * 
102      * @throws SocketException 
103      */
104     @Override
105     public  void serverOpen() throws SocketException {
106         _datagramSocket = new DatagramSocket(_socketAddress.getPort(), _socketAddress.getAddress());
107         _datagramSocket.setSoTimeout(100); // 1/10th second blocking timeout on receive() 
108     }
109     
110     /**
111      * Close the socket.
112      * 
113      * @throws SocketException
114      */
115     @Override
116     public void serverClose() throws SocketException {
117         // use 'this' to ensure sub-classes refer to their own '_datagramSocket' when inheriting this method
118         if (this._datagramSocket != null)
119             this._datagramSocket.close();
120     }
121
122     /**
123      * Accept packet from remote hosts.
124      * 
125      * 
126      * 
127      * @return true if the server should continue listening
128      */
129     @Override
130     public boolean serverListen() {
131         boolean result = false;
132         byte[] dataReceive = new byte[UDP_PAYLOAD_SIZE_MAX];
133         DatagramPacket packetReceive = new DatagramPacket(dataReceive, dataReceive.length);
134         NetworkMessage messageReceived;
135         try {
136             /* blocks waiting for packet until socket timeout expires, when SocketTimeOut
137              * exception is thrown.
138             */
139             if (this.getSocket() != null)
140                 this.getSocket().receive(packetReceive);
141             else
142                 throw new SocketTimeoutException("No socket available!");
143             
144             // packet was received
145             messageReceived = NetworkMessage.deserialize(packetReceive.getData());
146             
147             // prevent loopbacks
148             if (!messageReceived.getSender().equals(_title)) {
149             
150                 // add or update the last-seen time of the Sender host in the known services map
151                 ServiceAddressMap.LastSeenHost host = new ServiceAddressMap.LastSeenHost((InetSocketAddress)packetReceive.getSocketAddress());
152                 this._serviceToHostMap.put(messageReceived.getSender(), host);
153                 log(Level.FINEST, _title, MessageFormat.format("Added \"{0}\" to service map", messageReceived.getSender()));
154
155                 // pass the message to the process() method in the Owner Thread
156                 publish(messageReceived);
157
158                 result = true; // successful
159             }
160
161         } catch (SocketTimeoutException e) {
162             result = false; // no packet received
163             if (this._simulate) {
164                 LogRecord record = new LogRecord(Level.FINEST, "Simulated received message");
165                 record.setSourceClassName("Simulator");
166                 record.setMillis(System.currentTimeMillis());
167                 MessageLogRecord m = new MessageLogRecord(record);
168                 publish(new NetworkMessage("Log","Simulator",m));
169                 result = true;
170             }
171             
172         } catch (ObjectStreamException e) {
173             /* order of these Exception catches is important
174              * Deeper sub-classes must be listed before their super classes
175              */
176             // TODO: serverListen() add ObjectStreamException handler
177             System.err.println("ObjectStreamException");
178
179         } catch (IOException e) {
180             // TODO: serverListen() add IOException handler
181             System.err.println("IOException");
182
183         } catch (ClassNotFoundException e) {
184             // TODO: serverListen() add ClassNotFoundException handler
185             System.err.println("ClassNotFoundException");
186         }
187
188         return result;
189     }
190
191     /**
192      * Send an unsolicited message to a remote service.
193      * 
194      * This method is called by the main worker loop if there is a message to
195      * be sent.
196      * 
197      * @param message must have its _serviceTarget parameter set
198      * @return true if the message was sent
199      */
200     @Override
201     protected boolean serverSend(NetworkMessage message) {
202         boolean result = false;
203
204         if (message != null) {
205             ServiceAddressMap.LastSeenHost host = _serviceToHostMap.get(message.getTarget());
206             if (host != null) {
207                 InetSocketAddress address = host.address;
208                 if (address != null) {
209                     message.setSender(_title);
210                     try {
211                         byte[] dataSend = NetworkMessage.serialize(message);
212                         DatagramPacket packetSend = new DatagramPacket(dataSend, dataSend.length);
213                         // set target's remote host address and port
214                         packetSend.setAddress(address.getAddress());
215                         packetSend.setPort(address.getPort());
216
217                         // acknowledge receipt
218                         this.getSocket().send(packetSend);
219                         log(Level.FINEST, _title,
220                             MessageFormat.format("Sending packet for {0} to {1} ({3}:{4}) from {2}",
221                                 message.getIntent(),
222                                 message.getTarget(),
223                                 message.getSender(),
224                                 packetSend.getAddress().getHostAddress(),
225                                 packetSend.getPort()
226                             )
227                         );
228
229                         result = true; // successful
230                     } catch (IOException e) {
231                         // TODO: serverSend() add IOException handler
232                         e.printStackTrace();
233                     }
234                 }
235             } else {
236                 log(Level.WARNING, _title, MessageFormat.format("Unable to send message for \"{0}\" to unknown target \"{1}\"", message.getIntent(), message.getTarget()));
237             }
238         }
239         return result;
240     }
241
242     /* XXX: Methods below here all execute on the GUI Event Dispatch Thread */
243     
244     /**
245      * Clean up after doInBackground() has returned.
246      * 
247      * This method will run on the Owner Thread so must complete quickly.
248      */
249     @Override
250     protected  void done() {
251         // TODO: done() implement any clean-up after doInBackground() has returned
252     }
253 }