Begin writing code for reading and writing packets

This commit is contained in:
Kodi Craft 2024-07-07 11:41:21 +02:00
commit fae5bb3139
Signed by: kodi
GPG Key ID: 69D9EED60B242822
18 changed files with 737 additions and 0 deletions

188
.credo.exs Normal file
View File

@ -0,0 +1,188 @@
%{
#
# You can have as many configs as you like in the `configs:` field.
configs: [
%{
#
# Run any config using `mix credo -C <name>`. If no config name is given
# "default" is used.
#
name: "default",
#
# These are the files included in the analysis:
files: %{
#
# You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used.
#
included: [
"lib/",
"src/",
"test/",
],
excluded: [~r"/_build/", ~r"/deps/"]
},
plugins: [],
requires: [],
strict: false,
parse_timeout: 5000,
color: true,
#
# You can customize the parameters of any check by adding a second element
# to the tuple.
#
# To disable a check put `false` as second element:
#
# {Credo.Check.Design.DuplicatedCode, false}
#
checks: %{
enabled: [
#
## Consistency Checks
#
{Credo.Check.Consistency.ExceptionNames, []},
{Credo.Check.Consistency.LineEndings, []},
{Credo.Check.Consistency.ParameterPatternMatching, []},
{Credo.Check.Consistency.SpaceAroundOperators, []},
{Credo.Check.Consistency.SpaceInParentheses, []},
{Credo.Check.Consistency.TabsOrSpaces, []},
#
## Design Checks
#
# You can customize the priority of any check
# Priority values are: `low, normal, high, higher`
#
{Credo.Check.Design.AliasUsage,
[priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
{Credo.Check.Design.TagFIXME, []},
# You can also customize the exit_status of each check.
# If you don't want TODO comments to cause `mix credo` to fail, just
# set this value to 0 (zero).
#
{Credo.Check.Design.TagTODO, [exit_status: 2]},
#
## Readability Checks
#
{Credo.Check.Readability.AliasOrder, []},
{Credo.Check.Readability.FunctionNames, []},
{Credo.Check.Readability.LargeNumbers, []},
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
{Credo.Check.Readability.ModuleAttributeNames, []},
{Credo.Check.Readability.ModuleDoc, []},
{Credo.Check.Readability.ModuleNames, []},
{Credo.Check.Readability.ParenthesesInCondition, []},
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
{Credo.Check.Readability.PredicateFunctionNames, []},
{Credo.Check.Readability.PreferImplicitTry, []},
{Credo.Check.Readability.RedundantBlankLines, []},
{Credo.Check.Readability.Semicolons, []},
{Credo.Check.Readability.SpaceAfterCommas, []},
{Credo.Check.Readability.StringSigils, []},
{Credo.Check.Readability.TrailingBlankLine, []},
{Credo.Check.Readability.TrailingWhiteSpace, []},
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
{Credo.Check.Readability.VariableNames, []},
{Credo.Check.Readability.WithSingleClause, []},
#
## Refactoring Opportunities
#
{Credo.Check.Refactor.Apply, []},
{Credo.Check.Refactor.CondStatements, []},
{Credo.Check.Refactor.CyclomaticComplexity, []},
{Credo.Check.Refactor.FilterCount, []},
{Credo.Check.Refactor.FilterFilter, []},
{Credo.Check.Refactor.FunctionArity, []},
{Credo.Check.Refactor.LongQuoteBlocks, []},
{Credo.Check.Refactor.MapJoin, []},
{Credo.Check.Refactor.MatchInCondition, []},
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
{Credo.Check.Refactor.Nesting, []},
{Credo.Check.Refactor.RedundantWithClauseResult, []},
{Credo.Check.Refactor.RejectReject, []},
{Credo.Check.Refactor.UnlessWithElse, []},
{Credo.Check.Refactor.WithClauses, []},
#
## Warnings
#
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
{Credo.Check.Warning.BoolOperationOnSameValues, []},
{Credo.Check.Warning.Dbg, []},
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
{Credo.Check.Warning.IExPry, []},
{Credo.Check.Warning.IoInspect, []},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []},
{Credo.Check.Warning.OperationOnSameValues, []},
{Credo.Check.Warning.OperationWithConstantResult, []},
{Credo.Check.Warning.RaiseInsideRescue, []},
{Credo.Check.Warning.SpecWithStruct, []},
{Credo.Check.Warning.UnsafeExec, []},
{Credo.Check.Warning.UnusedEnumOperation, []},
{Credo.Check.Warning.UnusedFileOperation, []},
{Credo.Check.Warning.UnusedKeywordOperation, []},
{Credo.Check.Warning.UnusedListOperation, []},
{Credo.Check.Warning.UnusedPathOperation, []},
{Credo.Check.Warning.UnusedRegexOperation, []},
{Credo.Check.Warning.UnusedStringOperation, []},
{Credo.Check.Warning.UnusedTupleOperation, []},
{Credo.Check.Warning.WrongTestFileExtension, []}
],
disabled: [
#
# Checks scheduled for next check update (opt-in for now)
{Credo.Check.Refactor.UtcNowTruncate, []},
#
# Controversial and experimental checks (opt-in, just move the check to `:enabled`
# and be sure to use `mix credo --strict` to see low priority checks)
#
{Credo.Check.Consistency.MultiAliasImportRequireUse, []},
{Credo.Check.Consistency.UnusedVariableNames, []},
{Credo.Check.Design.DuplicatedCode, []},
{Credo.Check.Design.SkipTestWithoutComment, []},
{Credo.Check.Readability.AliasAs, []},
{Credo.Check.Readability.BlockPipe, []},
{Credo.Check.Readability.ImplTrue, []},
{Credo.Check.Readability.MultiAlias, []},
{Credo.Check.Readability.NestedFunctionCalls, []},
{Credo.Check.Readability.OneArityFunctionInPipe, []},
{Credo.Check.Readability.OnePipePerLine, []},
{Credo.Check.Readability.SeparateAliasRequire, []},
{Credo.Check.Readability.SingleFunctionToBlockPipe, []},
{Credo.Check.Readability.SinglePipe, []},
{Credo.Check.Readability.Specs, []},
{Credo.Check.Readability.StrictModuleLayout, []},
{Credo.Check.Readability.WithCustomTaggedTuple, []},
{Credo.Check.Refactor.ABCSize, []},
{Credo.Check.Refactor.AppendSingleItem, []},
{Credo.Check.Refactor.DoubleBooleanNegation, []},
{Credo.Check.Refactor.FilterReject, []},
{Credo.Check.Refactor.IoPuts, []},
{Credo.Check.Refactor.MapMap, []},
{Credo.Check.Refactor.ModuleDependencies, []},
{Credo.Check.Refactor.NegatedIsNil, []},
{Credo.Check.Refactor.PassAsyncInTestCases, []},
{Credo.Check.Refactor.PipeChainStart, []},
{Credo.Check.Refactor.RejectFilter, []},
{Credo.Check.Refactor.VariableRebinding, []},
{Credo.Check.Warning.LazyLogging, []},
{Credo.Check.Warning.LeakyEnvironment, []},
{Credo.Check.Warning.MapGetUnsafePass, []},
{Credo.Check.Warning.MixEnv, []},
{Credo.Check.Warning.UnsafeToAtom, []}
# {Credo.Check.Refactor.MapInto, []},
#
# Custom checks can be created using `mix credo.gen.check`.
#
]
}
}
]
}

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

4
.formatter.exs Normal file
View File

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
amethyst-*.tar
# Temporary files, for example, from tests.
/tmp/
# Language server logs
.elixir_ls/
# Direnv
.direnv

34
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,34 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "mix_task",
"name": "mix (Default task)",
"request": "launch",
"projectDir": "${workspaceRoot}",
"task": "run",
"taskArgs": [
"--no-halt"
],
"exitAfterTaskReturns": false
},
{
"type": "mix_task",
"name": "mix test",
"request": "launch",
"task": "test",
"taskArgs": [
"--trace"
],
"startApps": true,
"projectDir": "${workspaceRoot}",
"requireFiles": [
"test/**/test_helper.exs",
"test/**/*_test.exs"
]
}
]
}

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# Amethyst
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `amethyst` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:amethyst, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/amethyst>.

