server.clj
Branches > namespace-reorganization > e528244 > files / src / net / bowmap / packets / server.clj
(ns net.bowmap.packets.server
(:require
[clojure.alpha.spec :as s]
[dev.grahp.error :refer [assert! raise]]
[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', which should contain at least one readable packet.
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 (packet state numeric-id)]
(if dedec
(dedec buf)
(raise "Failed to decode packet, no such packet with that id." {:id numeric-id :state state}))))