server.clj

Branches > main > 0190c10 > files / src / net / bowmap / packet / server.clj

(ns net.bowmap.packet.server
  (:require
   [clojure.alpha.spec :as s]
   [dev.grahp.build :refer [build]]
   [dev.grahp.error :refer [raise]]
   [net.bowmap.client.state :as state]
   [net.bowmap.packet :as pk]
   [net.bowmap.packet.read :as read])
  (:import
   [io.netty.buffer ByteBuf]))

(defn build-packets [registry]
  (reduce (fn [acc [packet-id table]]
            (assoc acc packet-id
                   (let [reader (read/reader [:table table])]
                     (fn [buf]
                       (assoc (reader buf)
                              ::pk/id packet-id)))))
          {} registry))

(defn build-packet-ids [registry]
  (build [[state packet-ids] registry
          [i id] (map-indexed vector packet-ids)]
    [{::state/state state
      ::pk/numeric-id i}
     id]))

(def ^{:spec (s/map-of (s/schema [::state/state ::pk/numeric-id]) ::pk/id)}
  packet-ids
  (build-packet-ids
    {::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 ::pk/id (s/fspec :args (s/cat :buf #(instance? ByteBuf %))
                                        :ret ::pk/packet))}
  packets
  (build-packets
    {::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 []
     ::keep-alive [:id :long]
     ::client-tick-end []
     ::player-loaded []
     ::player-input [:flags [:bit-mask :ubyte [:forward :backward :left :right :jump :sneak :sprint]]]}))

(defn decode-packet
  "Decodes/deserializes one packet (::net.bowmap.packet) from framed ByteBuf 'buf'.
   The initial length field of the packet should be stripped from the buffer.
   Can throw if:
    1. No such packet with the 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-buf state]
  (let [numeric-id (read/read-varint! packet-buf)
        packet-id (packet-ids {::state/state state ::pk/numeric-id numeric-id})
        dedec (packets packet-id)]
    (if dedec
      (dedec packet-buf)
      (raise "Failed to decode packet, no such packet with that id." {:id numeric-id :state state :packet-id packet-id}))))