Begin work on implementing handshake
All checks were successful
Build & Test / nix-build (push) Successful in 1m5s

This commit is contained in:
Kodi Craft 2024-07-07 19:05:32 +02:00
parent 92325c05b1
commit c742fa7c97
Signed by: kodi
GPG Key ID: 69D9EED60B242822
5 changed files with 113 additions and 53 deletions

View File

@ -29,7 +29,7 @@ defmodule Amethyst.TCPListener do
@spec loop_acceptor(socket :: :gen_tcp.socket()) :: no_return() @spec loop_acceptor(socket :: :gen_tcp.socket()) :: no_return()
defp loop_acceptor(socket) do defp loop_acceptor(socket) do
{:ok, client} = :gen_tcp.accept(socket) {:ok, client} = :gen_tcp.accept(socket)
{:ok, pid} = Task.Supervisor.start_child(Amethyst.ConnectionSupervisor, fn -> serve(client) end) {:ok, pid} = Task.Supervisor.start_child(Amethyst.ConnectionSupervisor, fn -> Amethyst.Server.Stage1.serve(client) end)
:ok = :gen_tcp.controlling_process(client, pid) :ok = :gen_tcp.controlling_process(client, pid)
Logger.info("Received connection from #{inspect(client)}, assigned to PID #{inspect(pid)}") Logger.info("Received connection from #{inspect(client)}, assigned to PID #{inspect(pid)}")
loop_acceptor(socket) loop_acceptor(socket)

View File

