readers.clj

Bowmap · bowmap.git · Tickets

Branches > main > 32cb01c > files / src / net / bowmap / server / protocol / readers.clj

(ns net.bowmap.server.protocol.readers
  "Functions for reading values from io.netty.ByteBufs.
   Buffers are assumed to be properly framed and contain exactly 1 packet.
   Functions look like type<, where 'type' is a Minecraft protocol type.
   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.protocol.types :refer [max-varint-size]])
  (:import
   [io.netty.buffer ByteBuf]
   [java.nio.charset Charset]
   [java.util UUID]))

(s/def ::reader (s/fspec
                  :args (s/cat :buf #(instance? ByteBuf %))
                  :ret any?))

(defn- read-field
  "Reads a field into map 'm' from 'buf' via 'reader'.
   Throws a useful error message when that reading fails."
  [m field reader buf]
  (try
    (assoc-some m field (reader buf))
    (catch Throwable t
      (throw (ex-info "Error while reading table field" {:field field
                                                         :reader reader}
                      t)))))

(defn table<
  "Takes a sequence of alternating keys and readers, e.g.
   [:key reader< :key2 reader<].

   Returns a reader that builds a map where, for each key/reader pair,
   the read value from the buffer and is mapped with the corresponding key.
   Keys are omitted when the value is nil."
  [table]
  (let [t (partition 2 table)]
    (fn [buf]
      (reduce (fn [acc [field reader]]
                (read-field acc field reader buf))
              {} t))))

(defn ->reader
  "Attempts to coerces 'x' into a reader. Throws if it cannot.
   Vectors with pairs of [:key reader] are turned into tables readers.
   Nil returns an empty table reader."
  [x]
  (cond
    (or (vector? x) (nil? x)) (table< x)
    (fn? x) x
    :else (throw (IllegalArgumentException. (str "Cannot coerce into reader: " (pr-str x))))))

(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 varint< [buf]
  (loop [num-read 0
         result 0]
    (if (>= num-read 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. (long< buf) (long< buf)))

(defn enum< [reader m]
  (fn [buf]
    (m (reader buf))))