client.clj

Branches > client-handling-rewrite > 9180d22 > files / src / net / bowmap / packet / client.clj

(ns net.bowmap.packet.client
  "Clientbound packets"
  (:require
   [clojure.alpha.spec :as s]
   [dev.grahp.build :refer [build]]
   [dev.grahp.error :refer [assert!]]
   [net.bowmap.client.state :as state]
   [net.bowmap.packet :as pk]
   [net.bowmap.packet.write :as write])
  (:import
   [io.netty.buffer ByteBuf Unpooled]))

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

(defn build-packets [registry]
  (reduce (fn [acc [packet-id table]]
            (assoc acc packet-id (write/writer [:table table])))
          {} registry))

(def ^{:spec (s/map-of (s/schema [::state/state ::pk/id]) ::pk/numeric-id)}
  packet-ids
  "Map of [::state/state ::pk/id] to numeric-id.
   Packet ids (::pk/id) could map directly to numeric-ids and be stateless,
    but some packets have duplicates across connection states that you'd want to handle in the same way.
   If it turns out that I don't end up handling packets in this way, then packet ids could map directly to numeric ids."
  (build-packet-ids
    {::state/status
     [::status-response
      ::pong-response]
     ::state/login
     [::login-disconnect ;; Uses JSON instead of a text component :p
      ::encryption-request
      ::login-success
      ::set-compression
      ::login-plugin-request
      ::login-cookie-request]
     ::state/config
     [::cookie-request
      ::plugin-message
      ::disconnect
      ::finish-config
      ::keep-alive
      ::ping
      ::reset-chat
      ::registry-data
      ::remove-resource-pack
      ::add-resource-pack
      ::store-cookie
      ::transfer
      ::feature-flags
      ::update-tags
      ::known-packs
      ::custom-report-details
      ::server-links
      ::clear-dialog
      ::show-dialog
      ::code-of-conduct]}))

(def ^{:spec (s/map-of ::pk/id (s/fspec :args (s/cat :buf #(instance? ByteBuf %) :pk ::pk/packet)
                                        :ret #(instance? ByteBuf %)))}
  packets
  (build-packets
    {::status-response [:response "Response to ::sp/status-request" :json]
     ::pong-response [:timestamp "The same timestamp as ::sp/ping-request" :long]
     ::login-success [:uuid :uuid
                      :username :string
                      :properties [:list [:table [:name :string
                                                  :value :string
                                                  :signature [:optional :string]]]]]
     ::known-packs [:known-packs [:list [:table [:namespace :string
                                                 :id :string
                                                 :version :string]]]]

     ::registry-data [:registry-id :identifier
                      :entries [:list [:table [:id :identifier
                                               :data [:optional :nbt]]]]]
     ::finish-config []}))

(defn numeric-id [state id]
  (packet-ids {::state/state state ::pk/id id}))

(defn packet-encoder [pk]
  (packets (::pk/id pk)))

(defn encode-packet
  "Encodes packet 'pk' into a ByteBuf of the packet bytes."
  [pk state]
  {:pre [(pk/packet? pk)]}
  (let [id (assert! (numeric-id state (::pk/id pk)))
        encoder (assert! (packet-encoder pk))
        data-buf (-> (Unpooled/buffer)
                     (write/write-varint! id)
                     (encoder pk))]
    ;; This buffer only contains the data + a varint, which can never be larger than 5 bytes
    (-> (Unpooled/buffer (+ (ByteBuf/.writerIndex data-buf) 5))
        (write/write-varint! (ByteBuf/.writerIndex data-buf))
        (ByteBuf/.writeBytes data-buf))))