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()
defp loop_acceptor(socket) do
{: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)
Logger.info("Received connection from #{inspect(client)}, assigned to PID #{inspect(pid)}")
loop_acceptor(socket)

View File

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

View File

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