From 1ba7df1c4aabd3581a0b6b77176a5b77a2fbb2ff Mon Sep 17 00:00:00 2001 From: Mattia Date: Fri, 13 Nov 2020 21:40:12 +0100 Subject: [PATCH 1/3] Implemented LookAheadObjectInputStream to resolve vulnerability CWE-502 found by CodeQL (unsafe deserialization) Now only a strict set of objects can be sent through the ObjectInputStream. --- .../LookAheadObjectInputStream.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/main/java/com/battleship/networking/LookAheadObjectInputStream.java diff --git a/src/main/java/com/battleship/networking/LookAheadObjectInputStream.java b/src/main/java/com/battleship/networking/LookAheadObjectInputStream.java new file mode 100644 index 0000000..937583d --- /dev/null +++ b/src/main/java/com/battleship/networking/LookAheadObjectInputStream.java @@ -0,0 +1,74 @@ +package com.battleship.networking; + +import com.battleship.game.playerpack.PlayerData; + +import javax.swing.*; +import java.io.*; +import java.util.HashSet; + +/** + * Strengthened version of ObjectInputStream to allow + * only whitelisted classes to be deserialized. + */ +public class LookAheadObjectInputStream extends ObjectInputStream { + + private final HashSet whitelist = new HashSet<>(4); + + /** + * Creates an ObjectInputStream that reads from the specified InputStream. + * A serialization stream header is read from the stream and verified. + * This constructor will block until the corresponding ObjectOutputStream + * has written and flushed the header. + * + *

The serialization filter is initialized to the value of + * {@linkplain ObjectInputFilter.Config#getSerialFilter() the system-wide filter}. + * + *

If a security manager is installed, this constructor will check for + * the "enableSubclassImplementation" SerializablePermission when invoked + * directly or indirectly by the constructor of a subclass which overrides + * the ObjectInputStream.readFields or ObjectInputStream.readUnshared + * methods. + * + * @param in input stream to read from + * @throws StreamCorruptedException if the stream header is incorrect + * @throws IOException if an I/O error occurs while reading stream header + * @throws SecurityException if untrusted subclass illegally overrides + * security-sensitive methods + * @throws NullPointerException if {@code in} is {@code null} + */ + public LookAheadObjectInputStream(InputStream in) throws IOException { + super(in); + buildWhitelist(); + } + + /** + * Builds the whitelist that allows only game objects to be sent. + */ + private void buildWhitelist() { + whitelist.add(PlayerData.class.getName()); + whitelist.add(ImageIcon.class.getName()); + whitelist.add(String.class.getName()); + whitelist.add(int[].class.getName()); + } + + /** + * Load the local class equivalent of the specified stream class + * description if it's in the whitelist. + * + * @param desc an instance of class {@code ObjectStreamClass} + * @return a {@code Class} object corresponding to {@code desc} + * @throws IOException any of the usual Input/Output exceptions. + * @throws ClassNotFoundException if class of a serialized object cannot + * be found. + */ + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + + if (!whitelist.contains(desc.getName())) { + throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName()); + } + + return super.resolveClass(desc); + } + +} From 641e0978635355f0496da9a0dc02994cf1df7227 Mon Sep 17 00:00:00 2001 From: Mattia Date: Fri, 13 Nov 2020 21:43:09 +0100 Subject: [PATCH 2/3] Modified the NetworkConnection to make the Client try for a maximum number of times before giving up the connection to the server. Now both the Server and the Client can't play before a connection is made. General improvements to the gameplay and GUI of GameBoard. Closes #12 --- .../java/com/battleship/gui/GameBoard.form | 122 ++++++++++++------ .../java/com/battleship/gui/GameBoard.java | 82 ++++++++---- src/main/java/com/battleship/gui/Window.java | 8 ++ .../networking/NetworkConnection.java | 50 ++++++- 4 files changed, 195 insertions(+), 67 deletions(-) diff --git a/src/main/java/com/battleship/gui/GameBoard.form b/src/main/java/com/battleship/gui/GameBoard.form index b1466e3..25b6747 100644 --- a/src/main/java/com/battleship/gui/GameBoard.form +++ b/src/main/java/com/battleship/gui/GameBoard.form @@ -1,9 +1,9 @@

