| 1 | + | package com.damnvulnerableapp.networking.communication.client; |
| 2 | + | |
| 3 | + | import androidx.annotation.NonNull; |
| 4 | + | |
| 5 | + | import com.damnvulnerableapp.common.configuration.ClientConfiguration; |
| 6 | + | import com.damnvulnerableapp.common.configuration.Configurable; |
| 7 | + | import com.damnvulnerableapp.common.configuration.Configuration; |
| 8 | + | import com.damnvulnerableapp.networking.communication.CommunicationFactory; |
| 9 | + | import com.damnvulnerableapp.networking.exceptions.CommunicationException; |
| 10 | + | import com.damnvulnerableapp.networking.exceptions.ConnectionException; |
| 11 | + | import com.damnvulnerableapp.networking.exceptions.InvalidClientException; |
| 12 | + | import com.damnvulnerableapp.networking.exceptions.MessageParserException; |
| 13 | + | import com.damnvulnerableapp.networking.exceptions.TimeoutException; |
| 14 | + | import com.damnvulnerableapp.networking.messages.Message; |
| 15 | + | import com.damnvulnerableapp.networking.protocol.Protocol; |
| 16 | + | |
| 17 | + | import java.util.LinkedList; |
| 18 | + | import java.util.List; |
| 19 | + | |
| 20 | + | /** |
| 21 | + | * A client that can be used to send requests to a {@link com.damnvulnerableapp.networking.communication.server.Server}. |
| 22 | + | * It is by default independent of any communication - specific implementations, i.e. one may |
| 23 | + | * implement communication via file mappings, socket etc. This client is a wrapper for those |
| 24 | + | * implementations and provides an easy way to handle messages. |
| 25 | + | * |
| 26 | + | * @author Pascal Kühnemann |
| 27 | + | * @see com.damnvulnerableapp.networking.communication.server.Server |
| 28 | + | * @see Configurable |
| 29 | + | * */ |
| 30 | + | public abstract class Client implements Configurable { |
| 31 | + | |
| 32 | + | /** |
| 33 | + | * Type or identifier of the client. This is useful when connecting to a server that cares |
| 34 | + | * about the functionality of a client. E.g. a client could be a "normal" user, an automated |
| 35 | + | * command handler etc. Defaults to {@link ClientType#USER}. |
| 36 | + | * @see ClientType |
| 37 | + | * */ |
| 38 | + | private ClientType type; |
| 39 | + | |
| 40 | + | /** |
| 41 | + | * Wrapping communication layer that lies between the low - level implementation of |
| 42 | + | * communication and this client. It determines how messages are sent, how to handle initial |
| 43 | + | * connects and how to shutdown a connection. |
| 44 | + | * @see Protocol |
| 45 | + | * */ |
| 46 | + | private Protocol protocol; |
| 47 | + | |
| 48 | + | /** |
| 49 | + | * Low - level implementation of communication. This may e.g. be a socket - based or file |
| 50 | + | * mapping - based implementation. Depending on the setting, on implementation may be more |
| 51 | + | * stable than the other. |
| 52 | + | * @see EndPoint |
| 53 | + | * */ |
| 54 | + | private EndPoint endpoint; |
| 55 | + | |
| 56 | + | /** |
| 57 | + | * A list of {@link CommunicationListener} objects that will be called on certain events. Any |
| 58 | + | * method that triggers an event will handle calling the correct callbacks. It may be possible |
| 59 | + | * that an event is fired multiple times, because or parallelism. |
| 60 | + | * @see CommunicationListener |
| 61 | + | * */ |
| 62 | + | private final List<CommunicationListener> listeners; |
| 63 | + | |
| 64 | + | /** |
| 65 | + | * {@link com.damnvulnerableapp.common.configuration.Configuration} class holding information on |
| 66 | + | * low - level - specific details. |
| 67 | + | * @see com.damnvulnerableapp.common.configuration.Configuration |
| 68 | + | * */ |
| 69 | + | private ClientConfiguration configuration; |
| 70 | + | |
| 71 | + | /** |
| 72 | + | * Construct list of {@link CommunicationListener} and this {@link com.damnvulnerableapp.common.configuration.Configuration}. |
| 73 | + | * |
| 74 | + | * This construct will be called, if a new client is created that actually wants to perform |
| 75 | + | * connect operations. |
| 76 | + | * |
| 77 | + | * Do NOT call any constructor of this class by instantiating a subclass. Rather use |
| 78 | + | * {@link CommunicationFactory#createClient()} for creating clients. |
| 79 | + | * */ |
| 80 | + | public Client() { |
| 81 | + | |
| 82 | + | this.listeners = new LinkedList<>(); |
| 83 | + | |
| 84 | + | this.setConfiguration(new ClientConfiguration()); |
| 85 | + | |
| 86 | + | this.type = ClientType.USER; |
| 87 | + | } |
| 88 | + | |
| 89 | + | /** |
| 90 | + | * Construct this client as in {@link Client#Client()}. Additionally, this fixes a low - level |
| 91 | + | * implementation that is already created, i.e. this constructor will be used by a |
| 92 | + | * {@link com.damnvulnerableapp.networking.communication.server.Server} when accepting incoming |
| 93 | + | * client connections. |
| 94 | + | * |
| 95 | + | * Do NOT call any constructor of this class by instantiating a subclass. Rather use |
| 96 | + | * {@link CommunicationFactory#createClient()} for creating clients. |
| 97 | + | * |
| 98 | + | * @param endpoint Low - level implementation constructed by {@link com.damnvulnerableapp.networking.communication.server.Server} |
| 99 | + | * when accepting incoming connection. |
| 100 | + | * */ |
| 101 | + | public Client(EndPoint endpoint) { |
| 102 | + | this(); |
| 103 | + | this.endpoint = endpoint; |
| 104 | + | } |
| 105 | + | |
| 106 | + | /** |
| 107 | + | * Connects to a given target, described by <code>info</code>. If the low - level implementation |
| 108 | + | * is already up and running, this method will omit creating a new instance of {@link EndPoint} |
| 109 | + | * and immediately start with a {@link Protocol#handshake(EndPoint, ClientType)}. After the |
| 110 | + | * {@link Protocol#handshake(EndPoint, ClientType)}, all {@link CommunicationListener#onConnect(Client)} |
| 111 | + | * will be called. |
| 112 | + | * |
| 113 | + | * A {@link com.damnvulnerableapp.networking.communication.server.Server}, which is accepting |
| 114 | + | * incoming client connections, also calls this method, but with an already connected endpoint. |
| 115 | + | * Therefore, for server - side clients, only the handshake and listeners are invoked. |
| 116 | + | * |
| 117 | + | * @param info Information on where and how to connect. This depends on the low - level |
| 118 | + | * implementation of {@link EndPoint}. |
| 119 | + | * @throws ConnectionException If communication partner disconnects unexpectedly, the low - level |
| 120 | + | * implementation unexpectedly fails creating a connection or the |
| 121 | + | * {@link Protocol#handshake(EndPoint, ClientType)} fails. |
| 122 | + | * @throws MessageParserException If parsing a message from {@link Protocol#handshake(EndPoint, ClientType)} |
| 123 | + | * fails. |
| 124 | + | * @throws TimeoutException If connecting to a server or reading exceeds a configured time limit. |
| 125 | + | * @see Protocol |
| 126 | + | * @see EndPoint |
| 127 | + | * */ |
| 128 | + | public void connect(ConnectionInformation info) throws CommunicationException { |
| 129 | + | |
| 130 | + | // If endpoint is not in connected state: connect it |
| 131 | + | if (!this.isConnected()) { |
| 132 | + | |
| 133 | + | this.endpoint = this.createEndPoint(); |
| 134 | + | this.setConfiguration(this.configuration); |
| 135 | + | |
| 136 | + | final int oldTimeout = this.endpoint.getTimeout(); |
| 137 | + | this.endpoint.setTimeout(info.getTimeout()); |
| 138 | + | this.endpoint.connect(info); |
| 139 | + | this.endpoint.setTimeout(oldTimeout); |
| 140 | + | } |
| 141 | + | |
| 142 | + | try { |
| 143 | + | |
| 144 | + | final int oldTimeout = this.endpoint.getTimeout(); |
| 145 | + | this.endpoint.setTimeout(this.configuration.getHandshakeTimeout()); |
| 146 | + | this.protocol.handshake(this.endpoint, this.type); |
| 147 | + | this.endpoint.setTimeout(oldTimeout); |
| 148 | + | } catch (InvalidClientException ignored) {} |
| 149 | + | |
| 150 | + | for (CommunicationListener listener : this.listeners) |
| 151 | + | listener.onConnect(this); |
| 152 | + | } |
| 153 | + | |
| 154 | + | /** |
| 155 | + | * Disconnect this client from established connection. Before shutting down the connection |
| 156 | + | * and closing this {@link EndPoint}, all {@link CommunicationListener#onDisconnect(Client)} |
| 157 | + | * will be called. |
| 158 | + | * |
| 159 | + | * Before closing resources used in the internal {@link EndPoint} instance, {@link Protocol#shutdown(EndPoint)} |
| 160 | + | * is used to aim for a graceful shutdown. As this method may be called on closed clients |
| 161 | + | * as well in order to insure that all internal resources are closed, all exceptions of |
| 162 | + | * {@link Protocol#shutdown(EndPoint)} will be discarded. |
| 163 | + | * |
| 164 | + | * This method may also be called, if the connection is already closed, i.e. to free resources |
| 165 | + | * used by internal objects. |
| 166 | + | * |
| 167 | + | * @see Protocol |
| 168 | + | * @see EndPoint |
| 169 | + | * */ |
| 170 | + | public void disconnect() { |
| 171 | + | |
| 172 | + | for (CommunicationListener listener : this.listeners) |
| 173 | + | listener.onDisconnect(this); |
| 174 | + | |
| 175 | + | if (this.isConnected()) { |
| 176 | + | try { |
| 177 | + | // Hope that gracefully shutting down connections works... |
| 178 | + | this.protocol.shutdown(this.endpoint); |
| 179 | + | } catch (CommunicationException ignored) { |
| 180 | + | } |
| 181 | + | } |
| 182 | + | if (this.endpoint != null) |
| 183 | + | this.endpoint.disconnect(); |
| 184 | + | } |
| 185 | + | |
| 186 | + | /** |
| 187 | + | * Checks whether the underlying {@link EndPoint} is still connected to a server. |
| 188 | + | * |
| 189 | + | * @return <code>true</code>, if {@link EndPoint} is connected; <code>false</code> otherwise. |
| 190 | + | * @see EndPoint#isConnected() |
| 191 | + | * */ |
| 192 | + | public boolean isConnected() { |
| 193 | + | if (this.endpoint != null) |
| 194 | + | return this.endpoint.isConnected(); |
| 195 | + | return false; |
| 196 | + | } |
| 197 | + | |
| 198 | + | /** |
| 199 | + | * Sends a {@link Message} to a communication partner using a fixed {@link Protocol}. Before |
| 200 | + | * sending the message, if this client is still connected, all {@link CommunicationListener#onSend(Client, Message)} |
| 201 | + | * will be called. |
| 202 | + | * |
| 203 | + | * If there is a connection issue, i.e. internally some method throws {@link ConnectionException}, |
| 204 | + | * then this client will automatically be disconnected. |
| 205 | + | * |
| 206 | + | * @param message Message to send to the communication partner using a specified protocol. |
| 207 | + | * Setting this to <code>null</code> will result in protocol - dependent handling |
| 208 | + | * of the message. |
| 209 | + | * @throws ConnectionException If communication partner disconnects unexpectedly, the low - level |
| 210 | + | * implementation unexpectedly fails receiving a message or unexpectedly |
| 211 | + | * fails sending a message. Also the low - level implementation may |
| 212 | + | * be not connected anymore due to unknown reasons. This client is |
| 213 | + | * automatically disconnected. Also thrown if client was not connected |
| 214 | + | * via {@link Client#connect(ConnectionInformation)}. |
| 215 | + | * @throws MessageParserException If <code>message</code> is of an invalid format w.r.t. the |
| 216 | + | * underlying protocol. |
| 217 | + | * @throws TimeoutException If sending a message exceeds a preconfigured time limit. This may be |
| 218 | + | * linked to waiting for ACK messages after a sent message. |
| 219 | + | * @throws CommunicationException If any communication - related error occurs. It will enforce |
| 220 | + | * a disconnect. |
| 221 | + | * @see Protocol |
| 222 | + | * @see Message |
| 223 | + | * @see com.damnvulnerableapp.networking.messages.MessageParser |
| 224 | + | * */ |
| 225 | + | public void send(Message message) throws CommunicationException { |
| 226 | + | |
| 227 | + | if (!this.isConnected()) |
| 228 | + | throw new ConnectionException("Client is not connected."); |
| 229 | + | |
| 230 | + | for (CommunicationListener listener : this.listeners) |
| 231 | + | listener.onSend(this, message); |
| 232 | + | |
| 233 | + | // This will throw an exception on disconnected endpoint. |
| 234 | + | try { |
| 235 | + | this.protocol.send(this.endpoint, message); |
| 236 | + | } catch (CommunicationException e) { |
| 237 | + | |
| 238 | + | // Handle disconnect |
| 239 | + | this.disconnect(); |
| 240 | + | throw e; |
| 241 | + | } |
| 242 | + | } |
| 243 | + | |
| 244 | + | /** |
| 245 | + | * Receives a message by blocking until there is a message available. |
| 246 | + | * |
| 247 | + | * If there is a connection issue, i.e. internally some method throws {@link ConnectionException}, |
| 248 | + | * then this client will automatically be disconnected. |
| 249 | + | * |
| 250 | + | * Receiving a message will trigger all {@link CommunicationListener#onReceive(Client, Message)} |
| 251 | + | * and thus a client can post requests either by actively using {@link Client#send(Message)} or |
| 252 | + | * reactively by calling {@link Client#send(Message)} in {@link CommunicationListener#onReceive(Client, Message)}. |
| 253 | + | * |
| 254 | + | * E.g. in {@link com.damnvulnerableapp.networking.communication.server.NetworkServer}, there the |
| 255 | + | * clients will permanently listen for incoming requests, forward the requests to |
| 256 | + | * {@link CommunicationListener#onReceive(Client, Message)}, which in turn may reactively answer |
| 257 | + | * the requests with corresponding responses messages sent via {@link Client#send(Message)}. |
| 258 | + | * |
| 259 | + | * @return Message If a valid message is received, then this will be returned; null otherwise. |
| 260 | + | * @throws ConnectionException If communication partner disconnects unexpectedly, the low - level |
| 261 | + | * implementation unexpectedly fails receiving a message or unexpectedly |
| 262 | + | * fails sending a message. Also the low - level implementation may |
| 263 | + | * be not connected anymore due to unknown reasons. This client is |
| 264 | + | * automatically disconnected. Also thrown if client was not connected |
| 265 | + | * via {@link Client#connect(ConnectionInformation)}. |
| 266 | + | * @throws MessageParserException If a received message is of an invalid format. |
| 267 | + | * @throws TimeoutException If waiting for an incoming message exceeds a preconfigured time limit. |
| 268 | + | * @throws CommunicationException If any communication - related error occurs. |
| 269 | + | * @see Protocol |
| 270 | + | * @see Message |
| 271 | + | * @see com.damnvulnerableapp.networking.messages.MessageParser |
| 272 | + | * */ |
| 273 | + | public Message receive() throws CommunicationException { |
| 274 | + | |
| 275 | + | if (!this.isConnected()) |
| 276 | + | throw new ConnectionException("Client is not connected."); |
| 277 | + | |
| 278 | + | Message message; |
| 279 | + | try { |
| 280 | + | message = this.protocol.receive(this.endpoint); |
| 281 | + | } catch (ConnectionException e) { |
| 282 | + | |
| 283 | + | // Handle disconnect |
| 284 | + | this.disconnect(); |
| 285 | + | throw e; |
| 286 | + | } catch (MessageParserException e) { |
| 287 | + | |
| 288 | + | // Handle invalid messages |
| 289 | + | for (CommunicationListener listener : this.listeners) |
| 290 | + | listener.onInvalidMessage(this, e.getInvalidMessage()); |
| 291 | + | |
| 292 | + | throw e; |
| 293 | + | } |
| 294 | + | if (message == null) |
| 295 | + | return null; |
| 296 | + | |
| 297 | + | for (CommunicationListener listener : this.listeners) |
| 298 | + | listener.onReceive(this, message); |
| 299 | + | |
| 300 | + | return message; |
| 301 | + | } |
| 302 | + | |
| 303 | + | /** |
| 304 | + | * Adds a {@link CommunicationListener} to this list of communication listeners. Its callbacks |
| 305 | + | * will be invoked on specified event. |
| 306 | + | * |
| 307 | + | * @param listener Instance of {@link CommunicationListener} that should be invoked on events. |
| 308 | + | * @see CommunicationListener |
| 309 | + | * */ |
| 310 | + | public void addCommunicationListener(CommunicationListener listener) { |
| 311 | + | this.listeners.add(listener); |
| 312 | + | } |
| 313 | + | |
| 314 | + | /** |
| 315 | + | * Removes a {@link CommunicationListener} from this list of registered listeners. Therefore |
| 316 | + | * its callbacks will no longer be invoked on occurring events. |
| 317 | + | * |
| 318 | + | * @param listener Listener to remove. |
| 319 | + | * @see CommunicationListener |
| 320 | + | * */ |
| 321 | + | public void removeCommunicationListener(CommunicationListener listener) { |
| 322 | + | this.listeners.remove(listener); |
| 323 | + | } |
| 324 | + | |
| 325 | + | /** |
| 326 | + | * Specifies this {@link Protocol} to be used for communication with a server. |
| 327 | + | * |
| 328 | + | * Do NOT call this method. It is used in {@link CommunicationFactory} for linking a protocol |
| 329 | + | * to clients and servers. Calling this method may result in connection crashes etc. |
| 330 | + | * |
| 331 | + | * @param protocol {@link Protocol} to use for communication. |
| 332 | + | * @see Protocol |
| 333 | + | * */ |
| 334 | + | public void setProtocol(Protocol protocol) { |
| 335 | + | this.protocol = protocol; |
| 336 | + | } |
| 337 | + | |
| 338 | + | /** |
| 339 | + | * Specifies this {@link ClientType} that identifies a client's functionality. |
| 340 | + | * |
| 341 | + | * @param type Identifier for client's functionality. |
| 342 | + | * @see ClientType |
| 343 | + | * */ |
| 344 | + | public void setType(ClientType type) { |
| 345 | + | this.type = type; |
| 346 | + | } |
| 347 | + | |
| 348 | + | /** |
| 349 | + | * Provides a {@link ClientConfiguration} to be used for various configurable aspects of this |
| 350 | + | * client. Primarily, this will have an effect on this internal {@link EndPoint}. This will |
| 351 | + | * overwrite the default configuration. |
| 352 | + | * |
| 353 | + | * This method should be called before calling any other method of {@link Client}. |
| 354 | + | * |
| 355 | + | * @param configuration New configuration to use. |
| 356 | + | * @see ClientConfiguration |
| 357 | + | * @see Configurable |
| 358 | + | * */ |
| 359 | + | @Override |
| 360 | + | public void setConfiguration(Configuration configuration) { |
| 361 | + | // Check if configuration is a subclass of EndPointConfiguration |
| 362 | + | if (ClientConfiguration.class.isAssignableFrom(configuration.getClass())) { |
| 363 | + | this.configuration = (ClientConfiguration) configuration; |
| 364 | + | if (this.endpoint != null) { |
| 365 | + | try { |
| 366 | + | this.endpoint.setTimeout(this.configuration.getEndpointTimeout()); |
| 367 | + | } catch (CommunicationException ignored) {} |
| 368 | + | } |
| 369 | + | } |
| 370 | + | } |
| 371 | + | |
| 372 | + | /** |
| 373 | + | * Returns this {@link ClientConfiguration} that is currently active. This method will be useful |
| 374 | + | * when implementing a new {@link com.damnvulnerableapp.networking.communication.server.Server}. |
| 375 | + | * |
| 376 | + | * @return This configuration that is currently active. |
| 377 | + | * @see ClientConfiguration |
| 378 | + | * @see Configurable |
| 379 | + | * */ |
| 380 | + | @NonNull |
| 381 | + | @Override |
| 382 | + | public Configuration getConfiguration() { |
| 383 | + | return this.configuration; |
| 384 | + | } |
| 385 | + | |
| 386 | + | // Factory method pattern. This outsources "raw" communication impl. |
| 387 | + | /** |
| 388 | + | * Constructs an instance of the low - level implementation for communication. E.g. this can be |
| 389 | + | * a socket - based or file - mapping - based approach. Any {@link Client} - implementation has |
| 390 | + | * to specify how communication will be done. |
| 391 | + | * |
| 392 | + | * @return Instance of a low - level implementation for communication. |
| 393 | + | * @throws CommunicationException If establishing a connection fails. This should only happen, if |
| 394 | + | * the {@link EndPoint} is used in conjunction with a |
| 395 | + | * {@link com.damnvulnerableapp.networking.communication.server.Server}. |
| 396 | + | * */ |
| 397 | + | protected abstract EndPoint createEndPoint() throws CommunicationException; |
| 398 | + | } |
| 399 | + | |