Coverage Summary for Class: HeartbeatHandler (it.polimi.ingsw.gc14.Network.TCP.Server)

Class Class, % Method, % Branch, % Line, %
HeartbeatHandler 0% (0/1) 0% (0/5) 0% (0/8) 0% (0/28)


 package it.polimi.ingsw.gc14.Network.TCP.Server;
 
 import java.io.*;
 import java.net.*;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import it.polimi.ingsw.gc14.Network.NetworkConfig;
 
 /**
  * Handles the heartbeat communication associated with a TCP client.
  *
  * <p>This component listens for heartbeat {@code PING} messages from the client,
  * replies with {@code PONG} messages, and periodically checks whether the client
  * has remained silent for too long. If a timeout or communication error occurs,
  * both the heartbeat connection and the main client connection are disconnected.
  */
 public class HeartbeatHandler implements Runnable {
     /** Username of the client associated with this heartbeat connection. */
     private final String username;
     /** Dedicated socket used solely for heartbeat ping/pong exchange. */
     private final Socket socket;
     /** Input stream of {@link #socket} used to receive {@code PING} bytes from the client. */
     private final InputStream in;
     /** Output stream of {@link #socket} used to send {@code PONG} bytes back to the client. */
     private final OutputStream out;
 
     /**
      * Byte value representing a heartbeat ping message.
      */
     private static final int PING = 1;
 
     /**
      * Byte value representing a heartbeat pong response.
      */
     private static final int PONG = 2;
 
     /**
      * Main client handler associated with this heartbeat connection.
      */
     private final ClientHandler mainHandler;
 
     /**
      * Timestamp of the last received heartbeat message.
      */
     private volatile long lastReceivedTime = System.currentTimeMillis();
 
     /**
      * Indicates whether the heartbeat handler is still active.
      */
     private volatile boolean running = true;
 
 
     /**
      * Executor used to periodically check heartbeat timeouts.
      */
     private final ScheduledExecutorService watchdog =
             Executors.newSingleThreadScheduledExecutor();
 
     /**
      * Constructs a heartbeat handler for the specified client.
      *
      * @param username    the username of the connected client.
      * @param socket      the socket dedicated to heartbeat communication.
      * @param mainHandler the main client handler associated with the same player.
      * @throws IOException if the input or output stream cannot be obtained from the socket.
      */
     public HeartbeatHandler(String username, Socket socket, ClientHandler mainHandler)
             throws IOException {
         this.username = username;
         this.socket = socket;
         this.mainHandler = mainHandler;
         this.in = socket.getInputStream();
         this.out = socket.getOutputStream();
     }
 
     /**
      * Starts the heartbeat listening loop.
      *
      * <p>The method activates the watchdog task, then waits for incoming heartbeat
      * messages. When a {@code PING} is received, the last-received timestamp is
      * updated and a {@code PONG} response is sent back to the client.
      * If the stream is closed or an I/O error occurs, the client is disconnected.
      */
     @Override
     public void run() {
         startWatchdog();
         try {
             while (running) {
                 int b = in.read();
                 if (b == -1) {
                     disconnect();
                     break;
                 }
                 if (b == PING) {
                     lastReceivedTime = System.currentTimeMillis();
                     out.write(PONG);
                     out.flush();
                 }
             }
         } catch (IOException e) {
             disconnect();
         }
     }
 
     /**
      * Starts the periodic watchdog task that detects heartbeat timeouts.
      *
      * <p>If no heartbeat message is received within the configured silence threshold,
      * the associated client is disconnected.
      */
     private void startWatchdog() {
         watchdog.scheduleAtFixedRate(() -> {
             if (System.currentTimeMillis() - lastReceivedTime > NetworkConfig.SILENCE_THRESHOLD_MS) {
                 System.out.println("Heartbeat timeout: " + username);
                 disconnect();
             }
         }, 1, 1, TimeUnit.SECONDS);
     }
 
     /**
      * Disconnects the heartbeat channel and the associated main client connection.
      *
      * <p>The handler is stopped, the watchdog task is terminated, the main
      * {@link ClientHandler} is disconnected, and the heartbeat socket is closed.
      */
     private synchronized void disconnect() {
         running = false;
         watchdog.shutdownNow();
         mainHandler.disconnect();
         try { socket.close(); } catch (IOException ignored) {}
     }
 }