server.clj
Branches > main > 854a1d3 > files / src / net / bowmap / network / message / server.clj
(ns net.bowmap.network.message.server
(:require
[clojure.alpha.spec :as s]
[dev.grahp.build :refer [build]]
[dev.grahp.error :refer [adorn raise]]
[net.bowmap.network.message :as msg]
[net.bowmap.network.packet :as pk]
[net.bowmap.network.packet.read :refer [->bit-mask ->byte ->double ->enum
->float ->long ->short ->string
->table ->ubyte ->uuid ->varint]]
[net.bowmap.network.packet.write :refer [byte->]]
[net.bowmap.network.state :as state])
(:import
[io.netty.buffer ByteBuf]))
; TODO docstring, rename?
(defn build-message-readers [registry]
(reduce (fn [acc [packet-id table]]
(assoc acc packet-id
(let [reader (->table table)]
(fn [buf]
(assoc (reader buf)
::msg/id packet-id)))))
{} registry))
; TODO docstring, rename?
; TODO spec for sure
(defn build-message-id-map [registry]
(build [[state packet-ids] registry
[i id] (map-indexed vector packet-ids)]
[{::state/connection-state state
::pk/id i}
id]))
; TODO document why ids are enumerated like this?
(def ^{:spec (s/map-of (s/schema [::state/connection-state ::pk/id]) ::msg/id)}
packet-ids
(build-message-id-map
{::state/handshaking
[::handshaking]
::state/status
[::status-request
::ping-request]
::state/login
[::login-start
::encryption-response
::login-plugin-response
::login-acknowledged
::cookie-response]
::state/config
[::client-information
::cookie-response
::plugin-message
::finish-config
::keep-alive
::pong
::resource-pack-response
::known-packs
::custom-click-action
::accept-code-of-conduct]
::state/play
[::confirm-teleportation
::query-block-entity-tag
::bundle-item-selected
::change-difficulty
::change-game-mode
::acknowledge-message
::chat-command
::signed-chat-command
::chat-message
::player-session
::chunk-batch-received
::client-status
::client-tick-end
::client-information
::command-suggestions-request
::acknowledge-configuration
::click-container-button
::click-container
::close-container
::change-container-slot-state
::cookie-response
::plugin-message
::debug-subscription-request
::edit-book
::query-entity-tag
::interact
::jigsaw-generate
::keep-alive
::lock-difficulty
::set-player-position
::set-player-position-and-rotation
::set-player-rotation
::set-player-movement-flags
::move-vehicle
::paddle-boat
::pick-item-from-block
::pick-item-from-entity
::ping-request
::place-recipe
::player-abilities
::player-action
::player-command
::player-input
::player-loaded
::pong
::change-recipe-book-settings
::set-seen-recipe
::rename-item
::resource-pack-response
::seen-advancements
::select-trade
::set-beacon-effect
::set-held-item
::program-command-block
::program-command-block-minecart
::set-creative-mode-slot
::program-jigsaw-block
::program-structure-block
::set-test-block
::update-sign
::swing-arm
::teleport-to-entity
::test-instance-block-action
::use-item-on
::use-item
::custom-click-action]}))
(def ^{:spec (s/map-of ::msg/id (s/fspec :args (s/cat :buf #(instance? ByteBuf %))
:ret ::msg/message))}
packets
(build-message-readers
{::handshaking [:protocol ->varint
:address ->string
:port ->short
:intent (->enum ->varint {1 ::state/status 2 ::state/login 3 ::state/transfer})]
::status-request []
::ping-request [:timestamp ->long]
::login-start [:username ->string
:uuid ->uuid]
::login-acknowledged []
;; This packet has more fields I just don't care about them :p
::plugin-message []
;; This packet also has a bunch of fields that I probably do care about!
::client-information []
::known-packs []
::finish-config []
::confirm-teleportation [:teleport-id ->varint]
::keep-alive [:id ->long]
::client-tick-end []
::player-loaded []
::player-input [:flags (->bit-mask ->ubyte [:forward :backward :left :right :jump :sneak :sprint])]
::set-player-rotation [:yaw ->float
:pitch ->float
:flags ->byte]
::set-player-position [:x ->double
:feet-y ->double
:z ->double
:flags ->byte]
::set-player-movement-flags [:flags (->bit-mask ->byte [:on-ground :pushing-against-wall])]
::player-command [:entity-id ->varint
:action-id (->enum ->varint {0 :leave-bed
1 :start-sprinting
2 :stop-sprinting
3 :start-jumping-with-horse
4 :stop-jumping-with-horse
5 :open-vehicle-inventory
6 :start-flying-with-elytra})
:jump-boost ->varint]
::player-abilities [:flags (->bit-mask byte-> {1 :flying?})] ; TODO is this right?
::set-player-position-and-rotation [:x ->double
:feet-y ->double
:z ->double
:yaw ->float
:pitch ->float
:flags ->byte]}))
(defn packet->message
"Deserializes one (framed, serialized) packet into a message.
The initial length field of the packet should be stripped from the buffer.
Can throw if:
1. No such packet with the given numeric id and state exists
2. No schema exists for that packet id
3. 'buf' is not properly framed
4. The contents of 'buf' are not a valid packet"
[packet state]
(-> (let [numeric-id (->varint packet)
packet-id (packet-ids {::state/connection-state state ::pk/id numeric-id})
reader (packets packet-id)]
(if reader
(adorn (reader packet)
"Error while calling packet reader" {:numeric-id numeric-id :packet-id packet-id :reader reader})
(raise "Failed to read message, no such message with that id." {:id numeric-id :state state :packet-id packet-id})))
(adorn "Error while turning serverbound packet into message" {:buf packet :state state})))
(s/fdef packet->message
:args (s/cat :packet ::pk/packet :state ::state/connection-state)
:ret ::msg/message)