readers.clj
Branches > main > 0dda787 > files / src / net / bowmap / protocol / packet / readers.clj
(ns net.bowmap.protocol.packet.readers
"Functions for reading data from frames.
When given bad data, read functions will throw.
See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types"
(:require
[clojure.spec.alpha :as s]
[net.bowmap.protocol.packet.types :as t]
[net.bowmap.server.location :as location])
(:import
[io.netty.buffer ByteBuf]
[java.nio.charset Charset]
[java.util UUID]))
(defn ->varint [buf]
(loop [num-read 0
result 0]
(if (>= num-read t/max-varint-size)
(throw (Exception. "VarInt too long"))
(let [b (ByteBuf/.readByte buf)
result' (bit-or result (bit-shift-left (bit-and b 0x7F) (* 7 num-read)))]
(if (zero? (bit-and b 0x80))
result'
(recur (inc num-read) result'))))))
(defn ->string [buf]
(let [len (->varint buf)]
(if (neg? len)
(throw (Exception. "String length cannot be negative"))
(ByteBuf/.readCharSequence buf len (Charset/forName "UTF-8")))))
(defn ->uuid [buf]
(UUID. (ByteBuf/.readLong buf) (ByteBuf/.readLong buf)))
#_(defn read-array [codec len buf])
(defn ^:prviate read-bit-mask! [buf reader mask]
(let [n (reader buf)]
(reduce-kv (fn [acc bit x]
(if (bit-test n bit)
(conj acc x)
acc))
#{} mask)))
(def ->byte ByteBuf/.readByte)
(def ->ubyte (comp unchecked-byte ByteBuf/.readByte))
(def ->short ByteBuf/.readShort)
(def ->ushort (comp unchecked-byte ByteBuf/.readShort))
(def ->int ByteBuf/.readInt)
(def ->long ByteBuf/.readLong)
(def ->float ByteBuf/.readFloat)
(def ->double ByteBuf/.readDouble)
(def ->identifer ->string)
(defn ->enum [reader m]
(let [m (t/mapize-vector-or-something m)]
(fn [buf]
(m (reader buf)))))
; Table is a keyword?
(defn ->table [table-fields]
; TODO other one too
(assert (s/valid? ::t/table table-fields))
(let [table (s/conform ::t/table table-fields)]
(fn [buf]
(reduce (fn [acc {::t/keys! [field codec]}]
(if-some [x (codec buf)]
(assoc acc field x)
acc))
{} table))))
(defn coerce
"If reader is an actual reader, returns it. If reader is a vector, turnes it into a table reader."
[reader]
(if (vector? reader)
(->table reader)
reader))
; TODO what happens when a bad value is read in
(defn ->bit-mask [reader mask]
(let [mask (t/mapize-vector-or-something mask)]
(fn [buf]
(read-bit-mask! buf reader mask))))
(defn ->location [buf]
(let [v (ByteBuf/.readLong buf)
x (bit-shift-right v 38)
y (bit-shift-right (bit-shift-left v 52) 52)
z (bit-shift-right (bit-shift-left v 26) 38)]
#::location{:x x :y y :z z}))
;; TODO untested!
(defn- read-array [reader buf length]
(loop [i 0
acc []]
(if (< i length)
(recur (inc i)
(conj acc (reader buf)))
acc)))
(defn ->array [reader length]
(fn [buf]
(read-array reader buf length)))
(defn ->list [reader]
(fn [buf]
(let [length (->varint buf)]
(read-array reader buf length))))
(defn ->context-optional [writer]
(fn [buf x]
(if x
(writer buf x)
buf)))
(defn ->boolean [buf]
(= 1 (->varint buf)))
(defn ->optional [reader]
(fn [buf]
(when (->boolean buf)
(reader buf))))
(def ->identifier ->string)