package com.faforever.client.connectivity; import com.faforever.client.i18n.I18n; import com.faforever.client.relay.ConnectivityStateMessage; import com.faforever.client.relay.GpgServerMessage; import com.faforever.client.relay.ProcessNatPacketMessage; import com.faforever.client.relay.SendNatPacketMessage; import com.faforever.client.remote.FafService; import com.faforever.client.remote.domain.MessageTarget; import com.faforever.client.test.AbstractPlainJavaFxTest; import org.apache.commons.compress.utils.IOUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.springframework.util.SocketUtils; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.function.Consumer; import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.CoreMatchers.both; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.springframework.util.SocketUtils.PORT_RANGE_MAX; import static org.springframework.util.SocketUtils.PORT_RANGE_MIN; public class ConnectivityCheckTaskTest extends AbstractPlainJavaFxTest { private ConnectivityCheckTask instance; @Mock private I18n i18n; @Mock private FafService fafService; @Mock private DatagramGateway datagramGateway; @Captor private ArgumentCaptor<Consumer<GpgServerMessage>> connectivityMessageListenerCaptor; private DatagramSocket publicSocket; private int gamePort; @Before public void setUp() throws Exception { instance = new ConnectivityCheckTask(); instance.i18n = i18n; instance.fafService = fafService; instance.connectivityCheckTimeout = 5000; instance.setDatagramGateway(datagramGateway); gamePort = SocketUtils.findAvailableUdpPort(); publicSocket = new DatagramSocket(gamePort); } @After public void tearDown() throws Exception { IOUtils.closeQuietly(publicSocket); } @Test(expected = IllegalStateException.class) public void testCallPortNotSetThrowsIse() throws Exception { instance.call(); } @Test public void testPublic() throws Exception { int playerId = 1234; InetSocketAddress publicAddress = new InetSocketAddress(51111); doAnswer(invocation -> { byte[] bytes = String.format("\bAre you public? %s", playerId).getBytes(UTF_8); DatagramPacket publicCheckPacket = new DatagramPacket(bytes, bytes.length); publicCheckPacket.setAddress(InetAddress.getLocalHost()); publicCheckPacket.setPort(14123); @SuppressWarnings("unchecked") Consumer<DatagramPacket> listener = invocation.getArgumentAt(0, Consumer.class); listener.accept(publicCheckPacket); return null; }).when(datagramGateway).addOnPacketListener(any()); doAnswer(invocation -> { ProcessNatPacketMessage processNatPacketMessage = invocation.getArgumentAt(0, ProcessNatPacketMessage.class); InetAddress expectedAddress = InetAddress.getLocalHost(); InetSocketAddress actualAddress = processNatPacketMessage.getAddress(); String message = processNatPacketMessage.getMessage(); assertThat(message, is("Are you public? " + playerId)); assertThat(processNatPacketMessage.getTarget(), is(MessageTarget.CONNECTIVITY)); assertThat(actualAddress.getAddress(), is(expectedAddress)); assertThat(actualAddress.getPort(), is(both(greaterThan(PORT_RANGE_MIN)).and(lessThan(PORT_RANGE_MAX)))); verify(fafService).addOnMessageListener(eq(GpgServerMessage.class), connectivityMessageListenerCaptor.capture()); connectivityMessageListenerCaptor.getValue().accept( new ConnectivityStateMessage(ConnectivityState.PUBLIC, publicAddress) ); return null; }).when(fafService).sendGpgMessage(any()); instance.setPublicPort(publicSocket.getLocalPort()); ConnectivityStateMessage result = instance.call(); assertThat(result.getState(), is(ConnectivityState.PUBLIC)); assertThat(result.getSocketAddress(), is(publicAddress)); verify(datagramGateway).removeOnPacketListener(any()); verify(fafService).initConnectivityTest(gamePort); } @Test public void testStun() throws Exception { int playerId = 1234; InetSocketAddress outsideSocketAddress = new InetSocketAddress(51111); doAnswer(invocation -> { verify(fafService).addOnMessageListener(eq(GpgServerMessage.class), connectivityMessageListenerCaptor.capture()); SendNatPacketMessage sendNatPacketMessage = new SendNatPacketMessage(); sendNatPacketMessage.setTarget(MessageTarget.CONNECTIVITY); sendNatPacketMessage.setMessage("Hello " + playerId); try (DatagramSocket datagramSocket = new DatagramSocket(new InetSocketAddress(InetAddress.getLocalHost(), 0))) { sendNatPacketMessage.setPublicAddress((InetSocketAddress) datagramSocket.getLocalSocketAddress()); connectivityMessageListenerCaptor.getValue().accept(sendNatPacketMessage); connectivityMessageListenerCaptor.getValue().accept( new ConnectivityStateMessage(ConnectivityState.STUN, outsideSocketAddress) ); } return null; }).when(fafService).initConnectivityTest(gamePort); instance.setPublicPort(publicSocket.getLocalPort()); ConnectivityStateMessage connectivityStateMessage = instance.call(); assertThat(connectivityStateMessage.getState(), is(ConnectivityState.STUN)); assertThat(connectivityStateMessage.getSocketAddress(), is(outsideSocketAddress)); verify(fafService).initConnectivityTest(gamePort); } }