server.clj

Branches > namespace-reorganization > 73b711f > files / src / net / bowmap / packets / server.clj

(ns net.bowmap.packets.server
  (:require
   [clojure.alpha.spec :as s]
   [dev.grahp.error :refer [assert!]]
   [net.bowmap.client.state :as state]
   [net.bowmap.codecs.read :as read]
   [net.bowmap.packet :as pk]
   [net.bowmap.packets.registry :as reg])
  (:import
   [io.netty.buffer ByteBuf]))

;; Surely there's a better way to do that lmao
(defn type->codec
  "Takes a type from net.bowmap.codecs.read and returns a form that will evaluate to a function to decode that type from a ByteBuffer"
  [t]
  (assert! (if (vector? t)
             (let [[codec & args] t]
               (apply (read/composite-readers codec) args))
             (read/readers t))))

#_(defn build-packet [packet-id schema]
    (let [fn-name (symbol (str "decode-" (name packet-id)))
          buf (gensym "buf__")

          fields (map :field schema)
          types (map :type schema)
          name-codec-pairs (for [t types]
                             [(gensym "decode__") (type->codec t)])
          codec-calls (for [[codec-binding] name-codec-pairs]
                        `(~codec-binding ~buf))
          names->codecs (apply concat name-codec-pairs)]
      `(let [~@names->codecs]
         (fn ~fn-name [~buf]
           (assoc (zipmap [~@fields] [~@codec-calls])
                  ::pk/id ~packet-id)))))

(defn build-packet [packet-id schema]
  (let [fn-name (symbol (str "decode-" (name packet-id)))
        buf (gensym "buf__")

        fields (map :field schema)
        types (map :type schema)
        name-codec-pairs (for [t types]
                           [(gensym "decode__") (type->codec t)])
        codec-calls (for [[codec-binding] name-codec-pairs]
                      `(~codec-binding ~buf))
        names->codecs (apply concat name-codec-pairs)]
    `(let [~@names->codecs]
       (fn ~fn-name [~buf]
         (hash-map ::pk/id ~packet-id
                   ~@(interleave fields codec-calls))))))

(defmacro build-packets [registry]
  (reduce (fn [acc [packet-id schema]]
            (assoc acc packet-id (build-packet packet-id schema)))
          {} (reg/conform-registry registry)))

(def packets
  ^{:spec (s/map-of ::pk/id (s/fspec :args (s/cat #(instance? % ByteBuf))
                                     :ret ::pk/packet))}
  (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]}))

(defmacro build-packet-ids [registry]
  (into {}
        (mapcat (fn [[state packet-ids]]
                  (into {}
                        (map-indexed (fn [i pk] [{::state/state state ::pk/numeric-id i} pk]))
                        packet-ids)))
        registry))

(def packet-ids
  ^{:spec (s/map-of (s/schema [::state/state ::pk/numeric-id])
                    ::pk/id)}
  (build-packet-ids
   {::state/handshaking
    [::handshaking]
    ::state/status
    [::status-request
     ::ping-request]}))

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

;; TODO unit test all these :p
(defn packet
  "Takes a packet state and numeric id an returns a packet function (in 'packets'),
   or nil if there is no such packet."
  [state numeric-id]
  (packets (packet-id state numeric-id)))

(defn decode-packet!
  "Decodes and returns one packet from ByteBuf 'buf'.
   The initial length field of the packet should not be stripped from 'buf'.
   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"
  [buf state]
  {:pre [(read/can-read-packet? buf)]}
  ;; The length is currently unsed, though there are packets that require it in order to decode.
  ;; None of those packets have been implemented yet, though.
  (let [_unused-length (read/read-varint! buf)
        numeric-id (read/read-varint! buf)
        dedec (assert! (packet state numeric-id))]
    (dedec buf)))