Skip to content

Commit 1b7b90d

Browse files
committed
Achieve 100% test coverage
1 parent 7b9ed46 commit 1b7b90d

8 files changed

Lines changed: 99 additions & 27 deletions

File tree

coveralls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"skip_files": [
33
"lib/minecraft/packet/client/handshake.ex",
44
"lib/minecraft/packet/client/status.ex",
5-
"lib/minecraft/packet/server/status.ex"
5+
"lib/minecraft/packet/server/status.ex",
6+
"test/support"
67
]
78
}

lib/minecraft/packet.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmodule Minecraft.Packet do
1515
Given a raw binary packet, deserializes it into a `Packet` struct.
1616
"""
1717
@spec deserialize(binary, state :: atom, type :: :client | :server) ::
18-
{packet :: term, new_state :: atom, rest :: binary}
18+
{packet :: term, new_state :: atom, rest :: binary} | {:error, :invalid_packet}
1919
def deserialize(data, state, type \\ :client)
2020

2121
def deserialize(data, :handshaking, type) when is_binary(data) do

lib/minecraft/packet/handshake.ex

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ defmodule Minecraft.Packet.Handshake do
55
alias Minecraft.Packet.Client
66
alias Minecraft.Protocol
77
import Minecraft.Packet
8-
@protocol_1_12_2 340
9-
@protocol_1_12_2_v Minecraft.Packet.encode_varint(@protocol_1_12_2)
108

119
@type packet_id :: 0
1210

@@ -15,9 +13,9 @@ defmodule Minecraft.Packet.Handshake do
1513
"""
1614
@spec deserialize(packet_id, binary, type :: :client | :server) ::
1715
{packet :: term, new_state :: Protocol.state(), rest :: binary}
18-
def deserialize(packet_id, data, type \\ :client)
19-
20-
def deserialize(0, <<@protocol_1_12_2_v::binary, rest::binary>>, :client) do
16+
| {:error, :invalid_packet}
17+
def deserialize(0 = _packet_id, data, :client = _type) do
18+
{protocol_version, rest} = decode_varint(data)
2119
{server_addr, rest} = decode_string(rest)
2220
<<server_port::size(16), next_state::size(8), rest::binary>> = rest
2321

@@ -28,6 +26,7 @@ defmodule Minecraft.Packet.Handshake do
2826
end
2927

