server.clj
Branches > main > c6a1490 > 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 defn! get!]]
[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.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)
message-id (packet-ids {::state/connection-state state ::pk/id numeric-id})
reader (get! packets message-id "No such message with that id!" {:state state})]
(adorn (reader packet) "Error while calling packet reader" {:numeric-id numeric-id :packet-id message-id :reader reader})))
(s/fdef packet->message
:args (s/cat :packet ::pk/packet :state ::state/connection-state)
:ret ::msg/message)