readers.clj

Bowmap · bowmap.git · Tickets

Branches > main > d4a482d > 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])
  (: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))))

(def byte< ByteBuf/.readByte)
(def ubyte< ByteBuf/.readUnsignedByte)
(def short< ByteBuf/.readShort)
(def ushort< ByteBuf/.readUnsignedShort)
(def int< ByteBuf/.readInt)
(def long< ByteBuf/.readLong)
(def float< ByteBuf/.readFloat)
(def double< ByteBuf/.readDouble)

(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 max-varint-size 5)

(defn varint< [buf]
  (loop [num-read 0
         result 0]
    (if (>= num-read max-varint-size)
      (throw (Exception. "VarInt too long"))
      (let [b (byte< 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 array<
  "Returns a readers that 'length' number of values into a vector from
   a buffer via 'reader'."
  [reader length]
  (fn [buf]
    (into [] (repeatedly length #(reader buf)))))

(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 string< [buf]
  (ByteBuf/.readCharSequence buf (varint< buf) StandardCharsets/UTF_8))

(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< [reader m]
  (apply< m reader))