3028
packet = %Client.Handshake{
29+
protocol_version: protocol_version,
3130
server_addr: server_addr,
3231
server_port: server_port,
3332
next_state: next_state
@@ -36,6 +35,10 @@ defmodule Minecraft.Packet.Handshake do
3635
{packet, next_state, rest}
3736
end
3837

38+
def deserialize(_, _, _) do
39+
{:error, :invalid_packet}
40+
end
41+
3942
@doc """
4043
Serializes a handshake packet.
4144
"""

lib/minecraft/packet/status.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ defmodule Minecraft.Packet.Status do
1414
"""
1515
@spec deserialize(packet_id, binary, type :: :client | :server) ::
1616
{packet :: term, new_state :: Protocol.state(), rest :: binary}
17-
def deserialize(packet_id, data, type \\ :client)
18-
19-
def deserialize(0 = _packet_id, rest, :client) do
20-
{%Client.Status.Request{}, :status, rest}
17+
| {:error, :invalid_packet}
18+
def deserialize(0 = _packet_id, data, :client = _type) do
19+
{%Client.Status.Request{}, :status, data}
2120
end
2221

2322
def deserialize(1 = _packet_id, data, :client) do
@@ -35,6 +34,10 @@ defmodule Minecraft.Packet.Status do
3534
{%Server.Status.Pong{payload: payload}, :status, rest}
3635
end
3736

37+
def deserialize(_, _, _) do
38+
{:error, :invalid_packet}
39+
end
40+
3841
@doc """
3942
Serializes a status packet.
4043
"""

lib/minecraft/protocol.ex

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ defmodule Minecraft.Protocol do
2626
{:ok, pid}
2727
end
2828

29+
#
30+
# Server Callbacks
31+
#
32+
2933
@impl true
3034
def init({ref, socket, transport, _protocol_opts}) do
3135
:ok = :ranch.accept_ack(ref)
@@ -46,13 +50,33 @@ defmodule Minecraft.Protocol do
4650

4751
@impl true
4852
def handle_info({:tcp, socket, packet}, state) do
49-
{packet, current, rest} = Packet.deserialize(packet, state.current)
50-
Logger.debug(fn -> "REQUEST: #{inspect(packet)}" end)
53+
case Packet.deserialize(packet, state.current) do
54+
{packet, current, rest} when is_binary(rest) ->
55+
Logger.debug(fn -> "REQUEST: #{inspect(packet)}" end)
56+
57+
if byte_size(rest) > 0 do
58+
send(self(), {:tcp, socket, rest})
59+
end
60+
61+
handle_packet(packet, socket, current, state)
5162

52-
if byte_size(rest) > 0 do
53-
send(self(), {:tcp, socket, rest})
63+
{:error, :invalid_packet} ->
64+
Logger.error(fn -> "Received an invalid packet from client, closing connection." end)
65+
{:stop, :normal, state}
5466
end
67+
end
68+
69+
def handle_info({:tcp_closed, socket}, state) do
70+
Logger.info(fn -> "Client #{state.client_ip} disconnected." end)
71+
:ok = state.transport.close(socket)
72+
{:stop, :normal, state}
73+
end
74+
75+
#
76+
# Helpers
77+
#
5578

79+
defp handle_packet(packet, socket, current, state) do
5680
case Handler.handle(packet) do
5781
{:ok, :noreply} ->
5882
:ok = state.transport.setopts(socket, active: :once)
@@ -71,10 +95,4 @@ defmodule Minecraft.Protocol do
7195
{:stop, :normal, state}
7296
end
7397
end
74-
75-
def handle_info({:tcp_closed, socket}, state) do
76-
Logger.info(fn -> "Client #{state.client_ip} disconnected." end)
77-
:ok = state.transport.close(socket)
78-
{:stop, :normal, state}
79-
end
8098
end

lib/minecraft/protocol/handler.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ defmodule Minecraft.Protocol.Handler do
1111
@spec handle(packet :: struct) :: {:ok, :noreply | struct} | {:error, :unknown_packet}
1212
def handle(packet)
1313

14-
def handle(%Client.Handshake{}) do
14+
def handle(%Client.Handshake{protocol_version: 340}) do
1515
{:ok, :noreply}
1616
end
1717

18+
def handle(%Client.Handshake{protocol_version: _}) do
19+
{:error, :unsupported_protocol}
20+
end
21+
1822
def handle(%Client.Status.Request{}) do
1923
{:ok, json} =
2024
Poison.encode(%{

test/minecraft/integration/handshake_test.exs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ defmodule Minecraft.HandshakeTest do
2626
assert {:ok, %Server.Status.Pong{payload: 12_345_678}} =
2727
TestClient.send(client, %Client.Status.Ping{payload: 12_345_678})
2828
end
29+
30+
test "invalid protocol", %{client: client} do
31+
packet = %Client.Handshake{
32+
protocol_version: 123,
33+
server_addr: "localhost",
34+
server_port: 25565,
35+
next_state: :status
36+
}
37+
38+
assert {:error, :closed} = TestClient.send(client, packet)
39+
end
40+
41+
test "invalid packet results in socket closure", %{client: client} do
42+
assert {:error, :closed} = TestClient.send_raw(client, <<1, 2, 3>>)
43+
end
2944
end

test/support/test_client.ex

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@ defmodule Minecraft.TestClient do
1717
end
1818

1919
@doc """
20-
Sends a message to the server.
20+
Sends a packet to the server.
2121
"""
22-
@spec send(pid, struct) :: {:ok, response :: term} | {:error, term}
22+
@spec send(pid, packet :: struct) :: {:ok, response :: term} | {:error, term}
2323
def send(pid, packet) do
2424
GenServer.call(pid, {:send, packet})
2525
end
2626

27+
@doc """
28+
Sends raw data to the server.
29+
"""
30+
@spec send_raw(pid, data :: binary) :: {:ok, response :: term} | {:error, term}
31+
def send_raw(pid, data) do
32+
GenServer.call(pid, {:send_raw, data})
33+
end
34+
2735
@doc """
2836
Sets the client's connection state.
2937
"""
@@ -52,9 +60,29 @@ defmodule Minecraft.TestClient do
5260
def handle_call({:send, packet}, _from, {socket, state}) do
5361
{:ok, request} = Minecraft.Packet.serialize(packet)
5462
:ok = :gen_tcp.send(socket, request)
55-
{:ok, response} = :gen_tcp.recv(socket, 0)
56-
{response_packet, _, ""} = Minecraft.Packet.deserialize(response, state, :server)
57-
{:reply, {:ok, response_packet}, {socket, state}}
63+
64+
case :gen_tcp.recv(socket, 0) do
65+
{:ok, response} ->
66+
{response_packet, _, ""} = Minecraft.Packet.deserialize(response, state, :server)
67+
{:reply, {:ok, response_packet}, {socket, state}}
68+
69+
{:error, _} = err ->
70+
{:stop, :normal, err, {socket, state}}
71+
end
72+
end
73+
74+
@impl true
75+
def handle_call({:send_raw, data}, _from, {socket, state}) do
76+
:ok = :gen_tcp.send(socket, data)
77+
78+
case :gen_tcp.recv(socket, 0) do
79+
{:ok, response} ->
80+
{response_packet, _, ""} = Minecraft.Packet.deserialize(response, state, :server)
81+
{:reply, {:ok, response_packet}, {socket, state}}
82+
83+
{:error, _} = err ->
84+
{:stop, :normal, err, {socket, state}}
85+
end
5886
end
5987

6088
def handle_call({:set_state, state}, _from, {socket, _old_state}) do

0 commit comments

Comments
 (0)