serverbound.clj

Branches > main > e95c513 > files / src / net / bowmap / network / protocol / message / serverbound.clj

(ns net.bowmap.network.protocol.message.serverbound
  "Schemas for messages sent from the client to the server."
  (:require
   [clojure.spec.alpha :as s]
   [dev.grahp.build :refer [build]]
   [dev.grahp.error :refer [adorn raise]]
   [net.bowmap.network.protocol.message :as msg]
   [net.bowmap.network.protocol.frame :as frame]
   [net.bowmap.network.protocol.readers
    :refer [->bit-mask ->byte ->double ->enum ->float ->identifier ->int
            ->list ->long ->optional ->position ->short ->string ->ubyte
            ->uuid ->varint coerce]]
   [net.bowmap.network.protocol.state :as state])
  (:import
   [io.netty.buffer ByteBuf]))

; TODO docstring, rename?
(defn build-message-readers [registry]
  (reduce (fn [acc [numeric-id table]]
            (assoc acc numeric-id
                   (let [reader (coerce table)]
                     (fn [buf]
                       (assoc (reader buf)
                              ::msg/id numeric-id)))))
          {} registry))

; TODO docstring, rename?
; TODO spec for sure
(defn build-message-id-map [registry]
  (build [[state numeric-ids] registry
          [i id] (map-indexed vector numeric-ids)]
    [{::state/connection-state state
      ::msg/numeric-id i}
     id]))

; TODO document why ids are enumerated like this?
(def ^{:spec (s/map-of (s/keys :req [::state/connection-state ::msg/numeric-id]) ::msg/id)}
  numeric-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 idfc [])
(defn TODO [buf] (raise "Not shrimplemented yet!"))

(def ^{:spec (s/map-of ::msg/id (s/fspec :args (s/cat :buf #(instance? ByteBuf %))
                                         :ret ::msg/message))}
  messages
  (build-message-readers
    ; Handshaking
    {::handshaking [:protocol ->varint
                    :address ->string
                    :port ->short
                    :intent (->enum ->varint {1 ::state/status 2 ::state/login 3 ::state/transfer})]

     ; Status
     ::status-request []
     ::ping-request [:timestamp ->long]

     ; Login
     ::login-start [:username ->string
                    :uuid ->uuid]
     ::encryption-response [:shared-secret (->list ->byte)]
     ::login-plugin-response [:message ->varint
                              :data (fn [buf] (raise "Fuck you"))]
     ::login-acknowledged []
     ::cookie-response [:key ->identifier
                        :payload (->optional (->list ->byte))]

     ; Config

     ::client-information idfc
     ::plugin-message idfc
     ::finish-config []
     ::pong [:id ->int]
     ::resource-pack-response [:uuid ->uuid
                               :result (->enum ->varint {0 :sucessfully-downloaded
                                                         1 :declined
                                                         2 :failed-to-download
                                                         3 :accepted
                                                         4 :downloaded
                                                         5 :invalid-url
                                                         6 :failed-to-reload
                                                         7 :discarded})]
     ; The wiki is too damn confusing
     ::known-packs idfc
     ::custom-click-action TODO
     ::accept-code-of-conduct TODO

     ;; This message has more fields I just don't care about them :p
     ;; This message also has a bunch of fields that I probably do care about!

     ; Play

     ::confirm-teleportation [:teleport-id ->varint]
     ::query-block-entity-tag TODO
     ::bundle-item-selected TODO
     ::change-difficulty TODO
     ::change-game-mode TODO
     ::acknowledge-message TODO
     ::chat-command TODO
     ::signed-chat-command TODO
     ::chat-message TODO
     ::player-session TODO
     ::chunk-batch-received TODO
     ::client-status TODO
     ::client-tick-end []
     ::command-suggestions-request TODO
     ::acknowledge-configuration TODO
     ::click-container-button TODO
     ::click-container TODO
     ::close-container TODO
     ::change-container-slot-state TODO
     ::debug-subscription-request TODO
     ::edit-book TODO
     ::query-entity-tag TODO
     ::interact TODO
     ::jigsaw-generate TODO
     ::keep-alive [:id ->long]
     ::lock-difficulty TODO

     ::set-player-rotation [:yaw ->float
                            :pitch ->float
                            :flags ->byte]
     ::set-player-position-and-rotation [:x ->double
                                         :feet-y ->double
                                         :z ->double
                                         :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])]
     ::move-vehicle TODO
     ::paddle-boat TODO
     ::pick-item-from-block TODO
     ::pick-item-from-entity TODO
     ;; TODO is this different?
     #_[::ping-request TODO]
     ::place-recipe TODO
     ::player-abilities [:flags (->bit-mask ->byte {1 :flying?})] ; TODO is this right?
     ::player-action [:status (->enum ->varint [:started-digging :canceled-digging :finished-digging
                                                :drop-item-stack :drop-item :update-item-state :swap-item-in-hand])
                      ; TODO aren't positions and locations different things?
                      :location ->position
                      ; TODO block face might be something logical
                      :face (->enum ->byte [:bottom :top :north :south :west :east])
                      :sequence ->varint]
     ::player-command [:entity-id ->varint
                       :action-id (->enum ->varint [:leave-bed :start-sprinting :stop-sprinting :start-jumping-with-horse
                                                    :stop-jumping-with-horse :open-vehicle-inventory :start-flying-with-elytra])
                       :jump-boost ->varint]
     ::player-input [:flags (->bit-mask ->ubyte [:forward :backward :left :right :jump :sneak :sprint])]
     ::player-loaded []
     ::change-recipe-book-settings TODO
     ::set-seen-recipe TODO
     ::rename-item TODO
     ::seen-advancements TODO
     ::select-trade TODO
     ::set-beacon-effect TODO
     ::set-held-item TODO
     ::program-command-block TODO
     ::program-command-block-minecart TODO
     ::set-creative-mode-slot TODO
     ::program-jigsaw-block TODO
     ::program-structure-block TODO
     ::set-test-block TODO
     ::update-sign TODO
     ::swing-arm TODO
     ::teleport-to-entity TODO
     ::test-instance-block-action TODO
     ::use-item-on TODO
     ::use-item TODO}))

(assert (= (set (vals numeric-ids))
           (set (keys messages))))

(comment
  (require '[clojure.set :as set])
  (let [all-ids (set (vals numeric-ids))
        present-ids (set (keys messages))]
    (set/difference all-ids present-ids)))

(defn frame->message
  "Deserializes one frame into a message.
   The initial length field of the frame should be stripped from the buffer!
   Can throw if:
    1. No such frame with the given numeric id and state exists
    2. No schema exists for that message id
    3. 'buf' is not properly framed
    4. The contents of 'buf' are not a valid message"
  [frame state]
  (let [numeric-id (->varint frame)
        message-id (req! numeric-ids {::state/connection-state state ::msg/numeric-id numeric-id})
        reader (req! messages message-id)]
    (adorn (reader frame) "Error while calling message reader" {:numeric-id numeric-id :message-id message-id :reader reader})))

(s/fdef frame->message
        :args (s/cat :frame ::frame/frame :state ::state/connection-state)
        :ret ::msg/message)