4
config/runtime.exs Normal file
View File

@ -0,0 +1,4 @@
import Config
config :amethyst,
port: 25599 # Bogus port for testing, avoids unexpected conflicts

64
flake.lock Normal file
View File

@ -0,0 +1,64 @@
{
"nodes": {
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1720066371,
"narHash": "sha256-uPlLYH2S0ACj0IcgaK9Lsf4spmJoGejR9DotXiXSBZQ=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "622f829f5fe69310a866c8a6cd07e747c44ef820",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1720181791,
"narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

91
flake.nix Normal file
View File

@ -0,0 +1,91 @@
{
description = "Amethyst - Experimental Minecraft server in Elixir";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
nix-github-actions.url = "github:nix-community/nix-github-actions";
nix-github-actions.inputs.nixpkgs.follows = "nixpkgs";
systems.url = "github:nix-systems/default";
};
outputs = {
self,
nixpkgs,
systems,
nix-github-actions,
...
} @ inputs: let
inherit (nixpkgs) lib;
# Set the Erlang version
erlangVersion = "erlang_25";
# Set the Elixir version
elixirVersion = "elixir_1_17";
eachSystem = f:
nixpkgs.lib.genAttrs (import systems) (
system:
f (import nixpkgs {
inherit system;
overlays = [
(final: _: let
erlang = final.beam.interpreters.${erlangVersion};
beamPackages = final.beam.packages.${erlangVersion};
elixir = beamPackages.${elixirVersion};
in {
inherit erlang elixir;
inherit (beamPackages) elixir-ls hex;
inherit (beamPackages) mixRelease;
})
];
})
);
#secrets = import ./secrets.nix;
package = env: pkgs: (pkgs.beamPackages.mixRelease {
pname = "amethyst";
version = "0.1.0";
src = ./.;
elixir = pkgs.elixir;
mixEnv = env;
erlangDeterministicBuilds = false; # Technically less pure but allows doctests
removeCookie = false; # Insecure; Access to the file system can allow the cookie to be read and provides remote control of the Erlang VM
mixNixDeps = import ./mix.nix {inherit lib; beamPackages = pkgs.beamPackages;};
}).overrideAttrs (old: {
preInstall = ''
# Run automated tests
mix test --no-deps-check --no-start --color
'';
});
in {
packages = eachSystem (pkgs:
{
default = package "prod" pkgs;
dev = package "dev" pkgs;
}
);
checks = self.packages;
githubActions = nix-github-actions.lib.mkGithubMatrix {
checks = nixpkgs.lib.getAttrs [ "x86_64-linux" ] self.packages;
};
devShells = eachSystem (
pkgs: {
default = pkgs.mkShell {
buildInputs = with pkgs; [
erlang
elixir
elixir-ls
mix2nix
];
};
}
);
};
}

20
lib/amethyst.ex Normal file
View File

@ -0,0 +1,20 @@
defmodule Amethyst.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
Supervisor.child_spec({Task, fn -> Amethyst.TCPListener.accept(Application.fetch_env!(:amethyst, :port)) end}, restart: :permanent),
{Task.Supervisor, name: Amethyst.ConnectionSupervisor},
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Amethyst.Supervisor]
Supervisor.start_link(children, opts)
end
end

