From c742fa7c9784fdeb4c42f3fbb7c77209c1334a7f Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Sun, 7 Jul 2024 19:05:32 +0200 Subject: [PATCH] Begin work on implementing handshake --- lib/apps/tcp_listener.ex | 2 +- lib/data.ex | 70 +++++++++++++++++++++------------------- lib/servers/generic.ex | 36 +++++++++++++++++++++ lib/servers/stage1.ex | 21 ++++++++++++ test/data_test.exs | 37 ++++++++++----------- 5 files changed, 113 insertions(+), 53 deletions(-) create mode 100644 lib/servers/generic.ex diff --git a/lib/apps/tcp_listener.ex b/lib/apps/tcp_listener.ex index 440e734..9920c7b 100644 --- a/lib/apps/tcp_listener.ex +++ b/lib/apps/tcp_listener.ex @@ -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) diff --git a/lib/data.ex b/lib/data.ex index a084805..3235f73 100644 --- a/lib/data.ex +++ b/lib/data.ex @@ -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, <>}) do - {[value != 0 | acc], rest} + def bool({acc, <>, :reversed}) do + {[value != 0 | acc], rest, :reversed} end - def byte({acc, <>}) do - {[value | acc], rest} + def byte({acc, <>, :reversed}) do + {[value | acc], rest, :reversed} end - def ubyte({acc, <>}) do - {[value | acc], rest} + def ubyte({acc, <>, :reversed}) do + {[value | acc], rest, :reversed} end - def short({acc, <>}) do - {[value | acc], rest} + def short({acc, <>, :reversed}) do + {[value | acc], rest, :reversed} end - def ushort({acc, <>}) do - {[value | acc], rest} + def ushort({acc, <>, :reversed}) do + {[value | acc], rest, :reversed} end - def int({acc, <>}) do - {[value | acc], rest} + def int({acc, <>, :reversed}) do + {[value | acc], rest, :reversed} end - def long({acc, <>}) do - {[value | acc], rest} + def long({acc, <>, :reversed}) do + {[value | acc], rest, :reversed} end - def float({acc, <>}) do - {[value | acc], rest} + def float({acc, <>, :reversed}) do + {[value | acc], rest, :reversed} end - def double({acc, <>}) do - {[value | acc], rest} + def double({acc, <>, :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 | 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 | 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() <> = rest - {[value | acc], rest} + {[value | acc], rest, :reversed} end end diff --git a/lib/servers/generic.ex b/lib/servers/generic.ex new file mode 100644 index 0000000..532a29c --- /dev/null +++ b/lib/servers/generic.ex @@ -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 . + +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 diff --git a/lib/servers/stage1.ex b/lib/servers/stage1.ex index db5214f..8ffccd8 100644 --- a/lib/servers/stage1.ex +++ b/lib/servers/stage1.ex @@ -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, <>) 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(<>) do + raise RuntimeError, "Got unknown packet type #{type}!" + end end diff --git a/test/data_test.exs b/test/data_test.exs index 51a19b9..9961aa7 100644 --- a/test/data_test.exs +++ b/test/data_test.exs @@ -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