- + - + @@ -12,25 +12,10 @@ - - - - - - - - - - - - - - - - + @@ -68,35 +53,94 @@ - + - - - - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + - + - + + + diff --git a/src/main/java/com/battleship/gui/GameBoard.java b/src/main/java/com/battleship/gui/GameBoard.java index 1d14685..e3292a4 100644 --- a/src/main/java/com/battleship/gui/GameBoard.java +++ b/src/main/java/com/battleship/gui/GameBoard.java @@ -38,6 +38,7 @@ public class GameBoard { private JFrame frame; private JLabel playerFieldLabel; private JLabel enemyFieldLabel; + private JLabel statusLabel; private boolean isUserDataSet = false; private boolean isUserTurn = Player.isHost(); private int resolutionWidth = Integer.parseInt(BSConfigFile.readProperties("Resolution_Width")); @@ -130,6 +131,7 @@ private void createUIComponents() { private void setUserElements() { playerFieldLabel.setText(Player.getName() + "'s field"); playerFieldLabel.setIcon(Player.getAvatar()); + statusLabel.setText("Waiting for enemy..."); } /** @@ -139,11 +141,10 @@ private void setUserElements() { */ public void createServer(int port) { connection = new Server(data -> SwingUtilities.invokeLater(() -> handleData(data)), port); - try { - connection.startConnection(); - } catch (Exception e) { - e.printStackTrace(); - } + SwingUtilities.invokeLater(() -> { + waitForPlayer(connection); + statusLabel.setText("Your turn"); + }); } /** @@ -154,17 +155,23 @@ public void createServer(int port) { */ public void createClient(String ip, int port) { connection = new Client(data -> SwingUtilities.invokeLater(() -> handleData(data)), ip, port); - try { - connection.startConnection(); + SwingUtilities.invokeLater(() -> { + waitForPlayer(connection); + sendUserData(); + statusLabel.setText("Enemy turn"); + }); + } + + private void waitForPlayer(NetworkConnection conn) { + do { try { - Thread.sleep(300); // inefficient. We should really use another method - } catch (InterruptedException e) { + conn.startConnection(); + } catch (IllegalThreadStateException _threadStateException) { + // Unhandled exception + } catch (Exception e) { e.printStackTrace(); } - sendUserData(); - } catch (Exception e) { - e.printStackTrace(); - } + } while (!conn.isConnected()); } public void sendUserData() { @@ -175,6 +182,15 @@ public void sendUserData() { } } + private void setPlayerTurn(boolean playerTurn) { + if (playerTurn) { + statusLabel.setText("Your turn"); + } else { + statusLabel.setText("Enemy turn"); + } + isUserTurn = playerTurn; + } + // TODO: refactoring private void handleData(Object data) { if (data instanceof String) { @@ -193,7 +209,7 @@ private void handleData(Object data) { if (posToAttack[0] == SHIP_HIT) { // The enemy sends back the int array with a 1 in first position to signal that he has been hit enemyPositions[posToAttack[1]][posToAttack[2]].setBackground(Color.RED); - isUserTurn = true; + setPlayerTurn(true); } else if (posToAttack[0] == GAME_WON) { JOptionPane.showMessageDialog(frame, "You won!", @@ -218,7 +234,7 @@ private void handleData(Object data) { e.printStackTrace(); } } else { - isUserTurn = true; + setPlayerTurn(true); } } } @@ -245,14 +261,12 @@ private boolean hasPlayerWin() { private void $$$setupUI$$$() { createUIComponents(); mainPanel = new JPanel(); - mainPanel.setLayout(new GridLayoutManager(3, 3, new Insets(0, 0, 0, 0), -1, -1)); + mainPanel.setLayout(new GridLayoutManager(4, 3, new Insets(0, 0, 0, 0), -1, -1)); mainPanel.setForeground(new Color(-4473925)); mainPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), null, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); - mainPanel.add(gameBoard1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(400, 400), new Dimension(400, 400), new Dimension(400, 400), 0, false)); - gameBoard1.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 20), null, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); chatPanel = new JPanel(); chatPanel.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); - mainPanel.add(chatPanel, new GridConstraints(2, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(250, 250), new Dimension(250, 500), null, 0, false)); + mainPanel.add(chatPanel, new GridConstraints(3, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(250, 250), new Dimension(250, 500), null, 0, false)); chatPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10), "Chat", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); scrollPane = new JScrollPane(); chatPanel.add(scrollPane, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); @@ -265,14 +279,34 @@ private boolean hasPlayerWin() { Font inputFont = this.$$$getFont$$$("Roboto Light", -1, -1, input.getFont()); if (inputFont != null) input.setFont(inputFont); chatPanel.add(input, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); - mainPanel.add(gameBoard2, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(400, 400), new Dimension(400, 400), new Dimension(400, 400), 0, false)); - gameBoard2.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 20), null, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); + final JPanel panel1 = new JPanel(); + panel1.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1)); + mainPanel.add(panel1, new GridConstraints(1, 0, 2, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + final JPanel panel2 = new JPanel(); + panel2.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); + panel1.add(panel2, new GridConstraints(0, 0, 2, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + panel2.add(gameBoard1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(400, 400), new Dimension(400, 400), new Dimension(400, 400), 0, false)); + gameBoard1.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 20), null, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); playerFieldLabel = new JLabel(); + playerFieldLabel.setHorizontalAlignment(10); + playerFieldLabel.setHorizontalTextPosition(11); playerFieldLabel.setText("Your Field"); - mainPanel.add(playerFieldLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel2.add(playerFieldLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JPanel panel3 = new JPanel(); + panel3.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); + panel1.add(panel3, new GridConstraints(0, 1, 2, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); + panel3.add(gameBoard2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, new Dimension(400, 400), new Dimension(400, 400), new Dimension(400, 400), 0, false)); + gameBoard2.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(10, 10, 10, 20), null, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null)); enemyFieldLabel = new JLabel(); + enemyFieldLabel.setHorizontalAlignment(10); + enemyFieldLabel.setHorizontalTextPosition(11); enemyFieldLabel.setText("Enemy field"); - mainPanel.add(enemyFieldLabel, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + panel3.add(enemyFieldLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + statusLabel = new JLabel(); + statusLabel.setHorizontalAlignment(10); + statusLabel.setHorizontalTextPosition(11); + statusLabel.setText("Status"); + mainPanel.add(statusLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); } /** @@ -350,7 +384,7 @@ private void testPrint(Object src) { e.printStackTrace(); } enemyPositions[i][j].setEnabled(false); - isUserTurn = false; + setPlayerTurn(false); } } } diff --git a/src/main/java/com/battleship/gui/Window.java b/src/main/java/com/battleship/gui/Window.java index f49b047..ce2d870 100644 --- a/src/main/java/com/battleship/gui/Window.java +++ b/src/main/java/com/battleship/gui/Window.java @@ -105,21 +105,29 @@ private String getGameProperty(String key) { b_start_host = new JButton(); b_start_host.setIcon(new ImageIcon(getClass().getResource("/images/server.png"))); b_start_host.setText("Host Game"); + b_start_host.setMnemonic('H'); + b_start_host.setDisplayedMnemonicIndex(0); b_start_host.setToolTipText("Host a game"); panel.add(b_start_host, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); b_start_join = new JButton(); b_start_join.setIcon(new ImageIcon(getClass().getResource("/images/link.png"))); b_start_join.setText("Join Game"); + b_start_join.setMnemonic('J'); + b_start_join.setDisplayedMnemonicIndex(0); b_start_join.setToolTipText("Join a game"); panel.add(b_start_join, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); b_settings = new JButton(); b_settings.setIcon(new ImageIcon(getClass().getResource("/images/settings.png"))); b_settings.setText("Settings"); + b_settings.setMnemonic('S'); + b_settings.setDisplayedMnemonicIndex(0); b_settings.setToolTipText("Open the settings"); panel.add(b_settings, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); b_exit = new JButton(); b_exit.setIcon(new ImageIcon(getClass().getResource("/images/logout.png"))); b_exit.setText("Exit"); + b_exit.setMnemonic('E'); + b_exit.setDisplayedMnemonicIndex(0); b_exit.setToolTipText("Exit the application"); panel.add(b_exit, new GridConstraints(4, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final Spacer spacer1 = new Spacer(); diff --git a/src/main/java/com/battleship/networking/NetworkConnection.java b/src/main/java/com/battleship/networking/NetworkConnection.java index cc38dc6..a33efd9 100644 --- a/src/main/java/com/battleship/networking/NetworkConnection.java +++ b/src/main/java/com/battleship/networking/NetworkConnection.java @@ -1,8 +1,10 @@ package com.battleship.networking; +import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.net.ConnectException; import java.net.ServerSocket; import java.net.Socket; import java.util.function.Consumer; @@ -11,6 +13,7 @@ public abstract class NetworkConnection { private final ConnectionThread connectionThread = new ConnectionThread(); private final Consumer onReceiveCallback; + private boolean isConnected = false; /** * Constructor of the class @@ -30,6 +33,15 @@ public void startConnection() { connectionThread.start(); } + /** + * Checks if the socket is connected + * + * @return true if connected - false otherwise + */ + public boolean isConnected() { + return isConnected; + } + /** * Sends a serializable object * @@ -77,14 +89,17 @@ private class ConnectionThread extends Thread { @Override public void run() { - try (ServerSocket server = isServer() ? new ServerSocket(getPort()) : null; - Socket socket = isServer() ? server.accept() : new Socket(getIP(), getPort()); - ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); - ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) { + try ( + ServerSocket server = isServer() ? new ServerSocket(getPort()) : null; + Socket socket = isServer() ? server.accept() : createClient(getIP(), getPort(), 1000); // TODO: implement maxRetry + ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); + ObjectInputStream in = new LookAheadObjectInputStream(socket.getInputStream()) + ) { this.socket = socket; this.out = out; socket.setTcpNoDelay(true); + isConnected = true; while (true) { Serializable data = (Serializable) in.readObject(); @@ -96,5 +111,32 @@ public void run() { onReceiveCallback.accept("Connection closed."); } } + + /** + * Create the new Socket for the client, retry if the connection is not successful. + * + * @param ip the hostname + * @param port the port number + * @param maxRetry maximum tries before returning a null socket + * @return the Socket connected or null if no connection was made + */ + private Socket createClient(String ip, int port, int maxRetry) { + Socket socket = null; + int counter = 0; + + while (counter < maxRetry) { + try { + counter++; + socket = new Socket(ip, port); + break; + } catch (ConnectException connectionException) { + System.out.println(counter + " - Waiting for server..."); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return socket; + } } } From aeac01fd2387ecbb0512c7f87a48091bff7e126e Mon Sep 17 00:00:00 2001 From: Mattia Pizzolitto Date: Fri, 13 Nov 2020 22:19:23 +0100 Subject: [PATCH 3/3] Update NetworkConnection.java --- src/main/java/com/battleship/networking/NetworkConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/battleship/networking/NetworkConnection.java b/src/main/java/com/battleship/networking/NetworkConnection.java index a33efd9..95f7e24 100644 --- a/src/main/java/com/battleship/networking/NetworkConnection.java +++ b/src/main/java/com/battleship/networking/NetworkConnection.java @@ -93,7 +93,7 @@ public void run() { ServerSocket server = isServer() ? new ServerSocket(getPort()) : null; Socket socket = isServer() ? server.accept() : createClient(getIP(), getPort(), 1000); // TODO: implement maxRetry ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); - ObjectInputStream in = new LookAheadObjectInputStream(socket.getInputStream()) + LookAheadObjectInputStream in = new LookAheadObjectInputStream(socket.getInputStream()) ) { this.socket = socket;