35
lib/apps/tcp_listener.ex Normal file
View File

@ -0,0 +1,35 @@
defmodule Amethyst.TCPListener do
require Logger
@moduledoc """
This module listens for TCP connections and creates server tasks to handle them.
"""
def accept(port) do
{:ok, socket} = :gen_tcp.listen(port, [:binary, packet: 0, active: false, reuseaddr: true])
Logger.info("Listening on port #{port}")
loop_acceptor(socket)
end
@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 = :gen_tcp.controlling_process(client, pid)
Logger.info("Received connection from #{inspect(client)}, assigned to PID #{inspect(pid)}")
loop_acceptor(socket)
end
defp serve(socket) do
socket |> read_line() |> write_line(socket)
serve(socket)
end
defp read_line(socket) do
{:ok, data} = :gen_tcp.recv(socket, 0)
data
end
defp write_line(line, socket) do
:gen_tcp.send(socket, line)
end
end

109
lib/data.ex Normal file
View File

@ -0,0 +1,109 @@
defmodule Amethyst.Minecraft.Write do
import Bitwise
@moduledoc """
This module contains functions for writing certain Minecraft data types which are more complex
than simple binary data.
"""
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
varnum("", value)
end
def varint(_) do
raise ArgumentError, "Value is out of range for a varint"
end
def varlong(value) when value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807
do
<<value::64-unsigned>> = <<value::64-signed>>
varnum("", value)
end
def varlong(_) do
raise ArgumentError, "Value is out of range for a varlong"
end
defp varnum(acc, value) when value in 0..127 do
acc <> <<0::1, value::7-big>>
end
defp varnum(acc, value) do
acc <> <<1::1, value::7-big>> |> varnum(value >>> 7)
end
def string(value) do
<<varint(byte_size(value))::binary, value::binary>>
end
end
defmodule Amethyst.Minecraft.Read do
import Bitwise
@moduledoc """
This module contains functions for reading Minecraft data. Unlike Amethyst.Minecraft.Write, this
includes all supported data types in order to make the interface more consistent.
These functions allow you to chain them into eachother, at the end they will produce a list of all the
values they have read.
You may use the helper function Amethyst.Minecraft.Read.start/1 to start the chain and Amethyst.Minecraft.Read.stop/1
to get the data out of the chain. If you pass the option `reverse: true` to stop/1, the list will be reversed, this may
provide better performance if you do not mind the inconvenience of the reversed list.
iex> alias Amethyst.Minecraft.Read
iex> [_, _, _] = Read.start(<<1, 999::16, 64>>) |> Read.bool() |> Read.short() |> Read.byte() |> Read.stop()
[true, 999, 64]
"""
def start(binary) do
{[], binary}
end
def stop({acc, rest}, opts \\ []) do
if Keyword.get(opts, :force_empty, false) and bit_size(rest) != 0 do
raise ArgumentError, "Expected no more data, but there is still #{bit_size(rest)} bits left"
end
case Keyword.get(opts, :reverse, false) do
true -> acc
false -> Enum.reverse(acc)
end
end
def bool({acc, <<value, rest::bitstring>>}) do
{[value != 0 | acc], rest}
end
def byte({acc, <<value::big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def ubyte({acc, <<value::big-unsigned, rest::binary>>}) do
{[value | acc], rest}
end
def short({acc, <<value::16-big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def ushort({acc, <<value::16-big-unsigned, rest::binary>>}) do
{[value | acc], rest}
end
def int({acc, <<value::32-big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def long({acc, <<value::64-big-signed, rest::binary>>}) do
{[value | acc], rest}
end
def float({acc, <<value::32-float-big, rest::binary>>}) do
{[value | acc], rest}
end
def double({acc, <<value::64-float-big, rest::binary>>}) do
{[value | acc], rest}
end
end

5
lib/servers/stage1.ex Normal file
View File

@ -0,0 +1,5 @@
defmodule Amethyst.Server.Stage1 do
@moduledoc """
This module contains the stage 1 (Handshaking) server logic.
"""
end

28
mix.exs Normal file
View File

@ -0,0 +1,28 @@
defmodule Amethyst.MixProject do
use Mix.Project
def project do
[
app: :amethyst,
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Amethyst.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
]
end
end

6
mix.lock Normal file
View File

@ -0,0 +1,6 @@
%{
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"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"},
"jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
}

64
mix.nix Normal file
View File

@ -0,0 +1,64 @@
{ lib, beamPackages, overrides ? (x: y: {}) }:
let
buildRebar3 = lib.makeOverridable beamPackages.buildRebar3;
buildMix = lib.makeOverridable beamPackages.buildMix;
buildErlangMk = lib.makeOverridable beamPackages.buildErlangMk;
self = packages // (overrides self packages);
packages = with beamPackages; with self; {
bunt = buildMix rec {
name = "bunt";
version = "1.0.0";
src = fetchHex {
pkg = "bunt";
version = "${version}";
sha256 = "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5";
};
beamDeps = [];
};
credo = buildMix rec {
name = "credo";
version = "1.7.7";
src = fetchHex {
pkg = "credo";
version = "${version}";
sha256 = "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e";
};
beamDeps = [ bunt file_system jason ];
};
file_system = buildMix rec {
name = "file_system";
version = "1.0.0";
src = fetchHex {
pkg = "file_system";
version = "${version}";
sha256 = "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d";
};
beamDeps = [];
};
jason = buildMix rec {
name = "jason";
version = "1.4.3";
src = fetchHex {
pkg = "jason";
version = "${version}";
sha256 = "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3";
};
beamDeps = [];
};
};
in self

30
test/data_test.exs Normal file
View File

@ -0,0 +1,30 @@
defmodule WriteTest do
use ExUnit.Case
doctest Amethyst.Minecraft.Write
doctest Amethyst.Minecraft.Read
test "writing a varint" do
assert Amethyst.Minecraft.Write.varint(0) == <<0x00>>
assert Amethyst.Minecraft.Write.varint(127) == <<0x7F>>
assert Amethyst.Minecraft.Write.varint(128) == <<0x80, 0x01>>
assert Amethyst.Minecraft.Write.varint(255) == <<0xFF, 0x01>>
assert Amethyst.Minecraft.Write.varint(2_147_483_647) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x07>>
assert Amethyst.Minecraft.Write.varint(-1) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x0F>>
assert Amethyst.Minecraft.Write.varint(-2_147_483_648) == <<0x80, 0x80, 0x80, 0x80, 0x08>>
end
test "writing a varlong" do
assert Amethyst.Minecraft.Write.varlong(0) == <<0x00>>
assert Amethyst.Minecraft.Write.varlong(127) == <<0x7F>>
assert Amethyst.Minecraft.Write.varlong(128) == <<0x80, 0x01>>
assert Amethyst.Minecraft.Write.varlong(255) == <<0xFF, 0x01>>
assert Amethyst.Minecraft.Write.varlong(2_147_483_647) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x07>>
assert Amethyst.Minecraft.Write.varlong(9_223_372_036_854_775_807) == <<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F>>
assert Amethyst.Minecraft.Write.varlong(-1) == <<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01>>
end
test "writing a string" do
assert Amethyst.Minecraft.Write.string("Hello, world!") == <<0x0D, "Hello, world!">>
assert Amethyst.Minecraft.Write.string("") == <<0x00>>
end
end

1
test/test_helper.exs Normal file
View File

@ -0,0 +1 @@
ExUnit.start()