read.clj

Branches > namespace-reorganization > 66ebfc3 > files / src / net / bowmap / packet / read.clj

(ns net.bowmap.packet.read
  (:require
   [clojure.alpha.spec :as s]
   [dev.grahp.error :refer [ensure-not! raise]]
   [net.bowmap.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 5)
      (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 read-varint! [buf]
  (loop [num-read 0
         result 0]
    (if (>= num-read 5)
      (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 read-string! [buf]
  (let [len (read-varint! buf)]
    (ensure-not! neg? len)
    (ByteBuf/.readCharSequence buf len (Charset/forName "UTF-8"))))

(defn read-uuid! [buf]
  (UUID. (ByteBuf/.readLong buf) (ByteBuf/.readLong buf)))

(defn read-enum! [codec m buf]
  (m (codec buf)))

#_(defn read-array [codec len buf])

(defn read-table! [buf codec-fields]
  (reduce (fn [acc {:keys [field codec]}]
            (if-some [x (codec buf)]
              (assoc acc field x)
              acc))
          {} codec-fields))

(declare readers)

(defn reader
  "Returns a function of a ByteBuf that reads the respective type, as defined by 'readers'.
   Throws if no such type exists."
  [t]
  (if (vector? t)
    (let [[t & args] t]
      (apply (reader t) args))
    (or (readers t)
        (raise (str "No such type! " (name t)) {:type t}))))

(def readers
  {:byte   ByteBuf/.readByte
   :ubyte  (comp unchecked-byte ByteBuf/.readByte)
   :short  ByteBuf/.readShort
   :ushort (comp unchecked-short ByteBuf/.readShort)
   :int    ByteBuf/.readInt
   :long   ByteBuf/.readLong
   :varint read-varint!
   :string read-string!
   :uuid   read-uuid!
   :identifer read-string!
   :table (fn [table]
            (let [table (->> (s/conform ::t/table table)
                             (mapv #(assoc % :codec (reader (:type %)))))]
              (fn [buf]
                (read-table! buf table))))
   :enum (fn [t m]
           (fn [buf] (read-enum! (reader t) m buf)))})