Move SSD TruthStore limit checks to typed Kotlin

ober

75849b4bb5ab560e40647d821690b7cf897bc5b5

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index aeab9e0..6677add 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1634,6 +1634,7 @@
                 destinationNewFileDelta
                 storageStatsExceedsQuota storageCapacityWithinQuota
                 byteCountWithinLimit storageEntryBytesAllowed
+                boundedReadLimitValid
                 positiveCount positiveByteCount learnedExamplesFull
                 streamReadEnded streamReadEmpty streamByteWasRead
                 inputCountExceeded outputSingleWriteExceeds
@@ -1751,6 +1752,8 @@
           (and (>= bytes (int 0)) (<= bytes maxBytes)))
         (def (storageEntryBytesAllowed (bytes : Int) (maxBytes : Int)) : Bool
           (byteCountWithinLimit bytes maxBytes))
+        (def (boundedReadLimitValid (limit : Int)) : Bool
+          (and (>= limit (int 1)) (<= limit (int 2147483647))))
         (def (positiveCount (count : Int32)) : Bool
           (> count (int32 0)))
         (def (positiveByteCount (count : Int)) : Bool
@@ -8217,7 +8220,7 @@
        ""
        "    fun loadTruth(sourceKey: String): JSONObject? {"
        "        val file = truthPath(sourceKey)"
-       "        if (!fileExists(file)) return null"
+       "        if (localFileMissing(file)) return null"
        "        return jsonObjectFromText(readLocalText(file))"
        "    }"
        ""
@@ -8329,7 +8332,7 @@
        "    }"
        ""
        "    private fun readBoundedBytes(input: InputStream, limit: Long): ByteArray {"
-       "        require(limit in 1..Int.MAX_VALUE.toLong())"
+       "        require(boundedReadLimitValid(limit))"
        "        val output = ByteArrayOutputStream(boundedReadInitialCapacity(limit).toInt())"
        "        val buffer = ByteArray(32 * 1024)"
        "        var total = 0L"
@@ -8359,7 +8362,7 @@
        "    private fun storageStats(): StorageStats {"
        "        val stats = StorageStats(0, 0L)"
        "        fun walk(file: File, depth: Int) {"
-       "            if (depth > 32) throw IllegalStateException(\"Storage tree too deep\")"
+       "            if (treeDepthExceeded(depth, 32)) throw IllegalStateException(\"Storage tree too deep\")"
        "            if (fileIsSymbolicLink(file)) throw IllegalStateException(\"Storage symlink rejected\")"
        "            if (fileIsDirectory(file)) {"
        "                file.listFiles()?.forEach { walk(it, depth + 1) }"
@@ -8589,8 +8592,8 @@
        ""
        "    fun applyLearnedGuesses(session: SsdSession): Int {"
        "        val file = File(learnedDir, learnedExamplesFileName())"
-       "        if (!fileExists(file)) rebuildLearnedExamples()"
-       "        if (!fileExists(file)) return 0"
+       "        if (localFileMissing(file)) rebuildLearnedExamples()"
+       "        if (localFileMissing(file)) return 0"
        "        val examples = learnedExamplesFromDocument(jsonObjectFromText(readLocalText(file)))"
        "        return applyLearnedGuessesFromExamples(session, examples)"
        "    }"
@@ -9022,7 +9025,7 @@
        "    val buffer = ByteArray(1024 * 64)"
        "    while (true) {"
        "        val n = input.read(buffer)"
-       "        if (n < 0) break"
+       "        if (streamReadEnded(n)) break"
        "        digest.update(buffer, 0, n)"
        "    }"
        "    return bytesToLowerHex(digest.digest())"