server.clj

Branches > main > a408573 > 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]]]}))

#_"Decodes and returns one packet from ByteBuf 'buf', which should contain at least one readable packet.
   The initial length field of the packet should not be stripped from 'buf'.
   After calling, the buf will contain one less packet.
   Can throw if:
    1. No such packet with the packet id and state exists
    2. 'buf' is not properly framed, and does not contain a full packet
    3. The contents of 'buf' are not a valid packet"

(defn decode-packet
  ;; Pre-framed
  [buf state]
  (let [numeric-id (read/read-varint! buf)
        packet-id (packet-ids {::state/state state ::pk/numeric-id numeric-id})
        dedec (packets packet-id)]
    (if dedec
      (dedec buf)
      (raise "Failed to decode packet, no such packet with that id." {:id numeric-id :state state :packet-id packet-id}))))