readers.clj
Branches > main > latest > files / src / net / bowmap / server / packet / readers.clj
(ns net.bowmap.server.packet.readers
"Functions for reading packet values from frames.
Readers are named after the encoding they read, suffixed with an <, such as
string<, varint<, etc. Higher order functions like list< and map< build and
return readers.
See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types"
(:require
[clojure.spec.alpha :as s]
[medley.core :refer [assoc-some]]
[net.bowmap.server.packet.identifier :as identifier]
[net.bowmap.server.packet.varint :as varint])
(:import
[io.netty.buffer ByteBuf]
[java.nio.charset StandardCharsets]
[java.util UUID]))
(s/def ::reader (s/fspec
:args (s/cat :buf #(instance? ByteBuf %))
:ret any?))
(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 k (reader buf))
(catch Exception e
(throw (ex-info "Error while reading table field."
{:key k
:reader reader}
e)))))
(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 [k reader]]
(read-key acc k reader buf))
{} pairs))))
(defn apply<
"Returns a reader that calls f with the result of reading with each reader."
([f reader]
(fn [buf]
(f
(reader buf))))
([f reader1 reader2]
(fn [buf]
(f (reader1 buf)
(reader2 buf)))))
(defn then<
"Returns a reader that reads a value via 'reader', calls (f value) to get
the next reader, and reads with that."
[reader f]
(fn [buf]
((f (reader buf)) buf)))
(def byte< ByteBuf/.readByte)
(def ubyte< ByteBuf/.readUnsignedByte)
(def short< ByteBuf/.readShort)
(def ushort< ByteBuf/.readUnsignedShort)
(def int< ByteBuf/.readInt)
(def long< ByteBuf/.readLong)
(defn- finite
"Returns its argument, or throws if it is not a finite floating point number."
[x]
(if (and (float? x) (Double/isFinite x))
x
(throw (ex-info "Value is not a valid float (cannot read NaN/Infinity)"
{:value x}))))
(def float< (apply< finite ByteBuf/.readFloat))
(def double< (apply< finite ByteBuf/.readDouble))
(defn varint< [buf]
(varint/decode-varint ubyte< buf))
(defn varlong< [buf]
(varint/decode-varlong ubyte< buf))
(defn array<
"Returns a readers that 'length' number of values into a vector from
a buffer via 'reader'."
[reader length]
(if (nat-int? length)
(fn [buf]
(into [] (repeatedly length #(reader buf))))
(throw (ex-info "Reading array length must be a non-negative integer."
{:length length}))))
(defn list<
"Returns a reader that reads a varint length, then reads that many values
into a vector via 'reader' from a buffer.
When supplied 2 or more key value pairs, reads a vector of maps."
([reader]
(then< varint< (partial array< reader)))
([k v & kvs]
(list< (apply map< k v kvs))))
(defn- within-valid-length?
"Returns whether 'byte-count' number of UTF-8 bytes *may* be within
'max-length' UTF-16 code points (or string characters) when encoded into a
string. This can be used to avoid constructing strings that would be
certainly too long."
[max-length byte-count]
(<= 0 byte-count (* 3 max-length)))
(def maximum-string-characters 32767)
(defn string<*
"Returns a reader that reads a string as a varint byte length followed by its
UTF-8 bytes. Throws when the string exceeds 'max-length' characters."
[max-length]
{:pre [(int? max-length)
(<= 0 max-length maximum-string-characters)]}
(fn [buf]
(let [byte-count (varint< buf)]
(if-not (within-valid-length? max-length byte-count)
(throw (ex-info "String cannot fit within maximum length for reading."
{:max-length max-length
:number-of-bytes byte-count}))
(let [s (str (ByteBuf/.readCharSequence buf byte-count StandardCharsets/UTF_8))]
(if (> (count s) max-length)
(throw (ex-info "String exceeds maximum length for reading."
{:max-length max-length
:actual-length (count s)}))
s))))))
(def string< (string<* maximum-string-characters))
(def split-identifier<
"Reads an identifier from 2 strings: the namespace and the name."
(apply< identifier/decode-identifier string< string<))
(def uuid< (apply< UUID/new long< long<))
(defn enum<
"Returns a reader that reads one of a set of values via 'reader'.
Takes a map of {encoded-value decoded-value}, the same map as the enum
writer. Reads an encoded value via 'reader' and returns its decoded value.
Throws if the read value is not in the map. Use {value nil} to decode a
value as nil."
[reader enum-values]
(fn [buf]
(let [x (reader buf)]
(if (contains? enum-values x)
(get enum-values x)
(throw (ex-info "Failed to read enum, value is not a member."
{:value x
:enum-map enum-values}))))))