[
  {
    "path": ".circleci/config.yml",
    "content": "# Elixir CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-elixir/ for more details\nversion: 2.1\norbs:\n  slack: circleci/slack@3.4.2\njobs:\n  build:\n    docker:\n      - image: cimg/elixir:1.14.1\n        environment:  # environment variables for primary container\n          MIX_ENV: test\n          SEPARATE_IPV6_PORT: false\n    resource_class: medium\n\n    working_directory: ~/repo\n    steps:\n      - run: git clone https://github.com/X-Plane/elixir-raknet.git .\n      - run: git submodule update --init --remote\n      - run: mix local.hex --force\n\n      - restore_cache:  # restores saved mix cache;  Read about caching dependencies: https://circleci.com/docs/2.0/caching/\n          keys:  # list of cache keys, in decreasing specificity\n            - v1-mix-cache-{{ .Branch }}-{{ checksum \"mix.lock\" }}\n            - v1-mix-cache-{{ .Branch }}\n            - v1-mix-cache\n      - restore_cache:  # restores saved build cache\n          keys:\n            - v1-build-cache-{{ .Branch }}\n            - v1-build-cache\n\n      - run:\n          name: Compile\n          command: mix do deps.get, compile\n      - save_cache:  # generate and store mix cache\n          key: v1-mix-cache-{{ .Branch }}-{{ checksum \"mix.lock\" }}\n          paths: \"deps\"\n      - save_cache: # don't forget to save a *build* cache, too\n          key: v1-build-cache-{{ .Branch }}\n          paths: \"_build\"\n\n      - run: LOG_LEVEL=warn SEPARATE_IPV6_PORT=false mix test --exclude ipv6:true\n      - store_test_results:  # upload junit test results for display in Test Summary. More info: https://circleci.com/docs/2.0/collect-test-data/\n          path: _build/test/lib/raknet\n\n      - run: mix format --check-formatted\n      - run: bash -c \"mix credo --strict --ignore tagtodo ; if [[ \\$? -ge 16 ]] ; then exit 1 ; else exit 0 ; fi\"\n"
  },
  {
    "path": ".formatter.exs",
    "content": "[\n  line_length: 140,\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior (including a [minimum reproducible example](https://stackoverflow.com/help/minimal-reproducible-example), as appropriate):\n1. ...\n2. ...\n3. ...\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. macOS 10.15.4]\n - Elixir Version [e.g. 1.11.1]\n - Erlang Version [e.g. 23.0]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nraknet-*.tar\n\n"
  },
  {
    "path": ".idea/runConfigurations/Format.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Format\" type=\"MixRunConfigurationType\" factoryName=\"Elixir Mix\">\n    <module name=\"raknet\" />\n    <mix>\n      <argument>format</argument>\n    </mix>\n    <working-directory url=\"file://$PROJECT_DIR$\" />\n    <envs>\n      <env name=\"MIX_ENV\" value=\"test\" />\n    </envs>\n    <module name=\"raknet\" />\n    <module-filters inherit-application-module-filters=\"true\" />\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": ".idea/runConfigurations/Test.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Test\" type=\"MixExUnitRunConfigurationType\" factoryName=\"Elixir Mix ExUnit\">\n    <module name=\"raknet\" />\n    <module name=\"raknet\" />\n    <module-filters inherit-application-module-filters=\"true\" />\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 X-Plane\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Elixir RakNet\n\n**`main` build status**: [![main build status](https://circleci.com/gh/X-Plane/elixir-raknet/tree/main.svg?style=svg)](https://circleci.com/gh/X-Plane/elixir-raknet/tree/main) **Latest commit build status**: [![Last commit build status](https://circleci.com/gh/X-Plane/elixir-raknet.svg?style=svg)](https://circleci.com/gh/X-Plane/elixir-raknet)\n\nThis is an Elixir implementation of the [RakNet](https://github.com/facebookarchive/RakNet)/RakLib networking communication protocol.\n\nIt offers things like stateful connections, reliable (or unreliable) UDP transmissions, and client clock synchronization, all of which are generally necessary for implementing a multiplayer game server. \n\nNote that this is not a *complete* implementation of the RakNet protocol—it currently offers only what X-Plane needs for its massive multiplayer server. Known limitations include:\n\n1. The server doesn't do well with retransmitting unacknowledged reliable packets, since we only ever send unreliable packets in our MMO server. \n2. We don't support splitting packets larger than the connection's MTU size—this leaves the responsibility on the client to make sure your packets aren't over the size limit (which of course can vary depending on the client's connection—yikes!). In practice, this isn't a problem if you know your packets are reasonably small (well under 1 KB).\n\nWe'd welcome well-tested pull requests to fix these things, though. (See the [Contributing](#contributing) section below.)\n\n## Installation\n\nAdd `raknet` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:raknet, git: \"git://github.com/X-Plane/elixir-raknet.git\", branch: \"main\"},\n  ]\nend\n```\n\n## Usage\n\nIn your application, you'll need two things:\n\n1. A client state struct that implements the `RakNet.Client` protocol. For the X-Plane massive multiplayer server, our state struct looks like this:\n\n        defmodule MmoServer.Client.State do\n          @moduledoc \"State for our implementation of RakNet.Client\"\n          @enforce_keys [:session_id, :connection_pid]\n          defstruct session_id: -1,\n                    connection_pid: nil,\n                    # Other fields, which get default-initialized\n                    # when a new connection is negotiated. \n                    . . . \n        end\n\n    Then, in our implementation of the `RakNet.Client` protocol, we spin up a GenServer for each connection as follows:\n\n        defimpl RakNet.Client, for: MmoServer.Client.State do\n          def new(_client_struct, connection_pid, _) do\n            new_state = %MmoServer.Client.State{\n              connection_pid: connection_pid,\n              session_id: MmoServer.SessionIdServer.new_id()\n            }\n   \n            MmoServer.Client.start_link(new_state)\n            new_state\n          end\n        \n          def receive(%MmoServer.Client.State{session_id: id}, packet_type, packet_buffer, transit_time) do\n            MmoServer.Client.handle_packet(id, packet_type, packet_buffer, transit_time)\n          end\n        \n          def got_ack(%MmoServer.Client.State{session_id: _id}, _send_receipt_id) do\n            # X-Plane doesn't actually do anything with packet acknowledgements\n            nil\n          end\n        \n          def disconnect(%MmoServer.Client.State{session_id: id}) do\n            MmoServer.Client.disconnect(id)\n          end\n        end\n   \n2. One or more `RakNet.Server` GenServers (one per port you want to accept connections on). For the X-Plane MMO server, we accept connections on a range of ports, like this:\n\n        localhost = {127, 0, 0, 1}\n   \n        make_server_spec = fn port ->\n          # Only your client state type and port are required \n          [MmoServer.Client.State, port, include_timestamp_with_datagrams: true, host: localhost, client_timeout_ms: 20 * 60 * 1_000, open_ipv6_socket: true]\n        end\n     \n        servers =\n          Enum.map(port_min..port_max, fn port ->\n            {RakNet.Server, make_server_spec.(port)}\n          end)\n    \n        opts = [strategy: :one_for_one, name: MmoServer.Supervisor]\n   \n        Supervisor.start_link(servers, opts)\n\nOne configuration option above is worth calling out explicitly: the value of `:open_ipv6_socket` will determine whether we try to open a *separate* socket to receive IPv6 connections, or whether we accept IPv6 connections over the same socket as IPv4. The configuration you need here will depend on your OS configuration, but in general, Linux systems default to sharing a socket, while macOS defaults to using separate sockets. Alternatively, you can set a default value for this using the `SEPARATE_IPV6_PORT` environment variable.\n\nFrom this point forward, the `RakNet.Server` will create a new stateful `RakNet.Connection` for each RakNet client that connects on your port, and client packets will be forwarded to your client struct.\n\nThe client can send messages using the `connection_pid` it was constructed with, like so:\n\n    RakNet.Connection.send(state.connection_pid, :reliable, packet)\n\n...where the final argument is a bitstring, and the second argument is the reliability level the packet should be transmitted with. Supported reliability levels are defined in `RakNet.ReliabilityLayer.Reliability` as:\n\n- `:unreliable`\n- `:unreliable_sequenced`\n- `:reliable`\n- `:reliable_ordered`\n- `:reliable_sequenced`\n- `:unreliable_ack_receipt`\n- `:reliable_ack_receipt`\n- `:reliable_ordered_ack_receipt` \n\n## Running the tests\n\nYou can run the complete unit test suite via the standard `$ mix test` from the top-level directory. Note that you'll see a log message about receiving data before the connection negotiation finished—that's deliberate, since we test handling that error case, but it would be somewhat concerning for it to occur in production.\n\n[We use CircleCI](https://app.circleci.com/pipelines/github/X-Plane/elixir-raknet) to run the test suite on every commit.\n\nYou can run the same tests that CircleCI does as follows:\n\n1. Run the Credo linter: `$ mix credo --strict`\n2. Confirm the code matches the official formatter: `$ mix format --check-formatted`\n3. Confirm the tests pass: `$ mix test` (or if you like more verbose output, `$ mix test --trace`)\n\n## Contributing\n\nBefore submitting a pull request, please ensure:\n\n1. You've added appropriate tests for your new changes\n2. All tests pass\n3. The credo analysis is clean: `$ mix credo --strict --ignore tagtodo`\n4. You've run `$ mix format`\n"
  },
  {
    "path": "config/.credo.exs",
    "content": "# This file contains the configuration for Credo and you are probably reading\n# this after creating it with `mix credo.gen.config`.\n#\n# If you find anything wrong or unclear in this file, please report an\n# issue on GitHub: https://github.com/rrrene/credo/issues\n#\n%{\n  #\n  # You can have as many configs as you like in the `configs:` field.\n  configs: [\n    %{\n      #\n      # Run any exec using `mix credo -C <name>`. If no exec name is given\n      # \"default\" is used.\n      #\n      name: \"default\",\n      #\n      # These are the files included in the analysis:\n      files: %{\n        #\n        # You can give explicit globs or simply directories.\n        # In the latter case `**/*.{ex,exs}` will be used.\n        #\n        included: [\"lib/\", \"src/\", \"test/\", \"web/\", \"apps/\"],\n        excluded: [~r\"/_build/\", ~r\"/deps/\", ~r\"/node_modules/\", ~r\"/data/\"]\n      },\n      #\n      # Load and configure plugins here:\n      #\n      plugins: [],\n      #\n      # If you create your own checks, you must specify the source files for\n      # them here, so they can be loaded by Credo before running the analysis.\n      #\n      requires: [\n        \"config/credo_checks/explicitly_ignore_return_values.ex\"\n      ],\n      #\n      # If you want to enforce a style guide and need a more traditional linting\n      # experience, you can change `strict` to `true` below:\n      #\n      strict: true,\n      #\n      # If you want to use uncolored output by default, you can change `color`\n      # to `false` below:\n      #\n      color: true,\n      #\n      # You can customize the parameters of any check by adding a second element\n      # to the tuple.\n      #\n      # To disable a check put `false` as second element:\n      #\n      #     {Credo.Check.Design.DuplicatedCode, false}\n      #\n      checks: [\n        #\n        ## Consistency Checks\n        #\n        {Credo.Check.Consistency.ExceptionNames, []},\n        {Credo.Check.Consistency.LineEndings, []},\n        {Credo.Check.Consistency.ParameterPatternMatching, []},\n        {Credo.Check.Consistency.SpaceAroundOperators, []},\n        {Credo.Check.Consistency.SpaceInParentheses, []},\n        {Credo.Check.Consistency.TabsOrSpaces, []},\n\n        #\n        ## Design Checks\n        #\n        # You can customize the priority of any check\n        # Priority values are: `low, normal, high, higher`\n        #\n        {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},\n        # Don't cause to-do comments to fail\n        {Credo.Check.Design.TagTODO, [exit_status: 0]},\n        {Credo.Check.Design.TagFIXME, []},\n\n        #\n        ## Readability Checks\n        #\n        {Credo.Check.Readability.AliasOrder, []},\n        {Credo.Check.Readability.FunctionNames, []},\n        {Credo.Check.Readability.LargeNumbers, []},\n        {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 140]},\n        {Credo.Check.Readability.ModuleAttributeNames, []},\n        {Credo.Check.Readability.ModuleDoc, []},\n        {Credo.Check.Readability.ModuleNames, []},\n        {Credo.Check.Readability.ParenthesesInCondition, []},\n        {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},\n        {Credo.Check.Readability.PredicateFunctionNames, []},\n        {Credo.Check.Readability.PreferImplicitTry, []},\n        {Credo.Check.Readability.RedundantBlankLines, []},\n        {Credo.Check.Readability.Semicolons, []},\n        {Credo.Check.Readability.SpaceAfterCommas, []},\n        {Credo.Check.Readability.StringSigils, []},\n        {Credo.Check.Readability.TrailingBlankLine, []},\n        {Credo.Check.Readability.TrailingWhiteSpace, []},\n        {Credo.Check.Readability.UnnecessaryAliasExpansion, []},\n        {Credo.Check.Readability.VariableNames, []},\n\n        #\n        ## Refactoring Opportunities\n        #\n        {Credo.Check.Refactor.CondStatements, []},\n        {Credo.Check.Refactor.CyclomaticComplexity, []},\n        {Credo.Check.Refactor.FunctionArity, []},\n        {Credo.Check.Refactor.LongQuoteBlocks, []},\n        {Credo.Check.Refactor.MatchInCondition, []},\n        {Credo.Check.Refactor.NegatedConditionsInUnless, []},\n        {Credo.Check.Refactor.NegatedConditionsWithElse, []},\n        {Credo.Check.Refactor.Nesting, [max_nesting: 3]},\n        {Credo.Check.Refactor.UnlessWithElse, []},\n        {Credo.Check.Refactor.WithClauses, []},\n\n        #\n        ## Warnings\n        #\n        {Credo.Check.Warning.BoolOperationOnSameValues, []},\n        {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},\n        {Credo.Check.Warning.IExPry, []},\n        {Credo.Check.Warning.IoInspect, []},\n        {Credo.Check.Warning.MixEnv, []},\n        {Credo.Check.Warning.OperationOnSameValues, []},\n        {Credo.Check.Warning.OperationWithConstantResult, []},\n        {Credo.Check.Warning.RaiseInsideRescue, []},\n        {Credo.Check.Warning.UnusedEnumOperation, []},\n        {Credo.Check.Warning.UnusedFileOperation, []},\n        {Credo.Check.Warning.UnusedKeywordOperation, []},\n        {Credo.Check.Warning.UnusedListOperation, []},\n        {Credo.Check.Warning.UnusedPathOperation, []},\n        {Credo.Check.Warning.UnusedRegexOperation, []},\n        {Credo.Check.Warning.UnusedStringOperation, []},\n        {Credo.Check.Warning.UnusedTupleOperation, []},\n\n        #\n        # Controversial and experimental checks (opt-in, just replace `false` with `[]`)\n        #\n        {Credo.Check.Consistency.MultiAliasImportRequireUse, []},\n        # Tyler says: I don't like this one... it just enforces that you always name your unused vars the *same* way\n        #             either anonymously (like `_`) or with a real name (like `_client`)\n        {Credo.Check.Consistency.UnusedVariableNames, false},\n        # Tyler says: I like this one\n        {Credo.Check.Design.DuplicatedCode, []},\n        {Credo.Check.Readability.AliasAs, []},\n        {Credo.Check.Readability.MultiAlias, []},\n        {Credo.Check.Readability.Specs, false},\n        {Credo.Check.Readability.SeparateAliasRequire, false},\n        {Credo.Check.Readability.SinglePipe, []},\n        {Credo.Check.Readability.WithCustomTaggedTuple, []},\n        {Credo.Check.Refactor.ABCSize, false},\n        {Credo.Check.Refactor.AppendSingleItem, []},\n        {Credo.Check.Refactor.DoubleBooleanNegation, []},\n        {Credo.Check.Refactor.ModuleDependencies, false},\n        {Credo.Check.Refactor.PipeChainStart, false},\n        # Tyler says: I like this one\n        {Credo.Check.Refactor.VariableRebinding, []},\n        # Tyler says: I like this one\n        {Credo.Check.Warning.MapGetUnsafePass, []},\n        {Credo.Check.Warning.UnsafeToAtom, []},\n\n        #\n        # Custom checks can be created using `mix credo.gen.check`.\n        #\n        {ExplicitlyIgnoreReturnValues,\n         [\n           ignore: [\n             {[:Appsignal, :Transaction], :finish},\n             {[:Appsignal, :Transaction], :complete},\n             {[:HTTPoison], :start},\n             {[:RakNet, :Connection], :handle_message},\n             {[:RakNet, :Connection], :stop},\n             {[:RakNet, :Client], :got_ack},\n             {[:RakNet, :Client], :receive},\n             {[:RakNet, :Client], :disconnect},\n             {[:MmoServer, :Dsf], :remove},\n             {[:MmoServer, :Dsf], :update},\n             {[:MmoServer, :WorldCache], :put},\n             {[:MmoServer, :WorldCache], :drop},\n             {[:MmoServer, :SessionIdServer], :return_id}\n           ]\n         ]}\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "config/credo_checks/explicitly_ignore_return_values.ex",
    "content": "defmodule ExplicitlyIgnoreReturnValues do\n  @moduledoc \"\"\"\n  This is a horrifying hack.\n\n  It is a complete copy & paste of Credo's stock unused_operation and unused_function_return_helper,\n  but modified slightly to reverse the logic; instead of specifying modules and types to warn about when unused,\n  we specify an \"allow list\" of the *only* modules & functions to allow ignoring.\n\n  See original copypasta source here:\n  https://github.com/rrrene/credo/blob/master/lib/credo/check/warning/unused_operation.ex\n  https://github.com/rrrene/credo/blob/master/lib/credo/check/warning/unused_function_return_helper.ex\n  \"\"\"\n\n  @funs_to_allow_ignoring [\n    {[:Enum], :each},\n    {[:GenServer], :cast},\n    {[:GenServer], :stop},\n    {[:IO], :inspect},\n    {[:IO], :puts},\n    {[:IO], :warn},\n    {[:IO], :write},\n    {[:Logger], :configure},\n    {[:Logger], :debug},\n    {[:Logger], :error},\n    {[:Logger], :flush},\n    {[:Logger], :info},\n    {[:Logger], :log},\n    {[:Logger], :warn},\n    {[:Process], :cancel_timer},\n    {[:Process], :exit},\n    {[:Process], :flag},\n    {[:Process], :hibernate},\n    {[:Process], :register},\n    {[:Process], :sleep},\n    {[:Process], :unlink},\n    {[:Process], :unregister},\n    {[:Registry], :register},\n    {[:Registry], :unregister},\n    {[:Supervisor], :stop}\n  ]\n\n  @block_ops_with_head_expr [:if, :unless, :case, :for, :quote]\n\n  use Credo.Check, base_priority: :high, category: :warning\n\n  def explanations do\n    [\n      check: @moduledoc,\n      params: [\n        ignore:\n          \"A list of function AST names whose return values we should allow ignoring. \" <>\n            \"Elements of the list should look like: {[:Enum], :each} or {[:Custom, :Name, :Here], :function}\"\n      ]\n    ]\n  end\n\n  def param_defaults, do: [ignore: []]\n\n  def run(source_file, params \\\\ []) do\n    issue_meta = Credo.IssueMeta.for(source_file, params)\n    funs_to_ignore = @funs_to_allow_ignoring ++ Keyword.fetch!(params, :ignore)\n\n    Enum.reduce(find_unused_calls(source_file, params, funs_to_ignore), [], fn invalid_call, issues ->\n      {_, meta, _} = invalid_call\n\n      trigger =\n        invalid_call\n        |> Macro.to_string()\n        |> String.split(\"(\")\n        |> List.first()\n\n      # credo:disable-for-next-line\n      issues ++ [issue_for(&format_issue/2, issue_meta, meta[:line], trigger)]\n    end)\n  end\n\n  defp issue_for(format_issue_fun, issue_meta, line_no, trigger) do\n    format_issue_fun.(\n      issue_meta,\n      message: \"There should be no unused return values.\",\n      trigger: trigger,\n      line_no: line_no\n    )\n  end\n\n  @def_ops [:def, :defp, :defmacro]\n  @block_ops_with_head_expr [:if, :unless, :case, :for, :quote]\n\n  alias Credo.Code.Block\n  alias Credo.SourceFile\n\n  def find_unused_calls(%SourceFile{} = source_file, _params, funs_to_allow_ignoring) do\n    Credo.Code.prewalk(source_file, &traverse_defs(&1, &2, funs_to_allow_ignoring))\n  end\n\n  for op <- @def_ops do\n    defp traverse_defs({unquote(op), _meta, arguments} = ast, acc, functions) when is_list(arguments) do\n      candidates = Credo.Code.prewalk(ast, &find_candidates(&1, &2, functions))\n\n      if Enum.any?(candidates) do\n        {nil, acc ++ filter_unused_calls(ast, candidates)}\n      else\n        {ast, acc}\n      end\n    end\n  end\n\n  defp traverse_defs(ast, acc, _) do\n    {ast, acc}\n  end\n\n  #\n\n  defp find_candidates({{:., _, [{:__aliases__, _, modules}, function]}, _, _} = ast, acc, functions) do\n    if {modules, function} in functions do\n      {ast, acc}\n    else\n      # credo:disable-for-next-line\n      {ast, acc ++ [ast]}\n    end\n  end\n\n  defp find_candidates(ast, acc, _) do\n    {ast, acc}\n  end\n\n  # TODO: Everything below this line is unmodified from unused_function_return_helper.ex. If these functions are ever made public, use them.\n\n  defp filter_unused_calls(ast, candidates) do\n    candidates\n    |> Enum.map(&detect_unused_call(&1, ast))\n    |> Enum.reject(&is_nil/1)\n  end\n\n  defp detect_unused_call(candidate, ast) do\n    ast\n    |> Credo.Code.postwalk(&traverse_verify_candidate(&1, &2, candidate), :not_verified)\n    |> verified_or_unused_call(candidate)\n  end\n\n  defp verified_or_unused_call(:VERIFIED, _), do: nil\n  defp verified_or_unused_call(_, candidate), do: candidate\n\n  #\n\n  defp traverse_verify_candidate(ast, acc, candidate) do\n    if Credo.Code.contains_child?(ast, candidate) do\n      verify_candidate(ast, acc, candidate)\n    else\n      {ast, acc}\n    end\n  end\n\n  # we know that `candidate` is part of `ast`\n\n  for op <- @def_ops do\n    defp verify_candidate({unquote(op), _, arguments} = ast, :not_verified = _acc, candidate)\n         when is_list(arguments) do\n      # IO.inspect(ast, label: \"#{unquote(op)} (#{Macro.to_string(candidate)} #{acc})\")\n\n      if last_call_in_do_block?(ast, candidate) || last_call_in_rescue_block?(ast, candidate) do\n        {nil, :VERIFIED}\n      else\n        {nil, :FALSIFIED}\n      end\n    end\n  end\n\n  defp last_call_in_do_block?(ast, candidate) do\n    ast\n    |> Block.calls_in_do_block()\n    |> List.last()\n    |> Credo.Code.contains_child?(candidate)\n  end\n\n  defp last_call_in_rescue_block?(ast, candidate) do\n    ast\n    |> Block.calls_in_rescue_block()\n    |> List.last()\n    |> Credo.Code.contains_child?(candidate)\n  end\n\n  for op <- @block_ops_with_head_expr do\n    defp verify_candidate({unquote(op), _, arguments} = ast, :not_verified = acc, candidate)\n         when is_list(arguments) do\n      # IO.inspect(ast, label: \"#{unquote(op)} (#{Macro.to_string(candidate)} #{acc})\")\n\n      head_expression = Enum.slice(arguments, 0..-2)\n\n      if Credo.Code.contains_child?(head_expression, candidate) do\n        {nil, :VERIFIED}\n      else\n        {ast, acc}\n      end\n    end\n  end\n\n  defp verify_candidate({:=, _, _} = ast, :not_verified = acc, candidate) do\n    # IO.inspect(ast, label: \":= (#{Macro.to_string(candidate)} #{acc})\")\n\n    if Credo.Code.contains_child?(ast, candidate) do\n      {nil, :VERIFIED}\n    else\n      {ast, acc}\n    end\n  end\n\n  defp verify_candidate(\n         {:__block__, _, arguments} = ast,\n         :not_verified = acc,\n         candidate\n       )\n       when is_list(arguments) do\n    # IO.inspect(ast, label: \":__block__ (#{Macro.to_string(candidate)} #{acc})\")\n\n    last_call = List.last(arguments)\n\n    if Credo.Code.contains_child?(last_call, candidate) do\n      {ast, acc}\n    else\n      {nil, :FALSIFIED}\n    end\n  end\n\n  defp verify_candidate(\n         {:|>, _, arguments} = ast,\n         :not_verified = acc,\n         candidate\n       ) do\n    # IO.inspect(ast, label: \":__block__ (#{Macro.to_string(candidate)} #{acc})\")\n\n    last_call = List.last(arguments)\n\n    if Credo.Code.contains_child?(last_call, candidate) do\n      {ast, acc}\n    else\n      {nil, :VERIFIED}\n    end\n  end\n\n  defp verify_candidate({:->, _, arguments} = ast, :not_verified = acc, _candidate)\n       when is_list(arguments) do\n    # IO.inspect(ast, label: \":-> (#{Macro.to_string(ast)} #{acc})\")\n\n    {ast, acc}\n  end\n\n  defp verify_candidate({:fn, _, arguments} = ast, :not_verified = acc, _candidate)\n       when is_list(arguments) do\n    {ast, acc}\n  end\n\n  defp verify_candidate(\n         {:try, _, arguments} = ast,\n         :not_verified = acc,\n         candidate\n       )\n       when is_list(arguments) do\n    # IO.inspect(ast, label: \"try (#{Macro.to_string(candidate)} #{acc})\")\n\n    after_block = Block.after_block_for!(ast)\n\n    if after_block && Credo.Code.contains_child?(after_block, candidate) do\n      {nil, :FALSIFIED}\n    else\n      {ast, acc}\n    end\n  end\n\n  # my_fun()\n  defp verify_candidate(\n         {fun_name, _, arguments} = ast,\n         :not_verified = acc,\n         candidate\n       )\n       when is_atom(fun_name) and is_list(arguments) do\n    # IO.inspect(ast, label: \"my_fun() (#{Macro.to_string(candidate)} #{acc})\")\n\n    if Credo.Code.contains_child?(arguments, candidate) do\n      {nil, :VERIFIED}\n    else\n      {ast, acc}\n    end\n  end\n\n  # module.my_fun()\n  defp verify_candidate(\n         {{:., _, [{module, _, []}, fun_name]}, _, arguments} = ast,\n         :not_verified = acc,\n         candidate\n       )\n       when is_atom(fun_name) and is_atom(module) and is_list(arguments) do\n    # IO.inspect(ast, label: \"Mod.fun() (#{Macro.to_string(candidate)} #{acc})\")\n\n    if Credo.Code.contains_child?(arguments, candidate) do\n      {nil, :VERIFIED}\n    else\n      {ast, acc}\n    end\n  end\n\n  # :erlang_module.my_fun()\n  defp verify_candidate(\n         {{:., _, [module, fun_name]}, _, arguments} = ast,\n         :not_verified = acc,\n         candidate\n       )\n       when is_atom(fun_name) and is_atom(module) and is_list(arguments) do\n    # IO.inspect(ast, label: \"Mod.fun() (#{Macro.to_string(candidate)} #{acc})\")\n\n    if Credo.Code.contains_child?(arguments, candidate) do\n      {nil, :VERIFIED}\n    else\n      {ast, acc}\n    end\n  end\n\n  # MyModule.my_fun()\n  defp verify_candidate(\n         {{:., _, [{:__aliases__, _, mods}, fun_name]}, _, arguments} = ast,\n         :not_verified = acc,\n         candidate\n       )\n       when is_atom(fun_name) and is_list(mods) and is_list(arguments) do\n    # IO.inspect(ast, label: \"Mod.fun() (#{Macro.to_string(candidate)} #{acc})\")\n\n    if Credo.Code.contains_child?(arguments, candidate) do\n      {nil, :VERIFIED}\n    else\n      {ast, acc}\n    end\n  end\n\n  defp verify_candidate(ast, acc, _candidate) do\n    # IO.inspect(ast, label: \"_ (#{Macro.to_string(candidate)} #{acc})\")\n\n    {ast, acc}\n  end\nend\n"
  },
  {
    "path": "lib/application.ex",
    "content": "defmodule RakNet.Application do\n  @moduledoc \"Supervision tree for RakNet connections\"\n\n  use Application\n\n  def start(_type, _args) do\n    Logger.configure(\n      level:\n        case(System.fetch_env(\"LOG_LEVEL\")) do\n          {:ok, \"debug\"} -> :debug\n          {:ok, \"info\"} -> :info\n          {:ok, \"warn\"} -> :warn\n          {:ok, \"error\"} -> :error\n          _ -> :info\n        end\n    )\n\n    children = [\n      {Registry, keys: :unique, name: RakNet.Connection}\n    ]\n\n    opts = [strategy: :one_for_one, name: RakNet.Supervisor]\n    Supervisor.start_link(children, opts)\n  end\nend\n"
  },
  {
    "path": "lib/connection.ex",
    "content": "defprotocol RakNet.Client do\n  def new(client_struct, connection_pid, module_data)\n  def receive(client, packet_type, packet_buffer, time_comp)\n  def got_ack(client, send_receipt_id)\n  def disconnect(client)\nend\n\ndefmodule RakNet.Connection do\n  @moduledoc \"A stateful connection to a client\"\n  use GenServer, restart: :transient\n  require Logger\n  alias RakNet.Message\n  alias RakNet.ReliabilityLayer\n  alias RakNet.ReliabilityLayer.Reliability\n  import XUtil.Map\n  import XUtil.Time, only: [unix_timestamp_ms: 0]\n\n  defmodule State do\n    @moduledoc \"The state we pass around within a RakNet.Connection GenServer\"\n    @enforce_keys [\n      :host,\n      :port,\n      :encoded_host,\n      :client_ips_and_ports,\n      :encoded_client,\n      :server_identifier,\n      :client_module,\n      :respond,\n      :base_time\n    ]\n    defstruct host: nil,\n              port: nil,\n              encoded_host: <<>>,\n              # IPs may be 4 ipv4 octets, or 8 ipv6 hextets\n              client_ips_and_ports: [],\n              encoded_client: <<>>,\n              server_identifier: <<>>,\n              # The RakNet.Client module that implements your game logic client; this receives game-specific packets and\n              # uses the respond function we give it to communicate back to the user.\n              client_module: nil,\n              # Data you've asked us to pass to your client_module's new/2 factory\n              client_data: %{},\n              # The means the RakNet.Server that created us gave us to send a message across the wire to the client\n              respond: nil,\n              # The :os.system_time(:millisecond) time at which we were created.\n              # Use this to get RakNet.Server.timestamp() values relative to creation time\n              base_time: 0,\n              # Milliseconds of inactivity before we time out this connection and cause it to self-destruct.\n              # We count as \"activity\" a) connected pongs, and b) encapsulated client data packets\n              # (but not RakNet protocol overhead, like conneciton handshakes)\n              timeout_ms: 30_000,\n              # Corresponds to #define INCLUDE_TIMESTAMP_WITH_DATAGRAMS\n              # (which is in turn enabled by USE_SLIDING_WINDOW_CONGESTION_CONTROL in the RakNetDefinesOverrides.h)\n              include_timestamp_with_datagrams: false,\n              # Corresponds to #define MAXIMUM_NUMBER_OF_INTERNAL_IDS\n              max_number_of_internal_ids: 10,\n\n              # Server doesn't need to set any of this\n              receive_sequence: nil,\n              # Sequencing for whole messages\n              send_sequence: 0,\n              message_index: 0,\n              ordered_write_index: 0,\n              # Maps send_sequence indices to lists of (reliable only?) packets\n              unacknowledged_sent: %{},\n              # Sequencing for sequenced-but-not-ordered messages\n              sequenced_packet: 0,\n              packet_buffer: [],\n              ack_buffer: [],\n              mtu_size: 0,\n              # The :os.system_time(:millisecond) time at which we enqueued the oldest unset acknowledgement packet\n              oldest_unsent_ack_time_ms: 0,\n              # Last diff between our timestamp we sent with a ping and the time we received the pong\n              last_rtts: [],\n              # A TRef to the timer that will time out this connection and cause it to self-destruct\n              timeout_ref: nil,\n              # The instance of business_logic_module that tracks the state for this connection\n              client: nil\n  end\n\n  defmodule Resendable do\n    @moduledoc \"A set of packets that we'll resend if we don't get an :ack\"\n    @enforce_keys [:packets, :index, :next_resend_time]\n    defstruct packets: [], index: 0, next_resend_time: 0\n  end\n\n  @udp_header_size 28\n\n  # Sync rate is hardcoded to 1/100 sec in RakNet\n  @sync_ms 10\n  @ping_ms 5000\n\n  # This is hard-coded in RakNet, to prevent you from using all your RAM on a single client that fails to respond to\n  # a big stream of reliable packets\n  @max_tracked_reliable_packets 512\n\n  # RakNet calculated RTO dynamically based on round trip time variance and the like... we're just gonna make it 1 second + rtt\n  @retransmition_time_out_ms 1000\n\n  # RakNet calculates round-trip time (RTT) as the average of your up-to-5 last ping times\n  @rtt_window_size 5\n\n  def start_link(%State{} = state) do\n    GenServer.start_link(__MODULE__, state)\n  end\n\n  def stop(connection_pid), do: GenServer.stop(connection_pid, :shutdown)\n\n  def handle_message(connection_pid, message_type, data) do\n    GenServer.cast(connection_pid, {message_type, data})\n  end\n\n  @doc \"\"\"\n  Returns one of:\n  {:ok, nil}  (if you didn't request an ack receipt)\n  {:ok, ack_receipt_id}\n  {:error, message}\n  \"\"\"\n  def send(connection_pid, reliability, message)\n\n  def send(connection_pid, reliability, message)\n      when is_bitstring(message) and reliability in [:unreliable_ack_receipt, :reliable_ack_receipt, :reliable_ordered_ack_receipt] do\n    {:ok, GenServer.call(connection_pid, {:send, reliability, message})}\n  end\n\n  def send(connection_pid, reliability, message) when is_bitstring(message) and is_atom(reliability) do\n    GenServer.cast(connection_pid, {:send, reliability, message})\n    {:ok, nil}\n  end\n\n  ################ Server Implementation ################\n  @impl GenServer\n  def init(state) do\n    {:ok, _} = :timer.send_interval(@sync_ms, :sync)\n    Process.flag(:trap_exit, true)\n    {:ok, reschedule_timeout(state)}\n  end\n\n  @raknet_protocol_version 6\n  @use_security 0\n\n  @impl GenServer\n  def handle_info(:sync, connection) do\n    # TODO: Variable retransmission timeout based on RTT: send if estimatedTimeToNextTick+curTime < oldestUnsentAck+rto-RTT\n    {:noreply,\n     connection\n     |> sync_ack_buffer()\n     |> sync_requeue_reliable_data_packets()\n     |> sync_enqueued_data_packets()}\n  end\n\n  @impl GenServer\n  def handle_info(:sync_ping, connection) do\n    {:noreply, ping(connection)}\n  end\n\n  @impl GenServer\n  def handle_info({:EXIT, _pid, reason}, connection) do\n    Logger.debug(\"Connection exiting due to reason #{inspect(reason)}\")\n\n    if not is_nil(connection.client) do\n      RakNet.Client.disconnect(connection.client)\n    end\n\n    Process.exit(self(), :kill)\n  end\n\n  defp sync_ack_buffer(connection) do\n    current_time = unix_timestamp_ms()\n\n    if connection.oldest_unsent_ack_time_ms > 0 and\n         (connection.oldest_unsent_ack_time_ms <= current_time - @sync_ms or length(connection.ack_buffer) > 20) do\n      ack(sweep_line(connection.ack_buffer), connection.respond, connection)\n      %{connection | ack_buffer: [], oldest_unsent_ack_time_ms: 0}\n    else\n      connection\n    end\n  end\n\n  defp sync_enqueued_data_packets(connection) do\n    if Enum.empty?(connection.packet_buffer) do\n      connection\n    else\n      updated_conn = send_immediate(connection.packet_buffer, connection)\n      %{updated_conn | packet_buffer: []}\n    end\n  end\n\n  defp sync_requeue_reliable_data_packets(connection) do\n    current_time = RakNet.Server.timestamp()\n\n    # PerfTODO: This is more expensive than it needs to be... Instead use a queue of {resend_time, packet}?\n    to_resend =\n      Map.values(connection.unacknowledged_sent)\n      |> Enum.sort_by(select_key(:next_resend_time))\n      |> Enum.take_while(fn resendable -> resendable.next_resend_time <= current_time end)\n\n    if Enum.empty?(to_resend) do\n      connection\n    else\n      packets = to_resend |> Enum.map(select_key(:packets)) |> List.flatten()\n      # When the packets actually go out, we'll re-add them to unacknowledged_sent queue (with a new index)\n      indices = Enum.map(to_resend, select_key(:index))\n\n      %{\n        connection\n        | packet_buffer: packets ++ connection.packet_buffer,\n          unacknowledged_sent: Map.drop(connection.unacknowledged_sent, indices)\n      }\n    end\n  end\n\n  defp ping(connection) do\n    enqueue(:unreliable, make_ping_buffer(connection.base_time), connection)\n  end\n\n  defp send_immediate(packets, connection) when is_list(packets) do\n    encoded =\n      RakNet.Packet.encode(%{\n        sequence_number: connection.send_sequence,\n        encapsulated_packets: connection.packet_buffer,\n        timestamp: if(connection.include_timestamp_with_datagrams, do: RakNet.Server.timestamp(connection.base_time), else: -1)\n      })\n\n    # TODO: Periodically retransmit unacknowledged reliable packets\n    # TODO: Split packets larger than connection.mtu_size\n    # credo:disable-for-next-line\n    connection.respond.(<<Message.binary(:data_packet_4), encoded::binary>>, List.first(connection.client_ips_and_ports))\n\n    {reliable_packets, _} = Enum.split_with(packets, fn %ReliabilityLayer.Packet{reliability: r} -> Reliability.is_reliable?(r) end)\n\n    updated_unacknowledged =\n      if Enum.empty?(reliable_packets) or map_size(connection.unacknowledged_sent) > @max_tracked_reliable_packets do\n        connection.unacknowledged_sent\n      else\n        Map.put(connection.unacknowledged_sent, connection.send_sequence, %Resendable{\n          index: connection.send_sequence,\n          packets: reliable_packets,\n          next_resend_time: RakNet.Server.timestamp() + rtt(connection) * 1.5 + @retransmition_time_out_ms\n        })\n      end\n\n    %{\n      connection\n      | unacknowledged_sent: updated_unacknowledged,\n        ordered_write_index: 1 + max(connection.ordered_write_index, max_packet_value(reliable_packets, :order_index)),\n        message_index: 1 + max(connection.message_index, max_packet_value(reliable_packets, :message_index)),\n        send_sequence: connection.send_sequence + 1\n    }\n  end\n\n  defp max_packet_value(packets, packet_key, default \\\\ -1) when is_list(packets) and is_atom(packet_key) do\n    case Enum.max_by(packets, select_key(packet_key), fn -> default end) do\n      %ReliabilityLayer.Packet{} = p ->\n        case Map.fetch!(p, packet_key) do\n          num when is_number(num) -> num\n          _ -> default\n        end\n\n      _ ->\n        default\n    end\n  end\n\n  @impl GenServer\n  def handle_cast({:open_connection_request_1, data}, connection) do\n    Logger.debug(\"Received open connection request 1 from #{inspect(List.first(connection.client_ips_and_ports))}\")\n    <<_offline_msg_id::binary-size(16), @raknet_protocol_version::size(8), _zero_pad_to_mtu_size::bitstring>> = data\n    # +1 for the message ID byte (:open_connection_request_1)\n    mtu_size = byte_size(data) + @udp_header_size + 1\n\n    message =\n      <<Message.binary(:open_connection_reply_1), Message.offline_msg_id()::binary, connection.server_identifier::binary,\n        @use_security::size(8), mtu_size::size(16)>>\n\n    # credo:disable-for-next-line\n    connection.respond.(message, List.first(connection.client_ips_and_ports))\n    Logger.debug(\"Sent open connection reply 1 to #{inspect(List.first(connection.client_ips_and_ports))}\")\n\n    {:noreply, %{connection | mtu_size: mtu_size}}\n  rescue\n    err ->\n      Logger.error(\"Failed to parse open connection request 1 of #{byte_size(data)} bytes (needed at least 24 bytes)\")\n      Logger.error(\"Packet was: #{inspect(data)}\")\n      Logger.error(\"Connection state was: #{inspect(connection)}\")\n      reraise(err, __STACKTRACE__)\n  end\n\n  @impl GenServer\n  def handle_cast({:open_connection_request_2, data}, connection) do\n    Logger.debug(\"Received open connection request 2 from #{inspect(List.first(connection.client_ips_and_ports))}\")\n\n    server_addr_bytes = byte_size(data) - 16 - 2 - 8\n    <<_offline_id::binary-size(16), _svr_address::binary-size(server_addr_bytes), mtu_size::size(16), _client_id::size(64)>> = data\n\n    connection.respond.(\n      <<Message.binary(:open_connection_reply_2), Message.offline_msg_id()::binary, connection.server_identifier::binary,\n        connection.encoded_client::binary, mtu_size::size(16), @use_security::size(8)>>,\n      # credo:disable-for-next-line\n      List.first(connection.client_ips_and_ports)\n    )\n\n    Logger.debug(\"Sent open connection reply 2 to #{inspect(List.first(connection.client_ips_and_ports))}\")\n    {:noreply, connection}\n  end\n\n  @impl GenServer\n  def handle_cast({:ping, <<ping_time::size(64)>>}, connection) do\n    {:noreply, enqueue(:unreliable, make_pong_buffer(ping_time, connection.base_time), reschedule_timeout(connection))}\n  end\n\n  @impl GenServer\n  def handle_cast({:pong, <<our_sent_time::size(64), _::binary>>}, %State{last_rtts: prev_rtts} = connection) do\n    ping_time = RakNet.Server.timestamp(connection.base_time) - our_sent_time\n    updated_rtts = [ping_time | Enum.take(prev_rtts, @rtt_window_size - 1)]\n    {:noreply, reschedule_timeout(%{connection | last_rtts: updated_rtts})}\n  end\n\n  @impl GenServer\n  def handle_cast({:ack, packet}, %State{unacknowledged_sent: unacked} = connection) do\n    timestamp_size = if connection.include_timestamp_with_datagrams, do: 32, else: 0\n    # TODO: Do something with the timestamp if > 0\n    <<_timestamp::size(timestamp_size), ack_portion::binary>> = packet\n    {removed, still_unacked} = Map.split(unacked, message_indices_from_ack(ack_portion))\n\n    msgs_received =\n      removed\n      |> Map.values()\n      # Values from the removed map are Resendable structs; we just need to inspect the packets\n      |> Enum.flat_map(select_key(:packets))\n      |> Enum.filter(fn %ReliabilityLayer.Packet{reliability: r} -> Reliability.needs_client_ack?(r) end)\n      |> Enum.map(select_key(:message_index))\n      |> MapSet.new()\n\n    Enum.each(msgs_received, fn msg_idx -> RakNet.Client.got_ack(connection.client, msg_idx) end)\n\n    {:noreply, %{connection | unacknowledged_sent: still_unacked}}\n  end\n\n  @impl GenServer\n  def handle_cast({:nack, _packet}, connection) do\n    # TODO: Resend?\n    {:noreply, connection}\n  end\n\n  @impl GenServer\n  def handle_cast({:client_connect, data}, connection) do\n    Logger.debug(\"Received client connect from #{inspect(List.first(connection.client_ips_and_ports))}\")\n    <<_client_id::size(64), time_sent::size(64), @use_security::size(8), _password::binary>> = data\n\n    send_pong = RakNet.Server.timestamp(connection.base_time)\n\n    # TODO: Support IPv6\n    # TODO: Should we offer other ports clients can connect on?\n    empty_ip =\n      RakNet.SystemAddress.encode(%{\n        version: 4,\n        address: {255, 255, 255, 255},\n        port: 0\n      })\n\n    packet =\n      <<Message.binary(:server_handshake), connection.encoded_client::binary, 0::size(16)>> <>\n        :erlang.list_to_binary([connection.encoded_host] ++ List.duplicate(empty_ip, 9)) <>\n        <<time_sent::size(64), send_pong::size(64)>>\n\n    Logger.debug(\"Sent server handshake to #{inspect(List.first(connection.client_ips_and_ports))}\")\n    {:noreply, enqueue(:reliable_ordered, packet, connection)}\n  end\n\n  @impl GenServer\n  def handle_cast({:client_handshake, data}, connection) do\n    Logger.debug(\"Received client handshake from #{inspect(List.first(connection.client_ips_and_ports))}\")\n    # A system address is: 1 byte v4 or v6, followed by EITHER 4 bytes v4 address + 2 bytes port OR 28 bytes v6 addr & port\n    addresses_length = bit_size(data) - 2 * 64\n    <<_addresses::bitstring-size(addresses_length), ping_time::size(64), pong_time::size(64)>> = data\n\n    # We send the first ping immediately, then subsequent pings every 5 seconds\n    updated_conn =\n      enqueue(\n        :unreliable,\n        [\n          make_ping_buffer(connection.base_time),\n          make_pong_buffer(ping_time, connection.base_time)\n        ],\n        connection\n      )\n\n    {:ok, _} = :timer.send_interval(@ping_ms, :sync_ping)\n    rtt = max(0, pong_time - RakNet.Server.timestamp(connection.base_time))\n    client = RakNet.Client.new(connection.client_module, self(), connection.client_data)\n    Logger.debug(\"Finalized connection handshake with #{inspect(List.first(connection.client_ips_and_ports))}\")\n    {:noreply, %{updated_conn | last_rtts: [rtt], client: client}}\n  end\n\n  @impl GenServer\n  def handle_cast({:client_disconnect, _data}, connection) do\n    Logger.debug(\"Client #{inspect(List.first(connection.client_ips_and_ports))} disconnected\")\n\n    if not is_nil(connection.client) do\n      RakNet.Client.disconnect(connection.client)\n    end\n\n    Process.exit(self(), :normal)\n    {:noreply, connection}\n  end\n\n  @impl GenServer\n  def handle_cast({packet_type, data}, %State{} = connection) when is_atom(packet_type) do\n    %{encapsulated_packets: encapsulated_packets, sequence_number: recv_sequence, timestamp: _timestamp} =\n      if connection.include_timestamp_with_datagrams do\n        RakNet.Packet.decode_with_timestamp(data)\n      else\n        RakNet.Packet.decode_no_timestamp(data)\n      end\n\n    if Enum.empty?(encapsulated_packets) do\n      # Had a parsing error!\n      Logger.error(\"Failed to parse #{packet_type} (#{byte_size(data) + 1} bytes) #{inspect(data, limit: :infinity)}\")\n      {:noreply, connection}\n    else\n      {:noreply,\n       encapsulated_packets\n       |> Enum.reduce({connection, :unacknowledged}, fn packet, {conn, acked} ->\n         <<identifier::size(8), head_data::binary>> = packet.buffer\n         ident_atom = Message.name(identifier)\n\n         is_connection_negotiation = ident_atom in [:ping, :pong, :client_connect, :client_handshake, :client_disconnect]\n         finished_connection_negotiation = not Enum.empty?(conn.last_rtts) and conn.client != nil\n\n         if is_connection_negotiation or finished_connection_negotiation do\n           # TODO: Sequence indices are per-channel\n           # TODO: Immediately send acks for split packets\n           updated_conn =\n             if acked == :unacknowledged do\n               %{buffer_ack(recv_sequence, conn) | receive_sequence: max(conn.receive_sequence, recv_sequence)}\n             else\n               conn\n             end\n\n           if is_connection_negotiation do\n             {elem(handle_cast({ident_atom, head_data}, updated_conn), 1), :acknowledged}\n           else\n             # TODO: Support custom packet type atoms (right now we're passing through the integer values as the identifier)\n             RakNet.Client.receive(updated_conn.client, identifier, head_data, rtt(updated_conn) / 2)\n             {reschedule_timeout(updated_conn), :acknowledged}\n           end\n         else\n           Logger.info(\"Connection to #{inspect(conn.host)}:#{conn.port} received data before connection negotiation finished\")\n\n           # credo:disable-for-next-line\n           conn.respond.(<<RakNet.Message.binary(:connection_lost)>>, List.first(conn.client_ips_and_ports))\n           {conn, acked}\n         end\n       end)\n       |> elem(0)}\n    end\n  end\n\n  @impl GenServer\n  def handle_cast({:send, reliability, message}, %State{} = connection) do\n    {:noreply, sync_enqueued_data_packets(enqueue(reliability, message, connection))}\n  end\n\n  @impl GenServer\n  def handle_call({:send, reliability, message}, _from, %State{} = connection) do\n    updated_conn = sync_enqueued_data_packets(enqueue(reliability, message, connection))\n    {:reply, updated_conn.message_index, updated_conn}\n  end\n\n  @impl GenServer\n  def terminate(reason, connection) do\n    Logger.info(\"Terminating #{inspect(connection.host)}:#{connection.port} due to #{inspect(reason)}\")\n    Registry.unregister(RakNet.Connection, {connection.host, connection.port})\n  end\n\n  defp reschedule_timeout(%State{timeout_ref: nil} = connection) do\n    case :timer.exit_after(connection.timeout_ms, self(), :timeout) do\n      {:ok, timer_id} -> %{connection | timeout_ref: timer_id}\n      _ -> connection\n    end\n  end\n\n  defp reschedule_timeout(%State{} = connection) do\n    case :timer.cancel(connection.timeout_ref) do\n      {:ok, _} -> reschedule_timeout(%{connection | timeout_ref: nil})\n      # I guess we try killing it again later?\n      _ -> connection\n    end\n  end\n\n  defp enqueue(reliability, buffer, connection) when is_atom(reliability) and is_bitstring(buffer) do\n    enqueue(reliability, [buffer], connection)\n  end\n\n  defp enqueue(reliability, buffers, connection)\n       when (reliability == :unreliable_sequenced or reliability == :reliable_sequenced or reliability == :reliable_ordered_ack_receipt) and\n              is_list(buffers) do\n    num_buffers = length(buffers)\n\n    new_packets =\n      buffers\n      |> Enum.zip(0..(num_buffers - 1))\n      |> Enum.map(fn {buffer, idx} ->\n        %ReliabilityLayer.Packet{\n          reliability: reliability,\n          message_index: if(Reliability.is_reliable?(reliability), do: connection.message_index, else: nil),\n          order_index: connection.ordered_write_index,\n          sequencing_index: connection.sequenced_packet + idx,\n          buffer: buffer\n        }\n      end)\n\n    %{connection | packet_buffer: new_packets ++ connection.packet_buffer, sequenced_packet: connection.sequenced_packet + num_buffers}\n  end\n\n  defp enqueue(reliability, buffers, connection) when is_atom(reliability) and is_list(buffers) do\n    if Reliability.valid?(reliability) do\n      new_packets =\n        Enum.map(buffers, fn buffer ->\n          %ReliabilityLayer.Packet{\n            reliability: reliability,\n            message_index:\n              if(Reliability.is_reliable?(reliability) or Reliability.needs_client_ack?(reliability),\n                do: connection.message_index,\n                else: nil\n              ),\n            order_index: if(Reliability.is_sequenced?(reliability), do: connection.ordered_write_index, else: nil),\n            buffer: buffer\n          }\n        end)\n\n      %{connection | packet_buffer: new_packets ++ connection.packet_buffer}\n    else\n      Logger.error(\"Unknown reliability atom #{reliability}\")\n      connection\n    end\n  end\n\n  defp buffer_ack(packet_index, connection) when is_integer(packet_index) do\n    case connection.ack_buffer do\n      [] -> %{connection | ack_buffer: [packet_index], oldest_unsent_ack_time_ms: unix_timestamp_ms()}\n      _ -> %{connection | ack_buffer: [packet_index | connection.ack_buffer]}\n    end\n  end\n\n  @doc \"\"\"\n  The sweep line algorithm: https://en.wikipedia.org/wiki/Sweep_line_algorithm\n  Given a collection of integers, it combines them into the minimum number of contiguous ranges.\n\n      iex> RakNet.Connection.sweep_line(MapSet.new([5, 4, 3, 2, 1, 0]))\n      [{0, 5}]\n\n      iex> RakNet.Connection.sweep_line([5, 4, 3, 100, 1, 0])\n      [{0, 1}, {3, 5}, {100, 100}]\n  \"\"\"\n  def sweep_line(integers) do\n    [start | sorted] = Enum.sort(integers)\n\n    # Traverse the sorted array, while maintaining a set of \"active\" events:\n    # whenever you see a start/end time, respectively add or drop the\n    # corresponding event from the set, and add (if the active set is non-empty)\n    # an event to your solution.\n    sorted\n    |> Enum.reduce([{start, start}], fn packet_idx, [{b, e} | rest] = acc ->\n      if packet_idx == e + 1 do\n        [{b, packet_idx} | rest]\n      else\n        [{packet_idx, packet_idx} | acc]\n      end\n    end)\n    |> Enum.sort()\n  end\n\n  defp ack(buffered_acks, responder, connection) when is_list(buffered_acks) do\n    {timestamp_bits, timestamp} =\n      if connection.include_timestamp_with_datagrams do\n        {32, RakNet.Server.timestamp(connection.base_time)}\n      else\n        {0, 0}\n      end\n\n    responder.(\n      <<Message.binary(:ack), timestamp::size(timestamp_bits), length(buffered_acks)::size(16)>> <>\n        :erlang.list_to_binary(\n          Enum.map(buffered_acks, fn {range_min, range_max} ->\n            min_is_max = if range_min == range_max, do: 1, else: 0\n\n            <<min_is_max::size(8), range_min::little-size(24)>> <>\n              if(min_is_max == 1, do: <<>>, else: <<range_max::little-size(24)>>)\n          end)\n        ),\n      # credo:disable-for-next-line\n      List.first(connection.client_ips_and_ports)\n    )\n  end\n\n  def message_indices_from_ack(<<packet_count::size(16), remainder::binary>> = full) do\n    parsed =\n      Enum.reduce(1..packet_count, {[], remainder}, fn _, {indices_to_drop, rem_binary} ->\n        case rem_binary do\n          # 1 -> range min == range max (i.e., it's a single index)\n          <<1::size(8), rmin::little-size(24), rem::binary>> -> {[rmin | indices_to_drop], rem}\n          <<0::size(8), rmin::little-size(24), rmax::little-size(24)>> -> {Enum.to_list(rmin..rmax) ++ indices_to_drop, <<>>}\n          <<0::size(8), rmin::little-size(24), rmax::little-size(24), rem::binary>> -> {Enum.to_list(rmin..rmax) ++ indices_to_drop, rem}\n          _ -> :error\n        end\n      end)\n\n    case parsed do\n      {msg_indices_to_drop, <<>>} ->\n        msg_indices_to_drop\n\n      _ ->\n        Logger.error(\"Ack packet failed to parse: #{inspect(full)}\")\n        []\n    end\n  end\n\n  defp make_ping_buffer(base_time) do\n    <<Message.binary(:ping), RakNet.Server.timestamp(base_time)::size(64)>>\n  end\n\n  defp make_pong_buffer(ping_time, base_time) do\n    <<Message.binary(:pong), ping_time::size(64), RakNet.Server.timestamp(base_time)::size(64)>>\n  end\n\n  # Assume a high-but-not-crazy ping until the connection negotiation is finished (for the purpose of scheduling retries)\n  defp rtt(%State{last_rtts: []}), do: 200\n  defp rtt(%State{last_rtts: rtts}), do: Enum.sum(rtts) / length(rtts)\nend\n"
  },
  {
    "path": "lib/message.ex",
    "content": "defmodule RakNet.Message do\n  @moduledoc \"\"\"\n  Message types that RakNet can send. These are the first 8 bits of the packet.\n  Follows RakLib protocol: https://github.com/pmmp/RakLib/tree/master/src/protocol\n  \"\"\"\n\n  @names_and_vals %{\n    # These come from RakNet's DefaultMessageIDTypes enum (in MessageIdentifiers.h)\n    :ping => 0x00,\n    :unconnected_ping => 0x01,\n    :unconnected_ping_open_connections => 0x02,\n    :pong => 0x03,\n    :open_connection_request_1 => 0x05,\n    :open_connection_reply_1 => 0x06,\n    :open_connection_request_2 => 0x07,\n    :open_connection_reply_2 => 0x08,\n    # ID_CONNECTION_REQUEST\n    :client_connect => 0x09,\n    # ID_CONNECTION_REQUEST_ACCEPTED: Tell the client the connection request accepted\n    :server_handshake => 0x10,\n    # ID_CONNECTION_ATTEMPT_FAILED: Sent to the player when a connection request cannot be completed\n    :connection_attempt_failed => 0x11,\n    # ID_NEW_INCOMING_CONNECTION: A remote client has successfully connected\n    :client_handshake => 0x13,\n    :client_disconnect => 0x15,\n    # ID_CONNECTION_LOST: Reliable packet delivery failed, connection closed\n    :connection_lost => 0x16,\n    # ID_INCOMPATIBLE_PROTOCOL_VERSION\n    :incompatible_version => 0x19,\n    :unconnected_pong => 0x1C,\n    :advertise_system => 0x1D,\n\n    # These come from RakNet's reliability layer and \"congestion control\"---they are datagram headers\n    # The indices are the priority (0 to 15)\n    :data_packet_0 => 0x80,\n    :data_packet_1 => 0x81,\n    :data_packet_2 => 0x82,\n    :data_packet_3 => 0x83,\n    :data_packet_4 => 0x84,\n    :data_packet_5 => 0x85,\n    :data_packet_6 => 0x86,\n    :data_packet_7 => 0x87,\n    :data_packet_8 => 0x88,\n    :data_packet_9 => 0x89,\n    :data_packet_A => 0x8A,\n    :data_packet_B => 0x8B,\n    :data_packet_C => 0x8C,\n    :data_packet_D => 0x8D,\n    :data_packet_E => 0x8E,\n    :data_packet_F => 0x8F,\n    :nack => 0xA0,\n    :ack => 0xC0\n  }\n\n  @msg_names MapSet.new(Map.keys(@names_and_vals))\n  @msg_binary_vals MapSet.new(Map.values(@names_and_vals))\n  @vals_and_names Map.new(@names_and_vals, fn {name, val} -> {val, name} end)\n\n  @doc \"\"\"\n  Set of all message types listed above---:ping, :unconnected_ping, :pong, etc.\n  \"\"\"\n  def known_message_names, do: @msg_names\n\n  @doc \"\"\"\n  Set of all hex values that act as packet type identifiers.\n  E.g., 0x0 for ping, 0x3 for pong, 0x1d for advertise system, etc.\n  \"\"\"\n  def known_messages, do: @msg_binary_vals\n\n  @doc \"\"\"\n  True if we have a name atom for this message type\n  \"\"\"\n  def is_known?(message_binary) when is_integer(message_binary), do: MapSet.member?(@msg_binary_vals, message_binary)\n\n  def is_protocol_data?(message_binary) when is_integer(message_binary), do: message_binary in 0x80..0x8F\n\n  @doc \"\"\"\n  The message name atom for this binary message; :error if we don't recognize it\n  \"\"\"\n  def name(message_binary) when is_integer(message_binary), do: Map.get(@vals_and_names, message_binary, :error)\n\n  def binary(message_name) when is_atom(message_name), do: Map.fetch!(@names_and_vals, message_name)\n\n  # \"Magic\" bytes used to distinguish offline messages from garbage\n  def offline_msg_id, do: <<0, 255, 255, 0, 254, 254, 254, 254, 253, 253, 253, 253, 18, 52, 86, 120>>\nend\n"
  },
  {
    "path": "lib/packet.ex",
    "content": "defmodule RakNet.Packet do\n  @moduledoc \"Encoding & decoding utils for RakLib data, taken from ExRakLib\"\n  require Logger\n  alias RakNet.ReliabilityLayer\n  alias RakNet.ReliabilityLayer.Reliability\n\n  def decode_with_timestamp(<<timestamp::size(32), data::binary>>) do\n    Map.put(decode_no_timestamp(data), :timestamp, timestamp)\n  end\n\n  def decode_no_timestamp(data, internal \\\\ false) do\n    <<sequence_number::little-size(24), rest::binary>> = :erlang.iolist_to_binary(data)\n\n    %{\n      sequence_number: sequence_number,\n      encapsulated_packets: decode_no_timestamp(rest, [], internal),\n      timestamp: -1\n    }\n  rescue\n    e ->\n      Logger.error(inspect(e))\n      %{sequence_number: -1, encapsulated_packets: []}\n  end\n\n  defp decode_no_timestamp(\"\", encapsulated_packets, _internal) do\n    Enum.reverse(encapsulated_packets)\n  end\n\n  defp decode_no_timestamp(rest, encapsulated_packets, internal) do\n    {packet, rest} = decode_encapsulated_packet(rest, internal)\n    decode_no_timestamp(rest, [packet | encapsulated_packets], internal)\n  end\n\n  def encode(%{sequence_number: seq, encapsulated_packets: packets, timestamp: ts}) when ts >= 0 do\n    :erlang.iolist_to_binary([<<ts::size(32), seq::little-size(24)>>, Enum.map(packets, &encode_encapsulated_packet(&1, false))])\n  end\n\n  def encode(%{sequence_number: seq_number, encapsulated_packets: encapsulated}, internal \\\\ false) do\n    :erlang.iolist_to_binary([<<seq_number::little-size(24)>>, Enum.map(encapsulated, &encode_encapsulated_packet(&1, internal))])\n  end\n\n  def decode_encapsulated_packet(data, internal) do\n    <<reliability::unsigned-size(3), has_split::unsigned-size(5), post_header::binary>> = data\n\n    is_reliable = Reliability.is_reliable?(reliability)\n    is_sequenced = Reliability.is_sequenced?(reliability)\n\n    {length, identifier_ack, post_length} =\n      if internal do\n        <<length::size(32), identifier_ack::size(32), rest::binary>> = post_header\n        {length, identifier_ack, rest}\n      else\n        <<length::size(16), rest::binary>> = post_header\n        {trunc(Float.ceil(length / 8)), nil, rest}\n      end\n\n    # message_index is actually the sequencing index for :unreliable_sequenced\n    {message_index, post_reliability} =\n      if is_reliable or is_sequenced do\n        <<message_index::little-size(24), rest::binary>> = post_length\n        {message_index, rest}\n      else\n        {nil, post_length}\n      end\n\n    {order_index, order_channel, post_ordering} =\n      if is_sequenced do\n        <<order_index::little-size(24), order_channel::size(8), rest::binary>> = post_reliability\n        {order_index, order_channel, rest}\n      else\n        {nil, nil, post_reliability}\n      end\n\n    {split_count, split_id, split_index, post_split} =\n      if has_split > 0 do\n        <<split_count::size(32), split_id::size(16), split_index::size(32), rest::binary>> = post_ordering\n        {split_count, split_id, split_index, rest}\n      else\n        {nil, nil, nil, post_ordering}\n      end\n\n    <<buffer::binary-size(length), rest::binary>> = post_split\n\n    {%ReliabilityLayer.Packet{\n       reliability: Reliability.name(reliability),\n       has_split: has_split,\n       length: length,\n       identifier_ack: identifier_ack,\n       message_index: if(is_reliable, do: message_index, else: nil),\n       sequencing_index: if(is_reliable, do: nil, else: message_index),\n       order_index: order_index,\n       order_channel: order_channel,\n       split_count: split_count,\n       split_id: split_id,\n       split_index: split_index,\n       buffer: buffer\n     }, rest}\n  end\n\n  def encode_encapsulated_packet(%ReliabilityLayer.Packet{} = p, internal) do\n    if ReliabilityLayer.Packet.valid?(p) do\n      is_reliable = Reliability.is_reliable?(p.reliability)\n      is_sequenced = Reliability.is_sequenced?(p.reliability)\n      index = if is_reliable, do: p.message_index, else: p.sequencing_index\n\n      # TODO: Support splitting: https://github.com/mhsjlw/node-raknet/blob/master/src/client.js#L144\n      <<Reliability.binary(p.reliability)::unsigned-size(3), p.has_split::unsigned-size(5)>> <>\n        if internal do\n          <<byte_size(p.buffer)::size(32), p.identifier_ack::size(32)>>\n        else\n          <<trunc(byte_size(p.buffer) * 8)::size(16)>>\n        end <>\n        if is_reliable or is_sequenced do\n          <<index::little-size(24)>> <>\n            if is_sequenced do\n              <<p.order_index::little-size(24), p.order_channel::size(8)>>\n            else\n              <<>>\n            end\n        else\n          <<>>\n        end <>\n        if p.has_split > 0 do\n          <<p.split_count::size(32), p.split_id::size(16), p.split_index::size(32)>>\n        else\n          <<>>\n        end <>\n        p.buffer\n    else\n      Logger.error(\"Invalid packet: #{inspect(p, binaries: :as_binaries, limit: :infinity)}\")\n      <<>>\n    end\n  end\nend\n"
  },
  {
    "path": "lib/reliability_layer.ex",
    "content": "defmodule RakNet.ReliabilityLayer.Reliability do\n  @moduledoc \"Taken from RakNet's PacketPriority.h\"\n\n  @names_and_vals %{\n    :unreliable => 0,\n    :unreliable_sequenced => 1,\n    :reliable => 2,\n    :reliable_ordered => 3,\n    :reliable_sequenced => 4,\n\n    # These are the same as unreliable/reliable/reliable ordered, except that the business logic provider\n    # will get an :ack message when the client acknowledges receipt\n    :unreliable_ack_receipt => 5,\n    :reliable_ack_receipt => 6,\n    :reliable_ordered_ack_receipt => 7\n  }\n\n  @vals_and_names Map.new(@names_and_vals, fn {name, val} -> {val, name} end)\n\n  @doc \"\"\"\n  The message name atom for this binary message; :error if we don't recognize it\n  \"\"\"\n  def name(reliability_binary) when is_integer(reliability_binary), do: Map.get(@vals_and_names, reliability_binary, :error)\n\n  def binary(reliability_name) when is_atom(reliability_name), do: Map.fetch!(@names_and_vals, reliability_name)\n\n  def valid?(reliability_atom) when is_atom(reliability_atom), do: Map.has_key?(@names_and_vals, reliability_atom)\n\n  def is_reliable?(reliability_binary) when is_integer(reliability_binary), do: reliability_binary in [2, 3, 4, 6, 7]\n  def is_reliable?(reliability_atom) when is_atom(reliability_atom), do: binary(reliability_atom) in [2, 3, 4, 6, 7]\n\n  def is_ordered?(reliability_binary) when is_integer(reliability_binary), do: reliability_binary == 3\n  def is_ordered?(reliability_atom) when is_atom(reliability_atom), do: reliability_atom == :reliable_ordered\n\n  def is_sequenced?(reliability_binary) when is_integer(reliability_binary), do: reliability_binary in [1, 3, 4, 7]\n  def is_sequenced?(reliability_atom) when is_atom(reliability_atom), do: binary(reliability_atom) in [1, 3, 4, 7]\n\n  def needs_client_ack?(reliability_binary) when is_integer(reliability_binary), do: reliability_binary in [5, 6, 7]\n  def needs_client_ack?(reliability_atom) when is_atom(reliability_atom), do: binary(reliability_atom) in [5, 6, 7]\nend\n\ndefmodule RakNet.ReliabilityLayer.Packet do\n  @moduledoc \"See ReliabilityLayer.cpp, ReliabilityLayer::WriteToBitStreamFromInternalPacket()\"\n  alias RakNet.ReliabilityLayer.Reliability\n\n  @enforce_keys [:reliability, :buffer]\n  defstruct priority: 4,\n            reliability: Reliability.binary(:reliable_ordered),\n            has_split: 0,\n            length: -1,\n\n            # Used for internal packets only\n            identifier_ack: nil,\n\n            # Used for all reliable types\n            message_index: nil,\n\n            # Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED\n            sequencing_index: nil,\n\n            # Used for UNRELIABLE_SEQUENCED, RELIABLE_SEQUENCED, RELIABLE_ORDERED.\n            order_index: nil,\n            order_channel: 0,\n\n            # Split packets only\n            split_count: nil,\n            split_id: nil,\n            split_index: nil,\n\n            # The actual (encapsulated) packet data\n            buffer: nil\n\n  # credo:disable-for-next-line\n  def valid?(%RakNet.ReliabilityLayer.Packet{} = p) do\n    msg_idx_ok = not Reliability.is_reliable?(p.reliability) or (is_integer(p.message_index) and p.message_index >= 0)\n    order_idx_ok = not Reliability.is_sequenced?(p.reliability) or (is_integer(p.order_index) and p.order_index >= 0)\n\n    split_ok =\n      p.has_split == 0 or\n        (is_integer(p.split_count) and p.split_count > 0 and\n           is_integer(p.split_id) and is_integer(p.split_index))\n\n    msg_idx_ok and order_idx_ok and split_ok and\n      p.priority >= 0 and p.priority < 0xF and\n      Reliability.valid?(p.reliability) and p.buffer != nil\n  end\nend\n"
  },
  {
    "path": "lib/server.ex",
    "content": "defmodule RakNet.Server do\n  @moduledoc \"\"\"\n  A server implementing the RakNet protocol\n  \"\"\"\n  require Logger\n  import XUtil.Time, only: [unix_timestamp_ms: 0]\n\n  defmodule Spawner do\n    @moduledoc \"A dumb wrapper to make a spawned function supervisable\"\n    def start_link(impl_fn), do: {:ok, spawn(impl_fn)}\n  end\n\n  def child_spec([receiver | [port | options]]) do\n    %{\n      # String.to_atom/1 is fine here, because we'll be creating a very limited number of listeners per port\n      # credo:disable-for-next-line\n      id: String.to_atom(\"#{__MODULE__}:#{port}\"),\n      start: {__MODULE__, :start_link, [receiver, port, options]},\n      restart: :permanent,\n      type: :worker\n    }\n  end\n\n  def start_link(client_module, port, options \\\\ []) when is_atom(client_module) and is_integer(port) do\n    defaults = [\n      custom_packets: [],\n      custom_types: [],\n      client_data: %{},\n      client_timeout_ms: 10_000,\n      # Corresponds to #define INCLUDE_TIMESTAMP_WITH_DATAGRAMS\n      # (which is in turn enabled by USE_SLIDING_WINDOW_CONGESTION_CONTROL in the RakNetDefinesOverrides.h)\n      include_timestamp_with_datagrams: false,\n      # Corresponds to #define MAXIMUM_NUMBER_OF_INTERNAL_IDS\n      max_number_of_internal_ids: 10,\n      open_ipv4_socket: true,\n      open_ipv6_socket: System.get_env(\"SEPARATE_IPV6_PORT\") != \"false\",\n      # TODO: use gen_udp directly and reopen the socket if it gets closed\n      send: &Socket.Datagram.send!/3,\n      server_identifier: make_unique_id(),\n      offline_ping_response: \"RakNet Server\"\n    ]\n\n    host = Keyword.get(options, :host, {0, 0, 0, 0})\n\n    config =\n      defaults\n      |> Keyword.merge(options)\n      |> Enum.into(%{\n        client_module: struct(client_module),\n        encoded_host: RakNet.SystemAddress.encode(%{address: host, port: port})\n      })\n\n    socket_v4 = if config[:open_ipv4_socket], do: elem(open_socket(4, port), 1), else: nil\n    socket_v6 = if config[:open_ipv6_socket], do: elem(open_socket(6, port), 1), else: nil\n    sockets = {socket_v4, socket_v6}\n\n    Supervisor.start_link(\n      [\n        # String.to_atom/1 is fine here, because we'll be creating a very limited number of listeners per port\n        # credo:disable-for-lines:2\n        %{id: String.to_atom(\"#{__MODULE__}:#{port}_ipv4\"), start: {Spawner, :start_link, [fn -> serve_v4(sockets, config) end]}},\n        %{id: String.to_atom(\"#{__MODULE__}:#{port}_ipv6\"), start: {Spawner, :start_link, [fn -> serve_v6(sockets, config) end]}}\n      ],\n      strategy: :one_for_one\n    )\n  end\n\n  defp open_socket(ip_version, port) when ip_version == 4 or ip_version == 6 do\n    addl_args = if ip_version == 4, do: [:inet], else: [:inet6, {:ipv6_v6only, true}]\n    :gen_udp.open(port, [:binary, {:active, false}] ++ addl_args)\n  end\n\n  @doc \"The current Unix timestamp, in milliseconds\"\n  def timestamp(offset \\\\ 0), do: unix_timestamp_ms() - offset\n\n  @doc \"A 64-bit unique ID\"\n  def make_unique_id, do: <<timestamp()::size(48), :rand.uniform(65_536)::size(16)>>\n\n  defp serve_v4({nil, _}, _config), do: :ok\n  defp serve_v4({socket_v4, _} = sockets, config), do: serve_impl(&serve_v4/2, socket_v4, sockets, config)\n  defp serve_v6({_, nil}, _config), do: :ok\n  defp serve_v6({_, socket_v6} = sockets, config), do: serve_impl(&serve_v6/2, socket_v6, sockets, config)\n\n  defp serve_impl(server, receive_socket, sockets, config) do\n    # We have to do basic decoding in the main process, because it may update our client connection state. :(\n    case Socket.Datagram.recv!(receive_socket) do\n      nil ->\n        server.(sockets, config)\n\n      {packet, {client_ip, client_port}} = received_raw ->\n        case decode(received_raw, sockets, config) do\n          # Send to a client process to parse & optionally respond (never do any work here, since this is a single process)\n          {:connected, {client, packet_type, data}} -> RakNet.Connection.handle_message(client, packet_type, data)\n          {:unconnected, decoded} -> spawn(fn -> handle_unconnected_packet(sockets, config, decoded) end)\n          _ -> Logger.error(\"Failed to decode packet from #{inspect(client_ip)}:#{client_port}\\nPacket: #{inspect(packet)}\")\n        end\n    end\n\n    server.(sockets, config)\n  end\n\n  # One of:\n  #   {:error, %{}}\n  #   {:unconnected, {client_ip_and_port, :unconnected_ping, data}}\n  #   {:connected, {client, packet_type, data}}\n  defp decode({packet, client_ip_and_port}, sockets, config) do\n    case packet_decode(packet, config) do\n      {:error, _msg} = err ->\n        err\n\n      {:ok, :open_connection_request_1, data} ->\n        {:connected, {open_connection(sockets, client_ip_and_port, config), :open_connection_request_1, data}}\n\n      {:ok, :unconnected_ping, data} ->\n        {:unconnected, {client_ip_and_port, :unconnected_ping, data}}\n\n      {:ok, :unconnected_ping_open_connections, data} ->\n        {:unconnected, {client_ip_and_port, :unconnected_ping_open_connections, data}}\n\n      {:ok, packet_type, data} ->\n        case lookup(client_ip_and_port) do\n          nil -> {:unconnected, {client_ip_and_port, packet_type, data}}\n          client -> {:connected, {client, packet_type, data}}\n        end\n    end\n  end\n\n  defp open_connection(sockets, {host, port} = client_ip_and_port, config) do\n    existing_client = lookup(client_ip_and_port)\n\n    # TODO: Nuke the existing client, create a new one\n    if existing_client do\n      Logger.debug(\"Existing client requested to open a new connection\")\n      RakNet.Connection.stop(existing_client)\n    else\n      Logger.debug(\"Open a new connection to #{inspect(host)}:#{port}\")\n    end\n\n    {:ok, new_client} =\n      RakNet.Connection.start_link(%RakNet.Connection.State{\n        host: host,\n        port: port,\n        encoded_host: config[:encoded_host],\n        client_ips_and_ports: [client_ip_and_port],\n        encoded_client: RakNet.SystemAddress.encode(%{address: host, port: port}),\n        timeout_ms: config[:client_timeout_ms],\n        base_time: timestamp(),\n        server_identifier: config[:server_identifier],\n        client_module: config[:client_module],\n        client_data: config[:client_data],\n        include_timestamp_with_datagrams: config[:include_timestamp_with_datagrams],\n        max_number_of_internal_ids: config[:max_number_of_internal_ids],\n        respond: make_responder(sockets, config)\n      })\n\n    # Do *not* bring down the whole server if the connection dies; if that happens,\n    # we'll just start a new proceses next time we hear from the user.\n    Process.unlink(new_client)\n    Registry.register(RakNet.Connection, client_ip_and_port, new_client)\n    new_client\n  end\n\n  defp lookup(client_ip_and_port) do\n    case Registry.lookup(RakNet.Connection, client_ip_and_port) do\n      [{_, client}] ->\n        if Process.alive?(client) do\n          client\n        else\n          Registry.unregister(RakNet.Connection, client_ip_and_port)\n          nil\n        end\n\n      _ ->\n        nil\n    end\n  end\n\n  defp packet_decode(<<identifier::unsigned-size(8), data::binary>>, _config) do\n    case RakNet.Message.name(identifier) do\n      :error -> {:error, \"Unknown packet identifier\"}\n      name -> {:ok, name, data}\n    end\n  end\n\n  defp handle_unconnected_packet(sockets, config, {client_ip_port, :unconnected_ping, <<ping_time::size(64), _::binary>>}) do\n    send_unconnected_pong(sockets, client_ip_port, ping_time, config)\n  end\n\n  defp handle_unconnected_packet(sockets, config, {client_ip_port, :unconnected_ping_open_connections, <<ping_time::size(64), _::binary>>}) do\n    send_unconnected_pong(sockets, client_ip_port, ping_time, config)\n  end\n\n  defp handle_unconnected_packet(sockets, config, {client_ip_port, _other, _data}) do\n    send(sockets, <<RakNet.Message.binary(:connection_lost)>>, client_ip_port, config)\n  end\n\n  defp send_unconnected_pong(sockets, client_ip_port, ping_time, config) do\n    send(\n      sockets,\n      <<RakNet.Message.binary(:unconnected_pong), ping_time::size(64), config[:server_identifier]::binary,\n        RakNet.Message.offline_msg_id()::binary>>,\n      client_ip_port,\n      config\n    )\n  end\n\n  defp make_responder(sockets, config) do\n    fn packet, ip_and_port ->\n      send(sockets, packet, ip_and_port, config)\n    end\n  end\n\n  defp send(sockets, packet, client_ip_and_port, config) do\n    sockets\n    |> choose_socket(client_ip_and_port)\n    |> config[:send].(packet, client_ip_and_port)\n  end\n\n  defp choose_socket({socket_v4, socket_v6}, client_ip_and_port) do\n    case RakNet.SystemAddress.ip_version(client_ip_and_port) do\n      4 -> socket_v4\n      6 -> socket_v6\n    end\n  end\nend\n"
  },
  {
    "path": "lib/system_address.ex",
    "content": "defmodule RakNet.SystemAddress do\n  @moduledoc \"Tools for encoding & decoding IP addresses & ports\"\n\n  def encode(%{version: 4, address: {o1, o2, o3, o4}, port: port}),\n    do: <<4::size(8), o1::unsigned-size(8), o2::unsigned-size(8), o3::unsigned-size(8), o4::unsigned-size(8), port::unsigned-size(16)>>\n\n  def encode(%{version: 6, address: {h1, h2, h3, h4, h5, h6, h7, h8}, port: port}) do\n    <<6::size(8), 28::unsigned-size(8), 30::unsigned-size(8), port::unsigned-size(16), 0::size(32), h1::unsigned-size(16),\n      h2::unsigned-size(16), h3::unsigned-size(16), h4::unsigned-size(16), h5::unsigned-size(16), h6::unsigned-size(16),\n      h7::unsigned-size(16), h8::unsigned-size(16), 0::size(32)>>\n  end\n\n  def encode(%{address: addr, port: _port} = args) do\n    encode(Map.put(args, :version, ip_version(addr)))\n  end\n\n  # Octet and hextet versions\n  def ip_version({_, _, _, _}), do: 4\n  def ip_version({_, _, _, _, _, _, _, _}), do: 6\n\n  # Host-and-port patterns\n  def ip_version({{_, _, _, _}, port}) when is_integer(port), do: 4\n  def ip_version({{_, _, _, _, _, _, _, _}, port}) when is_integer(port), do: 6\n\n  def decode_many(addresses_and_ports) when is_bitstring(addresses_and_ports) do\n    decode_address_port(addresses_and_ports)\n  end\n\n  defp decode_address_port(bin, prev \\\\ [])\n\n  defp decode_address_port(<<4::size(8), address::binary-size(4), port::unsigned-size(16), rest::binary>>, prev) do\n    <<o1::unsigned-size(8), o2::unsigned-size(8), o3::unsigned-size(8), o4::unsigned-size(8)>> = address\n    decode_address_port(rest, [%{version: 4, address: {o1, o2, o3, o4}, port: port} | prev])\n  end\n\n  # Note: ipv6 addresses are serialized as their whole sockaddr_in6 struct\n  # Fields are:\n  #  1. Length of the struct (28 bytes == 224 bits)\n  #  2. sa_family_t, fixed value of AF_INET6 == 30 (0x1e)\n  #  3. port\n  #  4. flow info (???)\n  #  5. in6_addr (128 bits)\n  #  6. scope ID (???)\n  defp decode_address_port(<<6::size(8), 28::unsigned-size(8), 30::unsigned-size(8), ipv6_body::bitstring-size(208), rest::binary>>, prev) do\n    <<port::unsigned-size(16), _::unsigned-size(32), addr::bitstring-size(128), _::unsigned-size(32)>> = ipv6_body\n\n    <<a1::unsigned-size(16), a2::unsigned-size(16), a3::unsigned-size(16), a4::unsigned-size(16), a5::unsigned-size(16),\n      a6::unsigned-size(16), a7::unsigned-size(16), a8::unsigned-size(16)>> = addr\n\n    decode_address_port(rest, [%{version: 6, address: {a1, a2, a3, a4, a5, a6, a7, a8}, port: port} | prev])\n  end\n\n  defp decode_address_port(<<>>, prev) when is_list(prev), do: prev\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule RakNet.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :raknet,\n      version: \"0.1.0\",\n      build_path: \"_build\",\n      deps_path: \"deps\",\n      lockfile: \"mix.lock\",\n      elixir: \"~> 1.11\",\n      elixirc_options: [warnings_as_errors: halt_on_warnings?(Mix.env())],\n      start_permanent: Mix.env() == :prod,\n      consolidate_protocols: Mix.env() != :test,\n      deps: deps()\n    ]\n  end\n\n  def application do\n    [\n      extra_applications: [:logger],\n      mod: {RakNet.Application, []}\n    ]\n  end\n\n  defp deps do\n    [\n      {:x_util, git: \"https://github.com/X-Plane/elixir-xutil.git\", branch: \"main\"},\n      {:socket, \"~> 0.3.13\"},\n      {:assertions, \"~> 0.10\", only: :test},\n      {:credo, \"~> 1.5.1\", only: [:dev, :test], runtime: false}\n    ]\n  end\n\n  # Clever hack to allow unused functions and the like in test, but not dev or prod:\n  # https://blog.rentpathcode.com/elixir-warnings-as-errors-sometimes-f5a8d2c96b15\n  defp halt_on_warnings?(:test), do: false\n  defp halt_on_warnings?(_), do: true\nend\n"
  },
  {
    "path": "raknet.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"ELIXIR_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n    <output url=\"file://$MODULE_DIR$/_build/dev/lib/raknet/ebin\" />\n    <output-test url=\"file://$MODULE_DIR$/_build/test/lib/raknet/ebin\" />\n    <content url=\"file://$MODULE_DIR$\">\n      <sourceFolder url=\"file://$MODULE_DIR$/lib\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/spec\" isTestSource=\"true\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/test\" isTestSource=\"true\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/assets/node_modules/phoenix\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/assets/node_modules/phoenix_html\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/cover\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/deps\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/doc\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/logs\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/comb\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/socket\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/x_util\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/assertions\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/dev/lib/socket\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/dev/lib/comb\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/dev/lib/x_util\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/dev/lib/bunt\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/dev/lib/credo\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/dev/lib/file_system\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/dev/lib/jason\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/jason\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/bunt\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/file_system\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/_build/test/lib/credo\" />\n    </content>\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n    <orderEntry type=\"module\" module-name=\"x_util\" />\n    <orderEntry type=\"library\" name=\"assertions\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"socket\" level=\"project\" />\n    <orderEntry type=\"module\" module-name=\"x_util\" />\n    <orderEntry type=\"module\" module-name=\"x_util\" />\n    <orderEntry type=\"library\" name=\"x_util\" level=\"project\" />\n    <orderEntry type=\"module\" module-name=\"x_util\" />\n    <orderEntry type=\"library\" name=\"comb\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"ex_doc\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"ecto\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"absinthe\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"credo\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"file_system\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"bunt\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"jason\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"excoveralls\" level=\"project\" />\n    <orderEntry type=\"library\" name=\"inch_ex\" level=\"project\" />\n  </component>\n</module>"
  },
  {
    "path": "test/connection_test.exs",
    "content": "defmodule RakNet.ConnectionTest do\n  use ExUnit.Case, async: true\n  doctest RakNet.Connection\n  import RakNet.Connection, only: [message_indices_from_ack: 1]\n\n  require Assertions\n  import Assertions, only: [assert_lists_equal: 2]\n\n  @min_is_max 1\n  @min_is_not_max 0\n  @packet_count_1 1\n\n  test \"handles single acks\" do\n    assert [2] == message_indices_from_ack(<<@packet_count_1::size(16), @min_is_max::size(8), 2::little-size(24)>>)\n\n    assert message_indices_from_ack(<<0, 1, 1, 0, 0, 0>>) == [0]\n    assert message_indices_from_ack(<<0, 1, 1, 1, 0, 0>>) == [1]\n    assert message_indices_from_ack(<<0, 1, 1, 3, 0, 0>>) == [3]\n  end\n\n  test \"handles ack range\" do\n    assert_lists_equal(\n      [1, 2, 3, 4],\n      message_indices_from_ack(<<@packet_count_1::size(16), @min_is_not_max::size(8), 1::little-size(24), 4::little-size(24)>>)\n    )\n\n    assert_lists_equal([1, 2], message_indices_from_ack(<<0, 1, 0, 1, 0, 0, 2, 0, 0>>))\n  end\n\n  test \"handles many disjoint acks\" do\n    assert_lists_equal(\n      [0, 4, 5, 6, 8, 9, 10, 11, 48],\n      message_indices_from_ack(<<\n        # Packet count\n        4::size(16),\n        # Drop 0\n        @min_is_max::size(8),\n        0::little-size(24),\n        # Drop 4, 5, 6\n        @min_is_not_max::size(8),\n        4::little-size(24),\n        6::little-size(24),\n        # Drop 8-11, inclusive\n        @min_is_not_max::size(8),\n        8::little-size(24),\n        11::little-size(24),\n        # Drop 48\n        @min_is_max::size(8),\n        48::little-size(24)\n      >>)\n    )\n  end\nend\n"
  },
  {
    "path": "test/message_test.exs",
    "content": "defmodule RakNet.MessageTest do\n  use ExUnit.Case, async: true\n\n  test \"lists the message name atoms\" do\n    known_msg_names = RakNet.Message.known_message_names()\n    expected_atoms = MapSet.new([:ping, :unconnected_ping, :pong, :client_disconnect, :data_packet_8, :ack])\n    assert MapSet.subset?(expected_atoms, known_msg_names)\n  end\n\n  test \"lists the message binary values\" do\n    known_msgs = RakNet.Message.known_messages()\n    expected_vals = MapSet.new([0, 1, 2, 3, 5, 6, 7, 8, 9, 0x10, 0x13, 0x15, 0x1C, 0x1D, 0x80, 0x8F, 0xA0, 0xC0])\n    assert MapSet.subset?(expected_vals, known_msgs)\n\n    expected_missing = MapSet.new([4, 0x0A, 0x12, 0x14, 0x1B, 0x1E])\n    assert MapSet.disjoint?(known_msgs, expected_missing)\n\n    assert Enum.all?(Enum.map(expected_vals, &RakNet.Message.is_known?/1))\n    assert !Enum.any?(Enum.map(expected_missing, &RakNet.Message.is_known?/1))\n  end\n\n  test \"fetches message name atoms from binary values\" do\n    assert RakNet.Message.name(0) == :ping\n    assert RakNet.Message.name(0x13) == :client_handshake\n    assert RakNet.Message.name(0x13) == :client_handshake\n\n    assert RakNet.Message.name(0x14) == :error\n    assert RakNet.Message.name(0x1B) == :error\n  end\nend\n"
  },
  {
    "path": "test/packet_test.exs",
    "content": "defmodule RakNet.PacketTest do\n  use ExUnit.Case\n\n  @test_data_packet <<25, 62, 69, 23, 124, 1, 0, 32, 0, 224, 105, 1, 0, 1, 0, 0, 0, 144, 9, 89, 211, 245, 50, 93, 224, 84, 166, 81, 195, 12,\n                      94, 253, 127, 183, 0, 128, 255, 127, 255, 127, 195, 56, 128, 64, 0>>\n\n  @tag packet: true\n  test \"decodes unreliable sequenced data packets\" do\n    assert %{encapsulated_packets: [packet | []]} = RakNet.Packet.decode_with_timestamp(@test_data_packet)\n    assert packet.priority == 4\n    assert packet.reliability == :unreliable_sequenced\n    assert packet.length == 28\n    assert is_nil(packet.message_index)\n    assert not is_nil(packet.sequencing_index)\n  end\nend\n"
  },
  {
    "path": "test/server_test.exs",
    "content": "defmodule RakNet.ServerTest do\n  use ExUnit.Case\n  require Logger\n  import RakNet.Packet, only: [decode_with_timestamp: 1, decode_no_timestamp: 1]\n\n  @moduledoc \"\"\"\n  This is an acceptance test (in the sense popularized in the C++ community by Clare Macrae.\n\n  We run through the exact sequence of packets that an official RakNet sample app sends, and assert\n  that we send the correct responses.\n  \"\"\"\n\n  # I've hard-coded this as my GUID in my copy of the RakNet code, so that I can create reproducible packet tests\n  @fixed_id 12_345_678_901_234_567_890\n  @localhost {127, 0, 0, 1}\n  @localhost6 {0, 0, 0, 0, 0, 0, 0, 1}\n\n  test \"handles client connection negotiation\" do\n    {_server_pid, server_host_and_port} = start_server(49_101)\n    {client_send, client_send_padded} = make_client_send_fns(49_100, server_host_and_port)\n\n    # :open_connection_request_1 -> :open_connection_reply_1\n    open_conn_req_1_with_retries(client_send_padded, fn -> server_responded(\"0600ffff00fefefefefdfdfdfd12345678ab54a98ceb1f0ad20005d4\") end)\n\n    # :open_connection_request_2 -> :open_connection_reply_2\n    client_send.(\"0700ffff00fefefefefdfdfdfd123456780480fffffebfcd05d400059bb99c3c475c\")\n\n    assert server_responded(\"0800ffff00fefefefefdfdfdfd12345678ab54a98ceb1f0ad20480fffffebfcc05d400\", normalize_ip_addresses: true),\n           \"Failed to respond to open connection request 2\"\n\n    # :data_packet_4[:client_connect] -> :data_packet_4[:server_handshake]\n    client_send.(\"840000004001080000000900059bb98e0d2f4a00000000000000160052756d70656c7374696c74736b696e\")\n\n    assert server_responded_many([\n             {\"8400000060030000000000000000100480fffffebfcc0000043f57fe32bfcd04ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000000000000000000160000000000004345\",\n              [\n                normalize_ip_addresses: true,\n                # Ignore the timestamp at the end\n                discard_last_bytes: 4\n              ]},\n             {\"c0000101000000\", []}\n           ]),\n           \"Failed to respond to client_connect with handshake and/or ack client ordered packet 0\"\n\n    # Have the client ack the handshake\n    client_send.(\"c0000101000000\")\n    # :data_packet_4[client_handshake] requires a pong response from its ping\n    client_send.(\n      \"840100006002f001000000000000130480fffffebfcd043f57fe32bfcc04ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000000000000000040160000000000000025000048000000000000000025\"\n    )\n\n    # :data_packet_4[ping]\n    client_send.(\"8402000000004800000000000000002f\")\n\n    # :data_packet_4[ping, pong for 0x25, pong for 0x2f]\n    # \"8401000000004800000000000000402d000088030000000000000025000000000000402d00008803000000000000002f000000000000402d\",\n    assert server_sent_packets_with(2, [\n             {:ack, [1, 2]},\n             {:ping, :unreliable},\n             {:pong, :unreliable},\n             {:pong, :unreliable},\n             {:pong, :unreliable}\n           ]),\n           \"Failed to handle client handshake and additional enqueued pong\"\n\n    # Ack the data packet with the ping and 2 pongs\n    client_send.(\"c0000101010000\")\n    # 2 pongs to respond to our pings\n    client_send.(\"84030000000088030000000000003ca4000000000000002c000088030000000000003ca4000000000000002c\")\n\n    assert server_responded(\"c0000101030000\"), \"Failed to respond to ack client's packet 3\"\n\n    # Send an actual \"business logic\" packet---a chat message \"ahoy\"\n    client_send.(\"841a00006000280800000200000061686f7900\")\n    # Note that the \"a\" gets dropped from our \"ahoy\" packet, because the first byte of the message is always the client packet type\n    assert receive_business_logic_msg() == \"hoy\" <> <<0>>, \"Failed to receive 'ahoy' business logic message\"\n  end\n\n  @tag xplane: true\n  test \"handles client negotiation with embedded timestamps\" do\n    has_embedded_timestamp = true\n    # Send timestamps with datagrams!\n    {_server_pid, server_host_and_port} = start_xplane_server(49_108)\n    {client_send, client_send_padded} = make_client_send_fns(49_109, server_host_and_port)\n\n    # :open_connection_request_1 -> :open_connection_reply_1\n    open_conn_req_1_with_retries(client_send_padded, fn -> server_sent_packet_with({:open_connection_reply_1, :connection_negotiation}) end)\n\n    # :open_connection_request_2 -> :open_connection_reply_2\n    client_send.(\"0700FFFF00FEFEFEFEFDFDFDFD12345678043F57FE07BB8005D422A2C5FD8AF0CF58\")\n    assert server_sent_packet_with({:open_connection_reply_2, :connection_negotiation}), \"Failed to respond to open connection request 2\"\n\n    # :data_packet_4[:client_connect] -> :data_packet_4[:server_handshake]\n    client_send.(\"840001703F0000004000900000000922A2C5FD8AF0CF58000000000000005D00\")\n\n    assert server_sent_packets_with(2, [{:server_handshake, :reliable_ordered}, {:ack, [0]}], true),\n           \"Failed to respond to client_connect with handshake and/or ack client ordered packet 0\"\n\n    # Have the client ack the handshake\n    client_send.(\"C000000020000101000000\")\n    # :data_packet_4[client_handshake] requires a pong response from its ping\n    client_send.(\n      \"84000216EE0100006019E00100000000000013043F57FE07BB80061C1EBF6800000000FE80000000000000C2A53EFFFE188D771F000000061C1EBF6800000000FE8000000000000018AB2FDACB39D7F202000000061C1EBF6800000000FE80000000000000CBAD3755438672141C000000061C1EBF68000000002607FC20E12A44EBE8F61BF2372CAB6500000000061C1EBF6800000000FE80000000000000DD135BB99B71EB3216000000061C1EBF68000000002607FC20E12A44EB10A1ABB78F934D9300000000061C1EBF6800000000FE800000000000004FD46183DE0250B315000000061C1EBF68000000002607FC20E12A44EBD143E3D178F8727800000000061C1EBF68000000002607FC20E12A44EBE497120B5A43099E00000000061C1EBF68000000002607FB901787903B319202F400D6266300000000061C1EBF6800000000FE8000000000000000381638AF01F1F703000000061C1EBF6800000000FE80000000000000590E28A7A8EE2D3514000000061C1EBF6800000000FE80000000000000397893EEA9E580A113000000049B69AB37BF68061C1EBF68000000002607FB901787903B043F38E29EE8C16000000000061C1EBF68000000002607FB901787903B097329AED1ACD39B00000000061C1EBF6800000000FE8000000000000004A607634B35B90308000000061C1EBF68000000002605A601AD7804000CE83F74A5CB264500000000061C1EBF6800000000FE80000000000000C2A53EFFFE188D770C000000061C1EBF6800000000FE80000000000000C017C6FFFE53F8660B000000061C1EBF6800000000FE80000000000000040018EFABB6CE8A0A000000061C1EBF6800000000FD7465726D6E7573000DA8FCEB6EE7BF00000000061C1EBF6800000000FE800000000000001AAA9D708BA12BB30D000000061C1EBF6800000000FD7465726D6E7573000CA8FCEB6EE7BF00000000061C1EBF68000000002607FB901787903B88E0C05F76E6D09900000000061C1EBF68000000002607FB901787903B802F37306E7ABD4200000000061C1EBF68000000002605A601AD780400A4F5EA8182BC7797000000000456018792BF68043F57FE35BF68061C1EBF6800000000FD7465726D6E7573000CA8FCEB6EE7BF000000000000000000000000000000000000007000004800000000000000007D\"\n    )\n\n    # :data_packet_4[ping, pong for 0x25, pong for 0x2f]\n    # \"8401000000004800000000000000402d000088030000000000000025000000000000402d00008803000000000000002f000000000000402d\",\n    assert server_sent_packets_with(\n             2,\n             [\n               {:ack, [1]},\n               {:ping, :unreliable},\n               {:pong, :unreliable},\n               {:pong, :unreliable}\n             ],\n             has_embedded_timestamp\n           ),\n           \"Failed to handle client handshake and additional enqueued pong\"\n  end\n\n  @tag slow: true\n  test \"resends unacknowledged packets\" do\n    # ------------------------------------------------------------------------------------------------------------------\n    # BEGIN COPYPASTA\n    # Everything up to the Process.sleep() is copypasta from the connection negotiation test!\n    # ------------------------------------------------------------------------------------------------------------------\n    {_server_pid, server_host_and_port} = start_server(49_103)\n    {client_send, client_send_padded} = make_client_send_fns(49_102, server_host_and_port)\n\n    # :open_connection_request_1 -> :open_connection_reply_1\n    open_conn_req_1_with_retries(client_send_padded, fn -> server_sent_packet_with({:open_connection_reply_1, :connection_negotiation}) end)\n\n    # :open_connection_request_2 -> :open_connection_reply_2\n    client_send.(\"0700ffff00fefefefefdfdfdfd123456780480fffffebfcf05d400059bb99c3c475c\")\n    assert server_sent_packet_with({:open_connection_reply_2, :connection_negotiation}), \"Failed to respond to open connection request 2\"\n\n    # :data_packet_4[:client_connect] -> :data_packet_4[:server_handshake]\n    client_send.(\"840000004001080000000900059bb98e0d2f4a00000000000000160052756d70656c7374696c74736b696e\")\n\n    assert server_sent_packets_with(2, [{:server_handshake, :reliable_ordered}, {:ack, [0]}]),\n           \"Failed to respond to client_connect with handshake and/or ack client ordered packet 0\"\n\n    # ------------------------------------------------------------------------------------------------------------------\n    # END COPYPASTA\n    # ------------------------------------------------------------------------------------------------------------------\n\n    # Sleep while the client deliberately does *not* send an ack for the :server_handshake packet\n    Process.sleep(1100)\n    assert server_sent_packet_with({:server_handshake, :reliable_ordered}), \"Failed to resend reliable packet :server_handshake\"\n\n    Process.sleep(1100)\n    assert server_sent_packet_with({:server_handshake, :reliable_ordered}), \"Failed to resend :server_handshake a second time\"\n\n    # Ack the handshake (packet index 2, not 0, since reliable packet 0 got resent twice)\n    # TODO: Should we be keeping track of the packet's *old* index and allowing ack based on that? (0 in this case)\n    client_send.(\"c0000101020000\")\n\n    receive do\n      {:protocol_msg, msg_raw} -> raise RuntimeError, message: \"Server should not have sent any more messages; sent #{inspect(msg_raw)}\"\n    after\n      1_100 -> :ok\n    end\n  end\n\n  @tag slow: true\n  test \"connections time out after not hearing from the client\" do\n    timeout_ms = 500\n    {_server_pid, server_host_and_port} = start_server(49_105, timeout_ms)\n    {_client_send, client_send_padded} = make_client_send_fns(49_104, server_host_and_port)\n\n    # :open_connection_request_1 -> :open_connection_reply_1\n    open_conn_req_1_with_retries(client_send_padded, fn -> server_sent_packet_with({:open_connection_reply_1, :connection_negotiation}) end)\n\n    :timer.sleep(timeout_ms + 100)\n    matching_pids = Registry.lookup(RakNet.Connection, {@localhost, 49_104})\n\n    if not Enum.empty?(matching_pids) do\n      [{_, pid} | _] = matching_pids\n\n      if Process.alive?(pid) do\n        # Flush all messages on the process\n        :sys.get_state(pid)\n      end\n\n      assert not Process.alive?(pid)\n    end\n  end\n\n  defmodule AckRequestingClient do\n    @enforce_keys [:handle_data, :handle_ack, :connection_pid]\n    defstruct handle_data: nil, handle_ack: nil, connection_pid: nil\n\n    def new(connection_pid, test_pid) do\n      :timer.apply_interval(100, AckRequestingClient, :bug_the_user, [connection_pid])\n\n      %AckRequestingClient{\n        connection_pid: connection_pid,\n        handle_data: fn _packet_type, data -> send(test_pid, {:business_logic_msg, data}) end,\n        handle_ack: fn send_receipt_id -> send(test_pid, {:business_logic_ack, send_receipt_id}) end\n      }\n    end\n\n    def bug_the_user(connection_pid) do\n      RakNet.Connection.send(connection_pid, :reliable_ack_receipt, \"please acknowledge\")\n    end\n  end\n\n  defimpl RakNet.Client, for: AckRequestingClient do\n    def new(_client_struct, connection_pid, test_pid), do: AckRequestingClient.new(connection_pid, test_pid)\n    def receive(client, packet_type, packet_buffer, _time_comp), do: client.handle_data.(packet_type, packet_buffer)\n    def got_ack(client, send_receipt_id), do: client.handle_ack.(send_receipt_id)\n    def disconnect(_client), do: :ok\n  end\n\n  test \"forwards acks to game logic client\" do\n    # ------------------------------------------------------------------------------------------------------------------\n    # BEGIN COPYPASTA\n    # Everything up to the Process.sleep() is copypasta from the connection negotiation test!\n    # ------------------------------------------------------------------------------------------------------------------\n\n    {_server_pid, server_host_and_port} = start_server(49_201, 1_000, AckRequestingClient)\n\n    {client_send, client_send_padded} = make_client_send_fns(49_200, server_host_and_port)\n\n    # :open_connection_request_1 -> :open_connection_reply_1\n    open_conn_req_1_with_retries(client_send_padded, fn -> server_sent_packet_with({:open_connection_reply_1, :connection_negotiation}) end)\n\n    # :open_connection_request_2 -> :open_connection_reply_2\n    client_send.(\"0700ffff00fefefefefdfdfdfd123456780480fffffebfcd05d400059bb99c3c475c\")\n    assert server_sent_packet_with({:open_connection_reply_2, :connection_negotiation}), \"Failed to respond to open connection request 2\"\n\n    # :data_packet_4[:client_connect] -> :data_packet_4[:server_handshake]\n    client_send.(\"840000004001080000000900059bb98e0d2f4a00000000000000160052756d70656c7374696c74736b696e\")\n\n    assert server_sent_packets_with(2, [{:server_handshake, :reliable_ordered}, {:ack, [0]}]),\n           \"Failed to respond to client_connect with handshake and/or ack client ordered packet 0\"\n\n    # Have the client ack the handshake\n    client_send.(\"c0000101000000\")\n    # :data_packet_4[client_handshake] requires a pong response from its ping\n    client_send.(\n      \"840100006002f001000000000000130480fffffebfcd043f57fe32bfcc04ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000000000000000040160000000000000025000048000000000000000025\"\n    )\n\n    # :data_packet_4[ping]\n    client_send.(\"8402000000004800000000000000002f\")\n\n    # :data_packet_4[ping, pong for 0x25, pong for 0x2f]\n    assert server_sent_packets_with(2, [\n             {:ack, [1, 2]},\n             {:ping, :unreliable},\n             {:pong, :unreliable},\n             {:pong, :unreliable},\n             {:pong, :unreliable}\n           ]),\n           \"Failed to handle client handshake and additional enqueued pong\"\n\n    # Ack the data packet with the ping and 2 pongs\n    client_send.(\"c0000101010000\")\n    # 2 pongs to respond to our pings\n    client_send.(\"84030000000088030000000000003ca4000000000000002c000088030000000000003ca4000000000000002c\")\n\n    assert server_responded(\"c0000101030000\"), \"Failed to respond to ack client's packet 3\"\n\n    # ------------------------------------------------------------------------------------------------------------------\n    # END COPYPASTA\n    # ------------------------------------------------------------------------------------------------------------------\n\n    # Have the server send a value that requests an ack\n    assert server_sent_packet_with({\"please acknowledge\", :reliable_ack_receipt})\n    client_send.(\"c0000101020000\")\n    assert receive_business_logic_ack()\n  end\n\n  @tag ipv6: true\n  test \"handles IPv6 connections\" do\n    {_server_pid, _server_host_and_port} = start_server(49_211, 1_000, AckRequestingClient)\n    {client_send, client_send_padded} = make_client_send_fns(49_210, {@localhost6, 49_211}, 6)\n\n    # :open_connection_request_1 -> :open_connection_reply_1\n    open_conn_req_1_with_retries(client_send_padded, fn -> server_sent_packet_with({:open_connection_reply_1, :connection_negotiation}) end)\n\n    # :open_connection_request_2 -> :open_connection_reply_2\n    client_send.(\"0700ffff00fefefefefdfdfdfd123456780480fffffebfcd05d400059bb99c3c475c\")\n    assert server_sent_packet_with({:open_connection_reply_2, :connection_negotiation}), \"Failed to respond to open connection request 2\"\n\n    # :data_packet_4[:client_connect] -> :data_packet_4[:server_handshake]\n    client_send.(\"840000004001080000000900059bb98e0d2f4a00000000000000160052756d70656c7374696c74736b696e\")\n\n    assert server_sent_packets_with(2, [{:server_handshake, :reliable_ordered}, {:ack, [0]}]),\n           \"Failed to respond to client_connect with handshake and/or ack client ordered packet 0\"\n\n    # Have the client ack the handshake\n    client_send.(\"c0000101000000\")\n    # :data_packet_4[client_handshake] requires a pong response from its ping\n    client_send.(\n      \"840100006002f001000000000000130480fffffebfcd043f57fe32bfcc04ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000004ffffffff000000000000000040160000000000000025000048000000000000000025\"\n    )\n\n    # :data_packet_4[ping]\n    client_send.(\"8402000000004800000000000000002f\")\n\n    # :data_packet_4[ping, pong for 0x25, pong for 0x2f]\n    assert server_sent_packets_with(2, [\n             {:ack, [1, 2]},\n             {:ping, :unreliable},\n             {:pong, :unreliable},\n             {:pong, :unreliable},\n             {:pong, :unreliable}\n           ]),\n           \"Failed to handle client handshake and additional enqueued pong\"\n\n    # Ack the data packet with the ping and 2 pongs\n    client_send.(\"c0000101010000\")\n    # 2 pongs to respond to our pings\n    client_send.(\"84030000000088030000000000003ca4000000000000002c000088030000000000003ca4000000000000002c\")\n\n    assert server_responded(\"c0000101030000\"), \"Failed to respond to ack client's packet 3\"\n\n    # Have the server send a value that requests an ack\n    assert server_sent_packet_with({\"please acknowledge\", :reliable_ack_receipt})\n    client_send.(\"c0000101020000\")\n    assert receive_business_logic_ack()\n  end\n\n  test \"server complains if you send data packets without having negotiated a connection\" do\n    {_server_pid, server_host_and_port} = start_server(49_107)\n    {client_send, client_send_padded} = make_client_send_fns(49_106, server_host_and_port)\n\n    # Attempt to ack some non-existent packet\n    client_send.(\"c0000101010000\")\n\n    assert server_sent_packet_with({:connection_lost, :connection_negotiation}), \"Server should nag to establish a connection before ack\"\n\n    # Send a data packet out of the blue\n    client_send.(\"84030000000088030000000000003ca4000000000000002c000088030000000000003ca4000000000000002c\")\n    assert server_sent_packet_with({:connection_lost, :connection_negotiation}), \"Server should nag connect before sending data\"\n\n    # Start opening a connection, but then send a data packet before completing the connection negotiation\n    open_conn_req_1_with_retries(client_send_padded, fn -> server_sent_packet_with({:open_connection_reply_1, :connection_negotiation}) end)\n    client_send.(\"841a00006000280800000200000061686f7900\")\n    assert server_sent_packet_with({:connection_lost, :connection_negotiation}), \"Server should nag to finish connection negotiation\"\n  end\n\n  test \"server survives connection crash\" do\n    {server_pid, server_host_and_port} = start_server(49_900)\n\n    # Start a bunch of connections\n    [{live_client_send, port_to_keep_alive} | send_fns_and_ports_to_kill] =\n      Enum.map(1..10, fn i ->\n        port = 49_900 + i\n        {client_send, client_send_padded} = make_client_send_fns(port, server_host_and_port)\n        # :open_connection_request_1 -> :open_connection_reply_1\n        open_conn_req_1_with_retries(client_send_padded, fn ->\n          server_sent_packet_with({:open_connection_reply_1, :connection_negotiation})\n        end)\n\n        {client_send, port}\n      end)\n\n    ports_to_kill = Enum.map(send_fns_and_ports_to_kill, fn {_, port} -> port end)\n\n    # Kill all but one\n    Enum.each(ports_to_kill, fn port ->\n      [{_, pid}] = Registry.lookup(RakNet.Connection, {@localhost, port})\n      Process.exit(pid, :kill)\n      assert not Process.alive?(pid), \"Failed to kill process for port #{port}\"\n    end)\n\n    assert Process.alive?(server_pid), \"Server died when we killed its connections\"\n\n    [{_, live_pid}] = Registry.lookup(RakNet.Connection, {@localhost, port_to_keep_alive})\n    :sys.get_state(live_pid)\n    assert Process.alive?(live_pid), \"PID died unexpectedly\"\n\n    # :open_connection_request_2 -> :open_connection_reply_2\n    live_client_send.(\"0700ffff00fefefefefdfdfdfd123456780480fffffebfcd05d400059bb99c3c475c\")\n    assert server_sent_packet_with({:open_connection_reply_2, :connection_negotiation}), \"Failed to respond to open connection request 2\"\n    assert Process.alive?(server_pid), \"Server died when we killed its connections\"\n  end\n\n  defmodule DummyClient do\n    defstruct handle_data: nil\n\n    def accept_data(client, packet_type, buffer) do\n      client.handle_data.(packet_type, buffer)\n      client\n    end\n  end\n\n  defimpl RakNet.Client, for: DummyClient do\n    def new(_, _, test_pid), do: %DummyClient{handle_data: fn _type, data -> send(test_pid, {:business_logic_msg, data}) end}\n    def receive(client, packet_type, packet_buffer, _time_comp), do: DummyClient.accept_data(client, packet_type, packet_buffer)\n    def got_ack(client, _send_receipt_id), do: client\n    def disconnect(_client), do: :ok\n  end\n\n  defp start_server(port, timeout_ms \\\\ 10_000, client_module \\\\ nil) do\n    test_pid = self()\n\n    {:ok, server_pid} =\n      RakNet.Server.start_link(if(client_module, do: client_module, else: DummyClient), port,\n        client_timeout_ms: timeout_ms,\n        client_data: test_pid,\n        server_identifier: <<@fixed_id::size(64)>>,\n        host: @localhost,\n        open_ipv6_socket: System.get_env(\"SEPARATE_IPV6_PORT\") != \"false\",\n        send: fn _, packet, _client ->\n          send(test_pid, {:protocol_msg, packet})\n        end\n      )\n\n    {server_pid, {@localhost, port}}\n  end\n\n  defp start_xplane_server(port) do\n    test_pid = self()\n\n    {:ok, server_pid} =\n      RakNet.Server.start_link(DummyClient, port,\n        client_data: test_pid,\n        server_identifier: <<@fixed_id::size(64)>>,\n        host: @localhost,\n        send: fn _, packet, _client -> send(test_pid, {:protocol_msg, packet}) end,\n        # Begin X-Plane-specific #defines!\n        include_timestamp_with_datagrams: true,\n        max_number_of_internal_ids: 30\n      )\n\n    {server_pid, {@localhost, port}}\n  end\n\n  defp make_client_send_fns(client_port, server_host_and_port, ip_version \\\\ 4) do\n    socket =\n      if ip_version == 4 do\n        Socket.UDP.open!(client_port, local: [address: @localhost], version: 4)\n      else\n        Socket.UDP.open!(client_port, local: [address: @localhost6], version: 6)\n      end\n\n    client_send_padded = fn packet_string, pad_to_byte_length ->\n      data = packet_decode(packet_string)\n      pad_length = max(pad_to_byte_length * 8 - bit_size(data), 0)\n      Socket.Datagram.send!(socket, data <> <<0::size(pad_length)>>, server_host_and_port)\n    end\n\n    client_send = fn packet_string -> client_send_padded.(packet_string, 0) end\n    {client_send, client_send_padded}\n  end\n\n  defp packet_decode(packet_hex_string, pad_to_byte_length \\\\ 0) do\n    data = Base.decode16!(packet_hex_string, case: :mixed)\n    pad_length = max(pad_to_byte_length * 8 - bit_size(data), 0)\n    data <> <<0::size(pad_length)>>\n  end\n\n  defp server_responded(packet_hex_string, opts \\\\ []) when is_binary(packet_hex_string) do\n    server_responded_many([{packet_hex_string, opts}])\n  end\n\n  defp server_sent_packet_with(packet_spec, has_embedded_timestamp \\\\ false) do\n    server_sent_packets_with(1, [packet_spec], has_embedded_timestamp)\n  end\n\n  defp server_sent_packets_with(num_packets, packet_spec, has_embedded_timestamp \\\\ false) when is_list(packet_spec) do\n    actual_types_and_reliabilities =\n      num_packets\n      |> receive_n_sends()\n      |> Enum.flat_map(&parse_packet_type(&1, has_embedded_timestamp))\n\n    if Enum.sort(packet_spec) == Enum.sort(actual_types_and_reliabilities) do\n      true\n    else\n      Logger.error(\"Expected packets: #{inspect(packet_spec, limit: :infinity)}\")\n      Logger.error(\"Received packets: #{inspect(actual_types_and_reliabilities, limit: :infinity)}\")\n      false\n    end\n  end\n\n  # credo:disable-for-next-line\n  defp parse_packet_type(<<type::size(8), remainder::binary>>, has_embedded_timestamp) do\n    ack = RakNet.Message.binary(:ack)\n\n    decode_fn = if has_embedded_timestamp, do: &decode_with_timestamp/1, else: &decode_no_timestamp/1\n    ack_timestamp_bits = if has_embedded_timestamp, do: 32, else: 0\n\n    case type do\n      ^ack ->\n        [{:ack, RakNet.Connection.message_indices_from_ack(drop_leading_bits(remainder, ack_timestamp_bits))}]\n\n      x when x in 0x80..0x8F ->\n        Enum.map(decode_fn.(remainder)[:encapsulated_packets], fn packet ->\n          <<encapsulated_type::size(8), _::binary>> = packet.buffer\n\n          case RakNet.Message.name(encapsulated_type) do\n            :error -> {packet.buffer, packet.reliability}\n            name -> {name, packet.reliability}\n          end\n        end)\n\n      x when x in 0x05..0x16 ->\n        [{RakNet.Message.name(type), :connection_negotiation}]\n    end\n  end\n\n  defp server_responded_many(packet_specs) when is_list(packet_specs) do\n    packets_and_xforms =\n      Enum.map(packet_specs, fn {packet_hex_string, opts} ->\n        # Replace loopback adapter's IP+port combo (which is what happens when you run the RakNet samples in the terminal)\n        # with \"plain-old localhost\" (which is what we get when we run the ExUnit test)\n        normalize_ips =\n          if Keyword.get(opts, :normalize_ip_addresses, false) do\n            fn bits ->\n              bits\n              |> String.replace(<<128, 255, 255, 254, 191, 204>>, <<127, 0, 0, 1, 191, 204>>)\n              |> String.replace(<<128, 255, 255, 254, 191, 206>>, <<127, 0, 0, 1, 191, 206>>)\n              |> String.replace(<<63, 87, 254, 50, 191, 205>>, <<127, 0, 0, 1, 191, 205>>)\n              |> String.replace(<<63, 87, 254, 50, 191, 207>>, <<127, 0, 0, 1, 191, 207>>)\n            end\n          else\n            & &1\n          end\n\n        drop_trailing = fn bits ->\n          new_length_bytes = byte_size(bits) - Keyword.get(opts, :discard_last_bytes, 0)\n          if new_length_bytes > 2, do: <<bits::binary-size(new_length_bytes)>>, else: bits\n        end\n\n        drop_bits = Keyword.get(opts, :discard_first_bytes, 0) * 8\n        drop_leading = &drop_leading_bits(&1, drop_bits)\n\n        transform = fn bits -> bits |> drop_leading.() |> drop_trailing.() |> normalize_ips.() end\n        expected_raw = packet_decode(packet_hex_string, Keyword.get(opts, :pad_to_byte_length, 0))\n        {expected_raw, transform.(expected_raw), transform}\n      end)\n\n    reduce_remove_nil = fn x, acc -> if x, do: [x | acc], else: acc end\n\n    msgs_raw = receive_n_sends(length(packets_and_xforms))\n\n    missing_expected =\n      packets_and_xforms\n      |> Enum.map(fn {raw, expected, transform} ->\n        has_match = Enum.any?(msgs_raw, fn msg_raw -> transform.(msg_raw) == expected end)\n        if has_match, do: nil, else: raw\n      end)\n      |> Enum.reduce([], reduce_remove_nil)\n\n    if length(missing_expected) > 0 do\n      Logger.error(\"Failed to receive expected message(s):\")\n      Enum.each(missing_expected, fn msg -> Logger.error(\"#{inspect(msg, limit: :infinity)}\") end)\n    end\n\n    unexpected_msgs =\n      msgs_raw\n      |> Enum.map(fn msg_raw ->\n        matched = Enum.any?(packets_and_xforms, fn {_raw, expected, transform} -> transform.(msg_raw) == expected end)\n        if matched, do: nil, else: msg_raw\n      end)\n      |> Enum.reduce([], reduce_remove_nil)\n\n    if length(unexpected_msgs) > 0 do\n      Logger.error(\"Received unexpected message(s):\")\n      Enum.each(unexpected_msgs, fn msg -> Logger.error(\"#{inspect(msg, limit: :infinity)}\") end)\n    end\n\n    Enum.empty?(unexpected_msgs) and Enum.empty?(missing_expected)\n  end\n\n  defp drop_leading_bits(data, num_bits) do\n    <<_::size(num_bits), remainder::binary>> = data\n    remainder\n  end\n\n  # With enough tests going at once, we can overwhelm the network interface---it's okay if we have to try this again\n  defp open_conn_req_1_with_retries(client_send_padded, success_check, max_attempts \\\\ 10, attempt \\\\ 1)\n\n  defp open_conn_req_1_with_retries(client_send_padded, success_check, max_attempts, attempt) when max_attempts > attempt do\n    client_send_padded.(\"0500ffff00fefefefefdfdfdfd1234567806\", 1464)\n    assert success_check.()\n  rescue\n    _ ->\n      Process.sleep(250 + :rand.uniform(1250))\n      open_conn_req_1_with_retries(client_send_padded, success_check, max_attempts, attempt + 1)\n  end\n\n  defp open_conn_req_1_with_retries(_client_send_padded, _success_check, _max_attempts, _attempt) do\n    raise RuntimeError, message: \"Failed to receive open_connection_reply_1 from our open_connection_request_1\"\n  end\n\n  def receive_n_sends(n) when is_integer(n) do\n    Enum.map(1..n, fn _ ->\n      receive do\n        {:protocol_msg, msg_raw} -> msg_raw\n      after\n        1_000 ->\n          raise RuntimeError, message: \"Didn't receive enough messages\"\n      end\n    end)\n  end\n\n  def receive_business_logic_msg do\n    receive do\n      {:business_logic_msg, msg_raw} -> msg_raw\n    after\n      5_000 ->\n        raise RuntimeError, message: \"Didn't receive a business logic message\"\n    end\n  end\n\n  def receive_business_logic_ack do\n    receive do\n      {:business_logic_ack, msg_raw} -> msg_raw\n    after\n      5_000 ->\n        raise RuntimeError, message: \"Didn't receive a business logic ack\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/system_address_test.exs",
    "content": "defmodule RakNet.SystemAddressTest do\n  use ExUnit.Case, async: true\n  import Assertions, only: [assert_lists_equal: 2]\n\n  test \"decodes IPv4 addresses\" do\n    assert_lists_equal(RakNet.SystemAddress.decode_many(<<4, 63, 87, 254, 7, 187, 128, 4, 63, 87, 254, 53, 191, 104>>), [\n      %{version: 4, address: {63, 87, 254, 7}, port: 48_000},\n      %{version: 4, address: {63, 87, 254, 53}, port: 49_000}\n    ])\n  end\n\n  test \"decodes IPv6 addresses\" do\n    #         v6 size fam   port    flow info  address-------------------------------------------------------  scope\n    ipv6_1 = <<6, 28, 30, 191, 104, 0, 0, 0, 0, 254, 128, 0, 0, 0, 0, 0, 0, 4, 0, 24, 239, 171, 182, 206, 138, 10, 0, 0, 0>>\n    ipv6_2 = <<6, 28, 30, 191, 105, 0, 0, 0, 0, 38, 7, 251, 144, 23, 135, 144, 59, 213, 146, 204, 243, 194, 122, 129, 248, 0, 0, 0, 0>>\n\n    assert_lists_equal(RakNet.SystemAddress.decode_many(ipv6_1 <> ipv6_2), [\n      %{version: 6, address: {65_152, 0, 0, 0, 1024, 6383, 43_958, 52_874}, port: 49_000},\n      %{version: 6, address: {9735, 64_400, 6023, 36_923, 54_674, 52_467, 49_786, 33_272}, port: 49_001}\n    ])\n  end\n\n  test \"decodes a mix of IPv4 and IPv6 addresses\" do\n    ipv4_1 = <<4, 63, 87, 254, 7, 187, 128>>\n    ipv4_2 = <<4, 63, 87, 254, 53, 191, 104>>\n    ipv6_1 = <<6, 28, 30, 191, 104, 0, 0, 0, 0, 254, 128, 0, 0, 0, 0, 0, 0, 4, 0, 24, 239, 171, 182, 206, 138, 10, 0, 0, 0>>\n    ipv6_2 = <<6, 28, 30, 191, 105, 0, 0, 0, 0, 38, 7, 251, 144, 23, 135, 144, 59, 213, 146, 204, 243, 194, 122, 129, 248, 0, 0, 0, 0>>\n\n    assert_lists_equal(RakNet.SystemAddress.decode_many(ipv6_1 <> ipv4_1 <> ipv6_2 <> ipv4_2), [\n      %{version: 4, address: {63, 87, 254, 7}, port: 48_000},\n      %{version: 4, address: {63, 87, 254, 53}, port: 49_000},\n      %{version: 6, address: {65_152, 0, 0, 0, 1024, 6383, 43_958, 52_874}, port: 49_000},\n      %{version: 6, address: {9735, 64_400, 6023, 36_923, 54_674, 52_467, 49_786, 33_272}, port: 49_001}\n    ])\n\n    assert_lists_equal(\n      RakNet.SystemAddress.decode_many(ipv6_1 <> ipv4_1 <> ipv6_2 <> ipv4_2),\n      RakNet.SystemAddress.decode_many(ipv4_1 <> ipv6_1 <> ipv4_2 <> ipv6_2)\n    )\n\n    assert_lists_equal(\n      RakNet.SystemAddress.decode_many(ipv6_1 <> ipv4_1 <> ipv6_2 <> ipv4_2),\n      RakNet.SystemAddress.decode_many(ipv6_2 <> ipv6_1 <> ipv4_2 <> ipv4_1)\n    )\n  end\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "ExUnit.start()\n"
  }
]