Move SSD stream limit checks to typed Kotlin

ober

89387ba35910e34a30b6618bd9677c88a72b8c2b

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 648f630..db78b72 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1590,6 +1590,9 @@
                 storageStatsExceedsQuota storageCapacityWithinQuota
                 byteCountWithinLimit storageEntryBytesAllowed
                 positiveCount positiveByteCount learnedExamplesFull
+                streamReadEnded streamReadEmpty streamByteWasRead
+                inputCountExceeded outputSingleWriteExceeds
+                outputBufferWriteExceeds
                 learnedExamplesShouldRebuild treeDepthExceeded
                 zipEntryCountExceeded zipExpandedBytesExceeded
                 zipCompressionRatioExceeded zipExportQuotaExceeded
@@ -1699,6 +1702,20 @@
           (> count (int32 0)))
         (def (positiveByteCount (count : Int)) : Bool
           (> count (int 0)))
+        (def (streamReadEnded (read : Int32)) : Bool
+          (< read (int32 0)))
+        (def (streamReadEmpty (read : Int32)) : Bool
+          (= read (int32 0)))
+        (def (streamByteWasRead (value : Int32)) : Bool
+          (>= value (int32 0)))
+        (def (inputCountExceeded (count : Int) (limit : Int)) : Bool
+          (> count limit))
+        (def (outputSingleWriteExceeds (count : Int) (limit : Int)) : Bool
+          (> (+ count (int 1)) limit))
+        (def (outputBufferWriteExceeds (count : Int)
+                                       (length : Int)
+                                       (limit : Int)) : Bool
+          (or (< length (int 0)) (> (+ count length) limit)))
         (def (learnedExamplesFull (count : Int32) (maxExamples : Int32)) : Bool
           (>= count maxExamples))
         (def (learnedExamplesShouldRebuild (imported : Int32)
@@ -8160,14 +8177,14 @@
        "        private fun account(amount: Int): Int {"
        "            if (positiveCount(amount)) {"
        "                count += amount.toLong()"
-       "                if (count > limit) throw IllegalStateException(inputLimitExceededMessage(limit))"
+       "                if (inputCountExceeded(count, limit)) throw IllegalStateException(inputLimitExceededMessage(limit))"
        "            }"
        "            return amount"
        "        }"
        ""
        "        override fun read(): Int {"
        "            val value = super.read()"
-       "            if (value >= 0) account(1)"
+       "            if (streamByteWasRead(value)) account(1)"
        "            return value"
        "        }"
        "        override fun read(buffer: ByteArray, offset: Int, length: Int): Int ="
@@ -8179,13 +8196,13 @@
        "            private set"
        ""
        "        override fun write(value: Int) {"
-       "            if (count + 1L > limit) throw IllegalStateException(outputLimitExceededMessage(limit))"
+       "            if (outputSingleWriteExceeds(count, limit)) throw IllegalStateException(outputLimitExceededMessage(limit))"
        "            out.write(value)"
        "            count += 1L"
        "        }"
        ""
        "        override fun write(buffer: ByteArray, offset: Int, length: Int) {"
-       "            if (length < 0 || count + length.toLong() > limit) {"
+       "            if (outputBufferWriteExceeds(count, length.toLong(), limit)) {"
        "                throw IllegalStateException(outputLimitExceededMessage(limit))"
        "            }"
        "            out.write(buffer, offset, length)"
@@ -8200,10 +8217,10 @@
        "        var total = 0L"
        "        while (true) {"
        "            val read = input.read(buffer)"
-       "            if (read < 0) break"
-       "            if (read == 0) continue"
+       "            if (streamReadEnded(read)) break"
+       "            if (streamReadEmpty(read)) continue"
        "            total += read.toLong()"
-       "            if (total > limit) throw IllegalStateException(inputLimitExceededMessage(limit))"
+       "            if (inputCountExceeded(total, limit)) throw IllegalStateException(inputLimitExceededMessage(limit))"
        "            output.write(buffer, 0, read)"
        "        }"
        "        return output.toByteArray()"
@@ -8538,8 +8555,8 @@
        "        destination.outputStream().use { output ->"
        "            while (true) {"
        "                val read = zip.read(buffer)"
-       "                if (read < 0) break"
-       "                if (read == 0) continue"
+       "                if (streamReadEnded(read)) break"
+       "                if (streamReadEmpty(read)) continue"
        "                entryBytes += read.toLong()"
        "                budget.expandedBytes += read.toLong()"
        "                if (zipExpandedBytesExceeded(entryBytes, budget.expandedBytes, MAX_ZIP_ENTRY_BYTES, MAX_ZIP_TOTAL_BYTES)) {"