serverbound.clj

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

(ns net.bowmap.network.protocol.message.serverbound
  (: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.packet :as pk]
   [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 [packet-id table]]
            (assoc acc packet-id
                   (let [reader (coerce 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
      ::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))}
  packets
  (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 packet has more fields I just don't care about them :p
     ;; This packet 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 packets))))

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

(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 (req! numeric-ids {::state/connection-state state ::msg/numeric-id numeric-id})
        reader (req! packets message-id)]
    (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)