read.clj

Branches > client-handling-rewrite > 9ac197d > files / src / net / bowmap / network / packet / read.clj

(ns net.bowmap.network.packet.read
  "Functions for reading data from serialized packet ByteBufs.
   When given bad data, read functions will throw.
   See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types"
  (:require
   [dev.grahp.error :refer [adorn ensure-not! raise]]
   [dev.grahp.spec :as S]
   [net.bowmap.network.packet.types :as t])
  (:import
   [io.netty.buffer ByteBuf]
   [java.nio.charset Charset]
   [java.util UUID]))

(defn can-read-packet?
  "Returns whether a varint length can be read from buf, and whether buf has enough bytes to read a full packet.
   Can throw if varint is too long, meaning it is completely invalid."
  [buf]
  (loop [num-read 0
         result 0]
    (if (>= num-read t/max-varint-size)
      (raise "VarInt too long")
      (when (< num-read (ByteBuf/.readableBytes buf))
        (let [b (ByteBuf/.getByte buf (+ (ByteBuf/.readerIndex buf)
                                        num-read))
              result' (bit-or result (bit-shift-left (bit-and b 0x7F) (* 7 num-read)))]
          (if (zero? (bit-and b 0x80))
            (>= (ByteBuf/.readableBytes buf) (+ result' num-read 1))
            (recur (inc num-read) result')))))))

(defn ->varint [buf]
  (loop [num-read 0
         result 0]
    (if (>= num-read t/max-varint-size)
      (raise "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)]
    (ensure-not! neg? len)
    (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 codec mask]
  (let [n (codec 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]
  (fn [buf]
    (m (reader buf))))

; Table is a keyword?
(defn ->table [table-fields]
  ; TODO other one too
  (let [table (S/ensure-conform ::t/table table-fields)]
    (fn [buf]
      (reduce (fn [acc {::t/keys [field codec]}]
                (adorn (if-some [x (codec buf)]
                         (assoc acc field x)
                         acc)
                       "Error while reading message table field" {:table table :field field :codec codec}))
              {} table))))

(defn ->bit-mask [reader mask]
  (let [mask (t/mapize-vector-or-something mask)]
    (fn [buf]
      (read-bit-mask! buf reader mask))))