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