server.clj
Branches > namespace-reorganization > e863077 > 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]
(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)))