669730f
Branches > main > 669730f > files / src / net / bowmap / server / protocol / readers.clj
Add map readers and writers
Commit: 669730f7bd160dcbf254c24bfaace597b7e147cb
Parent: 431c728
5 changed files:
src/net/bowmap/server/protocol/clientbound_packets.clj view file
@@ -10,12 +10,12 @@ The order of packet ids determines their numeric ID." {::phase/status [::status-response status-response>- ::pong-response [::p/timestamp long>]]+ ::pong-response (map> ::p/timestamp long>)] ::phase/login [::disconnect nil ::encryption-request nil- ::login-success [::p/game-profile game-profile>- ::p/session-uuid uuid>]+ ::login-success (map> ::p/game-profile game-profile>+ ::p/session-uuid uuid>) ::set-compression nil ::login-plugin-request nil ::cookie-request nil]@@ -34,8 +34,8 @@ ::transfer nil ::feature-flags nil ::update-tags nil- ::known-packs [::p/known-packs (list> [::p/identifier split-identifier>- ::p/pack-version string>])]+ ::known-packs (map> ::p/known-packs (list> ::p/identifier split-identifier>+ ::p/pack-version string>)) ::custom-report-details nil ::server-links nil ::clear-dialog nil@@ -185,8 +185,6 @@ ::show-dialog-play nil]}) (def phase->id->numeric-id (update-vals clientbound-packets #(zipmap (take-nth 2 %) (range))))-(def phase->id->writer (update-vals clientbound-packets #(-> (apply hash-map %)- some-vals- (update-vals ->writer))))+(def phase->id->writer (update-vals clientbound-packets #(some-vals (apply hash-map %)))) (def ids (set (mapcat #(take-nth 2 %) (vals clientbound-packets))))src/net/bowmap/server/protocol/packet_serialization.clj view file
@@ -27,6 +27,7 @@ (let [buf (frame/->ByteBuf frame) numeric-id (readers/varint< buf) id (req! (serverbound/phase->numeric-id->id phase) numeric-id)- reader (req! (serverbound/phase->id->reader phase) id)]- (assoc (reader buf)+ reader (id (serverbound/phase->id->reader phase))]+ (assoc (when reader+ (reader buf)) ::packet/id id)))src/net/bowmap/server/protocol/readers.clj view file
@@ -9,47 +9,36 @@ [net.bowmap.server.protocol.types :as types]) (:import [io.netty.buffer ByteBuf]- [java.nio.charset Charset]+ [java.nio.charset StandardCharsets] [java.util UUID])) (s/def ::reader (s/fspec :args (s/cat :buf #(instance? ByteBuf %)) :ret any?)) -(defn- read-field- "Reads a field into map 'm' from 'buf' via 'reader'.- Throws a useful error message when that reading fails."- [m field reader buf]+(defn- read-key+ "Reads a map key via the supplied reader, throwing a useful error message+ when reading fails."+ [m k reader buf] (try- (assoc-some m field (reader buf))- (catch Exception t- (throw (ex-info "Error while reading table field" {:field field- :reader reader}- t)))))+ (assoc-some m k (reader buf))+ (catch Exception e+ (throw (ex-info "Error while reading table field."+ {:key k+ :reader reader}+ e))))) -(defn table<- "Takes a sequence of alternating keys and readers, e.g.- [:key reader< :key2 reader<].-- Returns a reader that builds a map where, for each key/reader pair,- the read value from the buffer and is mapped with the corresponding key.- Keys are omitted when the value is nil."- [table]- (let [t (partition 2 table)]+(defn map<+ "Takes pairs of map keys and readers, and returns a reader reads each value+ via the writers in their defined and maps the value to the corresponding+ key in a map if the value is not nil."+ [& keys-and-readers]+ {:pre [(even? (count keys-and-readers))]}+ (let [pairs (partition 2 keys-and-readers)] (fn [buf]- (reduce (fn [acc [field reader]]- (read-field acc field reader buf))- {} t))))--(defn ->reader- "Attempts to coerces 'x' into a reader. Throws if it cannot.- Vectors with pairs of [:key reader] are turned into tables readers.- Nil returns an empty table reader."- [x]- (cond- (or (vector? x) (nil? x)) (table< x)- (fn? x) x- :else (throw (ex-info "Cannot coerce into reader." {:x x}))))+ (reduce (fn [acc [k reader]]+ (read-key acc k reader buf))+ {} pairs)))) (def byte< ByteBuf/.readByte) (def ubyte< ByteBuf/.readUnsignedByte)@@ -65,7 +54,7 @@ result 0] (if (>= num-read types/max-varint-size) (throw (Exception. "VarInt too long"))- (let [b (ByteBuf/.readByte buf)+ (let [b (byte< buf) result' (bit-or result (bit-shift-left (bit-and b 0x7F) (* 7 num-read)))] (if (zero? (bit-and b 0x80)) result'@@ -75,25 +64,26 @@ "Returns a readers that 'length' number of values into a vector from a buffer via 'reader'." [reader length]- (let [reader (->reader reader)]- (fn [buf]- (into [] (repeatedly length #(reader buf))))))+ (fn [buf]+ (into [] (repeatedly length #(reader buf))))) (defn list< "Returns a reader that reads a varint length, then reads that many values- into a vector via 'reader' from a buffer."- [reader]- (let [reader (->reader reader)]- (fn [buf]- (let [length (varint< buf)]- ((array< reader length) buf)))))+ into a vector via 'reader' from a buffer.+ When supplied 2 or more key value pairs, reads a vector of maps."+ ([reader]+ (fn [buf]src/net/bowmap/server/protocol/serverbound_packets.clj view file
@@ -9,19 +9,19 @@ table that will turn into a reader, or nil for an empty reader. The order of packet ids determines their numeric ID." {::phase/handshaking- [::handshaking [:protocol varint<- :address string<- :port short<- ::phase/phase (enum< varint< {1 ::phase/status 2 ::phase/login 3 ::phase/transfer})]]+ [::handshaking (map< :protocol varint<+ :address string<+ :port short<+ ::phase/phase (enum< varint< {1 ::phase/status 2 ::phase/login 3 ::phase/transfer}))] ::phase/status- [::status-request []- ::ping-request [::p/timestamp long<]]+ [::status-request (map<)+ ::ping-request (map< ::p/timestamp long<)] ::phase/login- [::login-start [::p/username string<- ::p/uuid uuid<]+ [::login-start (map< ::p/username string<+ ::p/uuid uuid<) ::encryption-response nil ::login-plugin-response nil- ::login-acknowledged []+ ::login-acknowledged (map<) ::cookie-response nil] ::phase/config [::client-information nil@@ -31,8 +31,8 @@ ::keep-alive nil ::pong nil ::resource-pack-response nil- ::known-packs [::p/known-packs (list< [::p/identifier split-identifier<- ::p/pack-version string<])]+ ::known-packs (map< ::p/known-packs (list< ::p/identifier split-identifier<+ ::p/pack-version string<)) ::custom-click-action nil ::accept-code-of-conduct nil] ::phase/play@@ -107,7 +107,6 @@ ::custom-click-action nil]}) (def phase->numeric-id->id (update-vals serverbound-packets #(zipmap (range) (take-nth 2 %))))-(def phase->id->reader (update-vals serverbound-packets #(-> (apply hash-map %)- (update-vals ->reader))))+(def phase->id->reader (update-vals serverbound-packets #(some-vals (apply hash-map %)))) (def ids (set (mapcat #(take-nth 2 %) (vals serverbound-packets))))src/net/bowmap/server/protocol/writers.clj view file
@@ -18,41 +18,30 @@ :fn #(= (-> % :args :buf) (:ret %)))) -(defn- write-field- "Writes a field from map 'm' into 'buf' via 'writer'.- Throws a useful error message when that writing fails."- [writer buf m field]+(defn- write-key+ "Writes a map key via the supplied writer, throwing a useful error message+ when writing fails."+ [writer buf m k] (try- (writer buf (get m field))- (catch Exception t- (throw (ex-info "Error while writing table field" {:field field- field (get m field)- :writer writer- :table m}- t)))))+ (writer buf (get m k))+ (catch Exception e+ (throw (ex-info "Error while writing map key."+ {:key k+ :val (get m k)+ :writer writer+ :in-map m}+ e))))) -(defn table>- "Takes a sequence of alternating keys and writers, e.g.- [:key writer> :key2 writer>].-- Returns a writer that takes a map and, for each key/writer pair,- writes the value associated (or nil) with the key using the corresponding writer."- [table]- (let [t (partition 2 table)]- (fn table-writer [buf m]- (reduce (fn [acc [field writer]]- (write-field writer acc m field))- buf t))))--(defn ->writer- "Attempts to coerces 'x' into a writer. Throws if it cannot.- Vectors with pairs of [:key writer] are turned into tables writers.- Nil is not a valid writer."- [x]- (cond- (vector? x) (table> x)- (fn? x) x- :else (throw (ex-info "Cannot coerce into writer." {:x x}))))+(defn map>+ "Takes pairs of map keys and writers, and returns a writer that takes a map+ and writes each key's mapped values (or nil) via its corresponding writer."+ [& keys-and-writers]+ {:pre [(even? (count keys-and-writers))]}+ (let [pairs (partition 2 keys-and-writers)]+ (fn map-writer [buf m]+ (reduce (fn [acc [k writer]]+ (write-key writer acc m k))+ buf pairs)))) (def byte> buffer/write-byte!) @@ -108,18 +97,20 @@ (defn array> "Returns a writer that writes every value in a coll via 'writer'." [writer]- (let [writer (->writer writer)]- (fn [buf xs]- (reduce (fn [acc x]- (writer acc x))- buf xs))))+ (fn [buf xs]+ (reduce (fn [acc x]+ (writer acc x))+ buf xs))) (defn list> "Returns a writer that writes every value in a coll via 'writer', preceded- the varint length of the list."- [writer]- (then> (apply> count varint>)- (array> writer)))+ by the varint length of the coll.+ When supplied 2 or more key value pairs, writes a collection of maps via map>."+ ([writer]+ (then> (apply> count varint>)+ (array> writer)))+ ([k v & kvs]+ (list> (apply map> k v kvs)))) (def bytes> (list> byte>)) @@ -127,11 +118,11 @@ "Writes a string as a varint byte length followed by its UTF-8 bytes. When passed 1 arg, returns a string writer that throws when the string- exceeds 'max-length' characters."+ exceeds the supplied maximum number of UTF-8 bytes." ([max-length] (fn [buf s] (if (> (count s) max-length)- (throw (ex-info "Cannot encode string, too long" {:max-length max-length :string s}))+ (throw (ex-info "Cannot encode string, too long." {:max-length max-length :string s})) (string> buf s)))) ([buf s] (bytes> buf (String/.getBytes s StandardCharsets/UTF_8))))@@ -181,9 +172,9 @@ [writer] (context-optional> (then> boolean> writer))) -(def game-profile> (table> [::p/uuid uuid>- ::p/username (string> 16)- ::p/game-profile-properties- (list> [::p/game-profile-property-name (string> 64)- ::p/game-profile-property-value (string> 32767)- ::p/game-profile-property-signature (optional> (string> 1024))])]))+(def game-profile> (map> ::p/uuid uuid>+ ::p/username (string> 16)+ ::p/game-profile-properties+ (list> ::p/game-profile-property-name (string> 64)+ ::p/game-profile-property-value (string> 32767)+ ::p/game-profile-property-signature (optional> (string> 1024)))))