readers.clj

Branches > main > ebd8059 > files / src / net / bowmap / transport / readers.clj

(ns net.bowmap.transport.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.transport.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)