ab39dc4
Branches > namespace-reorganization > ab39dc4 > files / README.md
Update packet code
Commit: ab39dc45bcd9e0bacd7d506c11e40446e5b2ccc3
Parent: e528244
5 changed files:
src/net/bowmap/codecs/read.clj view file
@@ -37,6 +37,20 @@ (ensure-not! neg? len) (ByteBuf/.readCharSequence buf len (Charset/forName "UTF-8")))) +(defn read-enum! [codec m buf]+ (m (codec buf)))++(defn read-array [codec len buf])++(declare readers reader)++(defn reader+ "Returns a form that will evaluate to a function that takes a buffer and reads the respective type from it."+ ([t]+ (or (readers t) (raise "No such type!" {:type t})))+ ([t & args]+ (apply (reader t) args)))+ (def readers {:byte `ByteBuf/.readByte :ubyte `(comp unchecked-byte ByteBuf/.readByte)@@ -45,18 +59,6 @@ :int `ByteBuf/.readInt :long `ByteBuf/.readLong :varint `read-varint!- :string `read-string!})--(defn reader [t]- (or (readers t) (raise "No such type!" {:type t})))--(defn read-enum! [codec m buf]- (m (codec buf)))--(defn read-array [codec len buf])--; Perhaps have :optional and :required, so all non :optional or :prefixed-optional fields are implicitly :required-; Otherwise nils can easily creep into these functions and cause random NPEs-(def composite-readers- {:enum (fn [t enum-fn]- `(fn [buf#] (read-enum! ~(reader t) ~enum-fn buf#)))})+ :string `read-string!+ :enum (fn [t m]+ `(fn [buf#] (read-enum! ~(reader t) ~m buf#)))})src/net/bowmap/codecs/write.clj view file
@@ -1,6 +1,7 @@ (ns net.bowmap.codecs.write (:require- [clojure.data.json :as json])+ [clojure.data.json :as json]+ [dev.grahp.error :refer [raise]]) (:import [io.netty.buffer ByteBuf] [java.nio.charset Charset]))@@ -23,6 +24,17 @@ (defn write-json! [buf s] (write-string! buf (json/write-str s))) +(declare writer writers)++;; I think this should have :required/:optional/:prefixed-optional,+;; and have it that non-optional fields are implicity :required and thus nil checked+(defn writer+ "Returns a form that will evaluate to a function that takes a buffer and writes the respective type to it."+ ([t]+ (or (writers t) (raise "No such type!" {:type t})))+ ([t & args]+ (apply (writer t) args)))+ (def writers {:byte `ByteBuf/.writeByte :ubyte `ByteBuf/.writeByte@@ -33,6 +45,3 @@ :varint `write-varint! :string `write-string! :json `write-json!})--(def composite-writers- {})src/net/bowmap/packets/client.clj view file
@@ -6,7 +6,8 @@ [dev.grahp.pairs :refer [pairs]] [net.bowmap.client.state :as state] [net.bowmap.codecs.write :as write :refer [write-varint!]]- [net.bowmap.packet :as pk :refer [packet?]])+ [net.bowmap.packet :as pk :refer [packet?]]+ [net.bowmap.packets.registry :as reg]) (:import [io.netty.buffer ByteBuf Unpooled])) @@ -14,29 +15,22 @@ ;; TODO this also looks identical to the serverbound packet ids... (defmacro build-packet-ids [registry]- (into {} (mapcat (fn [[state packet-ids]]- (into {} (map-indexed (fn [i pk] [{::state/state state ::pk/id pk} i]))- packet-ids)))+ (into {}+ (mapcat (fn [[state packet-ids]]+ (into {} (map-indexed (fn [i pk] [{::state/state state ::pk/id pk} i]))+ packet-ids))) registry)) -;; May return nil-;; Should assert or something idk-;; TODO this function looks just like the one in serverbound packets (defn type->codec [t] (if (vector? t)- (let [[codec & args] t- writer (write/composite-writers codec)]- (when writer- (apply writer args)))- (write/writers t)))+ (apply write/writer t)+ (write/writer t))) (defn build-packet [pk-name schema]- (let [fn-name (symbol (str "encode-" (name pk-name)))- ;; TODO types can be []- [fields types] (pairs schema)- ;; ?????+ (let [fn-name (symbol (str "encode-" (name pk-name) "-packet"))+ fields (map :field schema)+ types (map :type schema) codecs (map type->codec types)- _ (assert codecs) name-codec-pairs (for [codec codecs] [(gensym) codec]) codec-names (map first name-codec-pairs)@@ -50,37 +44,36 @@ `(~codec ~buf (~field ~pk))) ~buf)))) +;; TODO why does this not use the conform-registry? (defmacro build-packets [registry] (reduce (fn [acc [packet-id schema]] (assoc acc packet-id (build-packet packet-id schema)))- {} (update-vals registry #(remove string? %))))+ {} (reg/conform-registry registry))) -;; TODO document why packet ids don't map to numeric ids directly-(def packet-ids- ^{:spec (s/map-of (s/schema [::state/state ::pk/id]) ::pk/numeric-id)}+(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]})) -;; Can return nil (defn numeric-id [state id] (packet-ids {::state/state state ::pk/id id})) -(def packets- ^{:spec (s/map-of ::pk/id (s/fspec :args (s/cat #(instance? ByteBuf %) ::pk/packet)- :ret #(instance? ByteBuf %)))}+(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 ::server/status-request" :json]- ::pong-response [:timestamp "The same timestamp as ::server/ping-request" :long]}))+ {::status-response [:response "Response to ::sp/status-request" :json]+ ::pong-response [:timestamp "The same timestamp as ::sp/ping-request" :long]})) -;; TODO can be nil (defn packet-encoder [pk] (packets (::pk/id pk))) -(comment- (packets ::status-response))- (defn encode-packet! [pk state] {:pre [(packet? pk)]} (let [id (assert! (numeric-id state (::pk/id pk)))@@ -88,6 +81,7 @@ data-buf (-> (Unpooled/buffer) (write-varint! id) (encoder pk))]- (-> (Unpooled/buffer)+ ;; This buffer only contains the data + a varint, which can never be larger than 5 bytes+ (-> (Unpooled/buffer (+ (ByteBuf/.writerIndex data-buf) 5)) (write-varint! (ByteBuf/.writerIndex data-buf)) (ByteBuf/.writeBytes data-buf))))src/net/bowmap/packets/registry.clj view file
@@ -4,7 +4,6 @@ [dev.grahp.error :refer [raise]] [net.bowmap.packet :as pk])) -;; TODO could have :type actually check that it's a valid type! (s/def ::packet-registry (s/map-of ::pk/id (s/* (s/cat :field keyword?src/net/bowmap/packets/server.clj view file
@@ -1,7 +1,7 @@ (ns net.bowmap.packets.server (:require [clojure.alpha.spec :as s]- [dev.grahp.error :refer [assert! raise]]+ [dev.grahp.error :refer [raise]] [net.bowmap.client.state :as state] [net.bowmap.codecs.read :as read] [net.bowmap.packet :as pk]@@ -13,13 +13,12 @@ (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))))+ (if (vector? t)+ (apply read/reader t)+ (read/reader t))) (defn build-packet [packet-id schema]- (let [fn-name (symbol (str "decode-" (name packet-id)))+ (let [fn-name (symbol (str "decode-" (name packet-id) "-packet")) buf (gensym "buf__") fields (map :field schema)@@ -39,9 +38,9 @@ (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))}+(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@@ -50,6 +49,7 @@ ::status-request [] ::ping-request [:timestamp :long]})) +;; TODO this is unreadable lol (defmacro build-packet-ids [registry] (into {} (mapcat (fn [[state packet-ids]]@@ -58,9 +58,8 @@ packet-ids))) registry)) -(def packet-ids- ^{:spec (s/map-of (s/schema [::state/state ::pk/numeric-id])- ::pk/id)}+(def ^{:spec (s/map-of (s/schema [::state/state ::pk/numeric-id]) ::pk/id)}+ packet-ids (build-packet-ids {::state/handshaking [::handshaking]@@ -68,17 +67,6 @@ [::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'.@@ -92,7 +80,8 @@ ;; 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)]+ 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}))))