@ -65,62 +65,64 @@ defmodule Amethyst.Minecraft.Read do
values they have read. values they have read.
You may use the helper function Amethyst.Minecraft.Read.start/1 to start the chain with a binary buffer. You may use the helper function Amethyst.Minecraft.Read.start/1 to start the chain with a binary buffer.
The return value of the chain is a tuple containing the list of values and the remaining binary buffer, note The return value of the chain is a tuple containing the list of values and the remaining binary buffer.
that the values are ordered from last to first, so in the opposite order of the chain.
iex> alias Amethyst.Minecraft.Read iex> alias Amethyst.Minecraft.Read
iex> {[_, _, _], ""} = Read.start(<<1, 999::16, 64>>) |> Read.bool() |> Read.short() |> Read.byte() iex> {[_, _, _], ""} = Read.start(<<1, 999::16, 64>>) |> Read.bool() |> Read.short() |> Read.byte() |> Read.stop()
{[64, 999, true], ""} {[true, 999, 64], ""}
""" """
def start(binary) do def start(binary) do
{[], binary} {[], binary, :reversed}
end
def stop({acc, rest, :reversed}) do
{Enum.reverse(acc), rest}
end end
def bool({acc, <<value, rest::binary>>}) do def bool({acc, <<value, rest::binary>>, :reversed}) do
{[value != 0 | acc], rest} {[value != 0 | acc], rest, :reversed}
end end
def byte({acc, <<value::big-signed, rest::binary>>}) do def byte({acc, <<value::big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def ubyte({acc, <<value::big-unsigned, rest::binary>>}) do def ubyte({acc, <<value::big-unsigned, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def short({acc, <<value::16-big-signed, rest::binary>>}) do def short({acc, <<value::16-big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def ushort({acc, <<value::16-big-unsigned, rest::binary>>}) do def ushort({acc, <<value::16-big-unsigned, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def int({acc, <<value::32-big-signed, rest::binary>>}) do def int({acc, <<value::32-big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def long({acc, <<value::64-big-signed, rest::binary>>}) do def long({acc, <<value::64-big-signed, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def float({acc, <<value::32-float-big, rest::binary>>}) do def float({acc, <<value::32-float-big, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def double({acc, <<value::64-float-big, rest::binary>>}) do def double({acc, <<value::64-float-big, rest::binary>>, :reversed}) do
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def varint(tuple, read \\ 0, nacc \\ 0) def varint(tuple, read \\ 0, nacc \\ 0)
def varint({acc, <<1::1, value::7, rest::binary>>}, read, nacc) when read < 5 do def varint({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 5 do
varint({acc, rest}, read + 1, nacc + (value <<< (7 * read))) varint({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
end end
def varint({acc, <<0::1, value::7, rest::binary>>}, read, nacc) do def varint({acc, <<0::1, value::7, rest::binary>>, :reversed}, read, nacc) do
total = nacc + (value <<< (7 * read)) total = nacc + (value <<< (7 * read))
<<value::32-signed>> = <<total::32-unsigned>> <<value::32-signed>> = <<total::32-unsigned>>
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def varint(_, read, _) when read >= 5 do def varint(_, read, _) when read >= 5 do
raise RuntimeError, "Got a varint which is too big!" raise RuntimeError, "Got a varint which is too big!"
@ -130,13 +132,13 @@ defmodule Amethyst.Minecraft.Read do
end end
def varlong(tuple, read \\ 0, nacc \\ 0) def varlong(tuple, read \\ 0, nacc \\ 0)
def varlong({acc, <<1::1, value::7, rest::binary>>}, read, nacc) when read < 10 do def varlong({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 10 do
varlong({acc, rest}, read + 1, nacc + (value <<< (7 * read))) varlong({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
end end
def varlong({acc, <<0::1, value::7, rest::binary>>}, read, nacc) do def varlong({acc, <<0::1, value::7, rest::binary>>, :reversed}, read, nacc) do
total = nacc + (value <<< (7 * read)) total = nacc + (value <<< (7 * read))
<<value::64-signed>> = <<total::64-unsigned>> <<value::64-signed>> = <<total::64-unsigned>>
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
def varlong(_, read, _) when read >= 10 do def varlong(_, read, _) when read >= 10 do
raise RuntimeError, "Got a varint which is too big!" raise RuntimeError, "Got a varint which is too big!"
@ -145,9 +147,9 @@ defmodule Amethyst.Minecraft.Read do
raise RuntimeError, "Got an incomplete varint!" raise RuntimeError, "Got an incomplete varint!"
end end
def string({acc, data}) do def string({acc, data, :reversed}) do
{[length], rest} = start(data) |> varint() {[length], rest, :reversed} = start(data) |> varint()
<<value::binary-size(length), rest::binary>> = rest <<value::binary-size(length), rest::binary>> = rest
{[value | acc], rest} {[value | acc], rest, :reversed}
end end
end end

36
lib/servers/generic.ex Normal file
View File

@ -0,0 +1,36 @@
# Amethyst - An experimental Minecraft server written in Elixir.
# Copyright (C) 2024 KodiCraft
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
defmodule Amethyst.Server.Generic do
@moduledoc """
This module includes generic logic which may be used by all stages of the server, including,
for instance, listening for packets.
"""
alias Amethyst.Minecraft.Read
def get_packet(client) do
end
defp get_varint(client, acc) do
{:ok, byte} = :gen_tcp.recv(client, 1)
case byte do
<<0::1, _::7>> -> Read.start(acc <> byte) |> Read.varint() |> Read.stop()
<<1::1, _::7>> -> get_varint(client, acc <> byte)
end
end
end

View File

@ -18,4 +18,25 @@ defmodule Amethyst.Server.Stage1 do
@moduledoc """ @moduledoc """
This module contains the stage 1 (Handshaking) server logic. This module contains the stage 1 (Handshaking) server logic.
""" """
alias Amethyst.Minecraft.Read
def serve(client) do
end
## DESERIALIZATION
# Handshake https://wiki.vg/Protocol#Handshake
def deserialize(0x00, <<data::binary>>) do
{[ver, addr, port, next], ""} = Read.start(data) |> Read.varint() |> Read.string() |> Read.ushort() |> Read.varint() |> Read.stop()
next = case next do
1 -> :status
2 -> :login
3 -> :transfer
_ -> raise RuntimeError, "Client requested moving to an unknown state!"
end
%{type: :handshake, version: ver, address: addr, port: port, next: next}
end
def deserialize(<<type, _::binary>>) do
raise RuntimeError, "Got unknown packet type #{type}!"
end
end end

View File

@ -1,5 +1,5 @@
defmodule WriteTest do defmodule WriteTest do
use ExUnit.Case use ExUnit.Case, async: true
@moduledoc """ @moduledoc """
This module contains tests for the Amethyst.Minecraft.Write module. This module contains tests for the Amethyst.Minecraft.Write module.
@ -34,36 +34,37 @@ defmodule WriteTest do
end end
defmodule ReadTest do defmodule ReadTest do
use ExUnit.Case use ExUnit.Case, async: true
@moduledoc """ @moduledoc """
This module contains tests for the Amethyst.Minecraft.Read module. This module contains tests for the Amethyst.Minecraft.Read module.
""" """
doctest Amethyst.Minecraft.Read doctest Amethyst.Minecraft.Read
alias Amethyst.Minecraft.Read
test "reading a varint" do test "reading a varint" do
assert Amethyst.Minecraft.Read.start(<<0x00>>) |> Amethyst.Minecraft.Read.varint() == {[0], ""} assert Read.start(<<0x00>>) |> Read.varint() |> Read.stop() == {[0], ""}
assert Amethyst.Minecraft.Read.start(<<0x7F>>) |> Amethyst.Minecraft.Read.varint() == {[127], ""} assert Read.start(<<0x7F>>) |> Read.varint() |> Read.stop() == {[127], ""}
assert Amethyst.Minecraft.Read.start(<<0x80, 0x01>>) |> Amethyst.Minecraft.Read.varint() == {[128], ""} assert Read.start(<<0x80, 0x01>>) |> Read.varint() |> Read.stop() == {[128], ""}
assert Amethyst.Minecraft.Read.start(<<0xFF, 0x01>>) |> Amethyst.Minecraft.Read.varint() == {[255], ""} assert Read.start(<<0xFF, 0x01>>) |> Read.varint() |> Read.stop() == {[255], ""}
assert Amethyst.Minecraft.Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0x07>>) |> Amethyst.Minecraft.Read.varint() == {[2_147_483_647], ""} assert Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0x07>>) |> Read.varint() |> Read.stop() == {[2_147_483_647], ""}
assert Amethyst.Minecraft.Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0x0F>>) |> Amethyst.Minecraft.Read.varint() == {[-1], ""} assert Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0x0F>>) |> Read.varint() |> Read.stop() == {[-1], ""}
assert Amethyst.Minecraft.Read.start(<<0x80, 0x80, 0x80, 0x80, 0x08>>) |> Amethyst.Minecraft.Read.varint() == {[-2_147_483_648], ""} assert Read.start(<<0x80, 0x80, 0x80, 0x80, 0x08>>) |> Read.varint() |> Read.stop() == {[-2_147_483_648], ""}
end end
test "reading a varlong" do test "reading a varlong" do
assert Amethyst.Minecraft.Read.start(<<0x00>>) |> Amethyst.Minecraft.Read.varlong() == {[0], ""} assert Read.start(<<0x00>>) |> Read.varlong() |> Read.stop() == {[0], ""}
assert Amethyst.Minecraft.Read.start(<<0x7F>>) |> Amethyst.Minecraft.Read.varlong() == {[127], ""} assert Read.start(<<0x7F>>) |> Read.varlong() |> Read.stop() == {[127], ""}
assert Amethyst.Minecraft.Read.start(<<0x80, 0x01>>) |> Amethyst.Minecraft.Read.varlong() == {[128], ""} assert Read.start(<<0x80, 0x01>>) |> Read.varlong() |> Read.stop() == {[128], ""}
assert Amethyst.Minecraft.Read.start(<<0xFF, 0x01>>) |> Amethyst.Minecraft.Read.varlong() == {[255], ""} assert Read.start(<<0xFF, 0x01>>) |> Read.varlong() |> Read.stop() == {[255], ""}
assert Amethyst.Minecraft.Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0x07>>) |> Amethyst.Minecraft.Read.varlong() == {[2_147_483_647], ""} assert Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0x07>>) |> Read.varlong() |> Read.stop() == {[2_147_483_647], ""}
assert Amethyst.Minecraft.Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F>>) |> Amethyst.Minecraft.Read.varlong() == {[9_223_372_036_854_775_807], ""} assert Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F>>) |> Read.varlong() |> Read.stop() == {[9_223_372_036_854_775_807], ""}
assert Amethyst.Minecraft.Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01>>) |> Amethyst.Minecraft.Read.varlong() == {[-1], ""} assert Read.start(<<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01>>) |> Read.varlong() |> Read.stop() == {[-1], ""}
end end
test "reading a string" do test "reading a string" do
assert Amethyst.Minecraft.Read.start(<<0x0D, "Hello, world!">>) |> Amethyst.Minecraft.Read.string() == {["Hello, world!"], ""} assert Read.start(<<0x0D, "Hello, world!">>) |> Read.string() |> Read.stop() == {["Hello, world!"], ""}
assert Amethyst.Minecraft.Read.start(<<0x00>>) |> Amethyst.Minecraft.Read.string() == {[""], ""} assert Read.start(<<0x00>>) |> Read.string() |> Read.stop() == {[""], ""}
end end
end end