Compare commits

...

2 Commits

Author SHA1 Message Date
9f9abf277d
Move to login mode when client wants to
Some checks failed
Build & Test / nix-build (push) Failing after 17s
2024-07-08 16:59:02 +02:00
809a37c644
Begin implementing Login stage 2024-07-08 16:57:49 +02:00
7 changed files with 126 additions and 6 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 -> Amethyst.Server.Stage1.serve(client) end) {:ok, pid} = Task.Supervisor.start_child(Amethyst.ConnectionSupervisor, fn -> Amethyst.Server.Handshake.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

@ -22,6 +22,10 @@ defmodule Amethyst.Minecraft.Write do
than simple binary data. than simple binary data.
""" """
def uuid(uuid) do
UUID.string_to_binary!(uuid)
end
def varint(value) when value in -2_147_483_648..2_147_483_647 do def varint(value) when value in -2_147_483_648..2_147_483_647 do
<<value::32-unsigned>> = <<value::32-signed>> # This is a trick to allow the arithmetic shift to act as a logical shift <<value::32-unsigned>> = <<value::32-signed>> # This is a trick to allow the arithmetic shift to act as a logical shift
varnum("", value) varnum("", value)
@ -115,6 +119,15 @@ defmodule Amethyst.Minecraft.Read do
{[value | acc], rest, :reversed} {[value | acc], rest, :reversed}
end end
def uuid({acc, <<uuid::16-binary, rest::binary>>, :reversed}) do
{[UUID.binary_to_string!(uuid) | acc], rest, :reversed}
end
def raw({acc, data, :reversed}, amount) do
<<data::binary-size(amount), rest::binary>> = data
{[data | acc], rest, :reversed}
end
def varint(tuple, read \\ 0, nacc \\ 0) def varint(tuple, read \\ 0, nacc \\ 0)
def varint({acc, <<1::1, value::7, rest::binary>>, :reversed}, 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, :reversed}, read + 1, nacc + (value <<< (7 * read))) varint({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))

View File

@ -14,9 +14,9 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
defmodule Amethyst.Server.Stage1 do defmodule Amethyst.Server.Handshake do
@moduledoc """ @moduledoc """
This module contains the stage 1 (Handshaking) server logic. This module contains the logic for the Handshake stage of the server.
""" """
require Logger require Logger
use Amethyst.Server use Amethyst.Server
@ -53,6 +53,7 @@ defmodule Amethyst.Server.Stage1 do
Logger.info("Got handshake, version #{ver} on #{addr}:#{port}. Wants to move to #{next}") Logger.info("Got handshake, version #{ver} on #{addr}:#{port}. Wants to move to #{next}")
case next do case next do
:status -> Amethyst.Server.Status.serve(client) :status -> Amethyst.Server.Status.serve(client)
:login -> Amethyst.Server.Login.serve(client)
_ -> raise RuntimeError, "Unhandled move to next mode #{next}" _ -> raise RuntimeError, "Unhandled move to next mode #{next}"
end end
end end

105
lib/servers/login.ex Normal file
View File

