readers.clj

Bowmap · bowmap.git · Tickets

Branches > main > latest > 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]
   [net.bowmap.server.packet.varint :as varint])
  (: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))))

(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 byte< ByteBuf/.readByte)
(def ubyte< ByteBuf/.readUnsignedByte)
(def short< ByteBuf/.readShort)
(def ushort< ByteBuf/.readUnsignedShort)
(def int< ByteBuf/.readInt)
(def long< ByteBuf/.readLong)

(defn- finite
  "Returns its argument, or throws if it is not a finite floating point number."
  [x]
  (if (and (float? x) (Double/isFinite x))
    x
    (throw (ex-info "Value is not a valid float (cannot read NaN/Infinity)"
                    {:value x}))))

(def float< (apply< finite ByteBuf/.readFloat))
(def double< (apply< finite ByteBuf/.readDouble))

(defn varint< [buf]
  (varint/decode-varint ubyte< buf))

(defn varlong< [buf]
  (varint/decode-varlong ubyte< buf))

(defn array<
  "Returns a readers that 'length' number of values into a vector from
   a buffer via 'reader'."
  [reader length]
  (if (nat-int? length)
    (fn [buf]
      (into [] (repeatedly length #(reader buf))))
    (throw (ex-info "Reading array length must be a non-negative integer."
                    {:length length}))))

(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- within-valid-length?
  "Returns whether 'byte-count' number of UTF-8 bytes *may* be within
   'max-length' UTF-16 code points (or string characters) when encoded into a
   string. This can be used to avoid constructing strings that would be
   certainly too long."
  [max-length byte-count]
  (<= 0 byte-count (* 3 max-length)))

(def maximum-string-characters 32767)

(defn string<*
  "Returns a reader that reads a string as a varint byte length followed by its
   UTF-8 bytes. Throws when the string exceeds 'max-length' characters."
  [max-length]
  {:pre [(int? max-length)
         (<= 0 max-length maximum-string-characters)]}
  (fn [buf]
    (let [byte-count (varint< buf)]
      (if-not (within-valid-length? max-length byte-count)
        (throw (ex-info "String cannot fit within maximum length for reading."
                        {:max-length max-length
                         :number-of-bytes byte-count}))
        (let [s (str (ByteBuf/.readCharSequence buf byte-count StandardCharsets/UTF_8))]
          (if (> (count s) max-length)
            (throw (ex-info "String exceeds maximum length for reading."
                            {:max-length max-length
                             :actual-length (count s)}))
            s))))))

(def string< (string<* maximum-string-characters))

(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<
  "Returns a reader that reads one of a set of values via 'reader'.
   Takes a map of {encoded-value decoded-value}, the same map as the enum
   writer. Reads an encoded value via 'reader' and returns its decoded value.
   Throws if the read value is not in the map. Use {value nil} to decode a
   value as nil."
  [reader enum-values]
  (fn [buf]
    (let [x (reader buf)]
      (if (contains? enum-values x)
        (get enum-values x)
        (throw (ex-info "Failed to read enum, value is not a member."
                        {:value x
                         :enum-map enum-values}))))))