| | |
| | | import java.io.InputStream; |
| | | import java.net.ServerSocket; |
| | | import java.net.Socket; |
| | | import java.net.SocketException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.time.Duration; |
| | | import java.util.List; |
| | | import java.util.concurrent.BlockingQueue; |
| | | import java.util.concurrent.CopyOnWriteArrayList; |
| | |
| | | private static final byte[] FRAME_END = "#\r\n".getBytes(StandardCharsets.UTF_8); |
| | | |
| | | private final ServerSocket serverSocket; |
| | | private final int port; |
| | | private final ExecutorService executor = Executors.newSingleThreadExecutor(); |
| | | private final BlockingQueue<String> responses = new LinkedBlockingQueue<>(); |
| | | private final BlockingQueue<ScriptedAction> actions = new LinkedBlockingQueue<>(); |
| | | private final List<String> requests = new CopyOnWriteArrayList<>(); |
| | | |
| | | ScriptedBtsTcpSimulator() throws IOException |
| | | { |
| | | this(true); |
| | | } |
| | | |
| | | private ScriptedBtsTcpSimulator(boolean acceptConnections) throws IOException |
| | | { |
| | | serverSocket = new ServerSocket(0); |
| | | executor.submit(this::serve); |
| | | port = serverSocket.getLocalPort(); |
| | | if (acceptConnections) |
| | | executor.submit(this::serve); |
| | | else |
| | | serverSocket.close(); |
| | | } |
| | | |
| | | static ScriptedBtsTcpSimulator refusingConnections() throws IOException |
| | | { |
| | | return new ScriptedBtsTcpSimulator(false); |
| | | } |
| | | |
| | | int port() |
| | | { |
| | | return serverSocket.getLocalPort(); |
| | | return port; |
| | | } |
| | | |
| | | void enqueue(String response) |
| | | { |
| | | responses.add(response); |
| | | actions.add(new ScriptedAction(Duration.ZERO, response.getBytes(StandardCharsets.UTF_8), false)); |
| | | } |
| | | |
| | | void enqueueDelayed(Duration delay, String response) |
| | | { |
| | | if (delay.isNegative()) throw new IllegalArgumentException("delay must not be negative"); |
| | | actions.add(new ScriptedAction(delay, response.getBytes(StandardCharsets.UTF_8), false)); |
| | | } |
| | | |
| | | void enqueueRaw(byte[] response) |
| | | { |
| | | actions.add(new ScriptedAction(Duration.ZERO, response.clone(), false)); |
| | | } |
| | | |
| | | void enqueueDisconnect() |
| | | { |
| | | actions.add(new ScriptedAction(Duration.ZERO, null, true)); |
| | | } |
| | | |
| | | List<String> requests() |
| | |
| | | |
| | | private void serve() |
| | | { |
| | | try (Socket socket = serverSocket.accept()) |
| | | while (!serverSocket.isClosed()) |
| | | { |
| | | while (!socket.isClosed()) |
| | | try (Socket socket = serverSocket.accept()) |
| | | { |
| | | String request = readFrame(socket.getInputStream()); |
| | | requests.add(request); |
| | | String response = responses.poll(5, TimeUnit.SECONDS); |
| | | if (response == null) throw new IOException("No scripted BTS response available"); |
| | | socket.getOutputStream().write(response.getBytes(StandardCharsets.UTF_8)); |
| | | socket.getOutputStream().write(FRAME_END); |
| | | socket.getOutputStream().flush(); |
| | | serveConnection(socket); |
| | | } |
| | | catch (SocketException ex) |
| | | { |
| | | if (!serverSocket.isClosed()) throw new IllegalStateException(ex); |
| | | } |
| | | catch (Exception ignored) |
| | | { |
| | | } |
| | | } |
| | | catch (Exception ignored) |
| | | } |
| | | |
| | | private void serveConnection(Socket socket) throws Exception |
| | | { |
| | | while (!socket.isClosed()) |
| | | { |
| | | String request = readFrame(socket.getInputStream()); |
| | | requests.add(request); |
| | | ScriptedAction action = actions.poll(5, TimeUnit.SECONDS); |
| | | if (action == null) throw new IOException("No scripted BTS action available"); |
| | | TimeUnit.MILLISECONDS.sleep(action.delay().toMillis()); |
| | | if (action.disconnect()) return; |
| | | socket.getOutputStream().write(action.payload()); |
| | | socket.getOutputStream().write(FRAME_END); |
| | | socket.getOutputStream().flush(); |
| | | } |
| | | } |
| | | |
| | |
| | | executor.shutdownNow(); |
| | | executor.awaitTermination(2, TimeUnit.SECONDS); |
| | | } |
| | | |
| | | private record ScriptedAction(Duration delay, byte[] payload, boolean disconnect) |
| | | { |
| | | } |
| | | } |