@ -0,0 +1,105 @@
# 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.Login do
@moduledoc """
This module contains the logic for the Handshake stage of the server.
"""
require Logger
use Amethyst.Server
alias Amethyst.Minecraft.Read
alias Amethyst.Minecraft.Write
## DESERIALIZATION
# Login Start https://wiki.vg/Protocol#Login_Start
def deserialize(0x00, data) do
{[name, uuid], ""} = Read.start(data) |> Read.string() |> Read.uuid() |> Read.stop()
{:login_start, name, uuid}
end
# Encryption Response https://wiki.vg/Protocol#Encryption_Response
def deserialize(0x01, data) do
{[secret_length], rest} = Read.start(data) |> Read.varint() |> Read.stop()
{[secret, verify_token_length], rest} = Read.start(rest) |> Read.raw(secret_length) |> Read.varint() |> Read.stop()
{[verify_token], ""} = Read.start(rest) |> Read.raw(verify_token_length) |> Read.stop()
{:encryption_response, secret, verify_token}
end
# Login Plugin Response https://wiki.vg/Protocol#Login_Plugin_Response
def deserialize(0x02, data) do
{[message_id, success], rest} = Read.start(data) |> Read.varint() |> Read.bool() |> Read.stop()
if success do
{:login_plugin_response, message_id, rest}
else
{:login_plugin_response, message_id, nil}
end
end
# Login Acknowledged https://wiki.vg/Protocol#Login_Acknowledged
def deserialize(0x03, "") do
{:login_acknowledged}
end
# Cookie Response https://wiki.vg/Protocol#Cookie_Response_(login)
def deserialize(0x04, data) do
{[key, exists], rest} = Read.start(data) |> Read.string() |> Read.bool() |> Read.stop()
if exists do
{[length], rest} = Read.start(rest) |> Read.varint() |> Read.stop()
{[data], _} = Read.start(rest) |> Read.raw(length) |> Read.stop()
{:cookie_response, key, data}
else
{:cookie_response, key, nil}
end
end
def deserialize(type, _) do
raise RuntimeError, "Got unknown packet type #{type}!"
end
## SERIALIZATION
# Disconnect (login) https://wiki.vg/Protocol#Disconnect_(login)
def serialize({:disconnect, reason}) do
Write.varint(0x00) <> Write.string(reason)
end
# Encryption Request https://wiki.vg/Protocol#Encryption_Request
def serialize({:encryption_request, id, pubkey, verify_token, auth}) do
Write.varint(0x01) <> Write.string(id) <> Write.varint(byte_size(pubkey)) <> pubkey <> Write.varint(byte_size(verify_token)) <> verify_token <> <<auth::8>>
end
# Login Success https://wiki.vg/Protocol#Login_Success
def serialize({:login_success, uuid, username, props, strict}) do
Write.varint(0x02) <> Write.uuid(uuid) <> Write.string(username) <> Write.varint(length(props)) <>
Enum.reduce(props, "", fn {name, value, signature}, acc -> acc <> Write.string(name) <> Write.string(value) <> case signature do
nil -> <<0x00>>
signature -> <<0x01>> <> Write.string(signature)
end end) <> <<strict::8>>
end
# Set Compression https://wiki.vg/Protocol#Set_Compression
def serialize({:set_compression, threshold}) do
Write.varint(0x03) <> Write.varint(threshold)
end
# Login Plugin Request https://wiki.vg/Protocol#Login_Plugin_Request
def serialize({:login_plugin_request, id, channel, data}) do
Write.varint(0x04) <> Write.varint(id) <> Write.string(channel) <> data
end
# Cookie Request (login) https://wiki.vg/Protocol#Cookie_Request_(login)
def serialize({:cookie_request_login, id}) do
Write.varint(0x05) <> Write.string(id)
end
def serialize(packet) do
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"
end
## HANDLING
def handle(tuple, _) do
Logger.error("Unhandled but known packet #{elem(tuple, 0)}")
end
end

View File

@ -16,8 +16,7 @@
defmodule Amethyst.Server.Status do defmodule Amethyst.Server.Status do
@moduledoc """ @moduledoc """
This module contains the Status logic, this is not really its own login state since the client This module contains the logic for the Status stage of the server.
is only asking a bit of information.
""" """
require Logger require Logger
use Amethyst.Server use Amethyst.Server

View File

@ -22,7 +22,8 @@ defmodule Amethyst.MixProject do
# Run "mix help deps" to learn about dependencies. # Run "mix help deps" to learn about dependencies.
defp deps do defp deps do
[ [
{:credo, "~> 1.7", only: [:dev, :test], runtime: false} {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:uuid, "~> 1.1"}
] ]
end end
end end

View File

@ -3,4 +3,5 @@
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
} }