All Downloads are FREE. Search and download functionalities are using the official Maven repository.

horan.kse3-eio_3.0.3.6.source-code.Adaptors.scala Maven / Gradle / Ivy

There is a newer version: 0.3.11
Show newest version
// This file is distributed under the BSD 3-clause license.  See file LICENSE.
// Copyright (c) 2014-15, 2020-23 Rex Kerr, UCSF, and Calico Life Sciences LLC

package kse.eio


import java.io._
import java.nio._
import java.nio.file._
import java.nio.channels.{ReadableByteChannel, WritableByteChannel, SeekableByteChannel}
import java.nio.charset.StandardCharsets._
import java.util.Base64

import scala.collection.immutable.{Range => Rg}

import kse.basics.{given, _}
import kse.basics.intervals.{Iv, PIv}
import kse.flow.{given, _}
import kse.maths.{given, _}
import kse.maths.packed.{given, _}


object EioBase64 {
  private val encode64urlTable: Array[Byte] = Array(
    'A'.toByte, 'B'.toByte, 'C'.toByte, 'D'.toByte, 'E'.toByte, 'F'.toByte, 'G'.toByte, 'H'.toByte,
    'I'.toByte, 'J'.toByte, 'K'.toByte, 'L'.toByte, 'M'.toByte, 'N'.toByte, 'O'.toByte, 'P'.toByte,
    'Q'.toByte, 'R'.toByte, 'S'.toByte, 'T'.toByte, 'U'.toByte, 'V'.toByte, 'W'.toByte, 'X'.toByte,
    'Y'.toByte, 'Z'.toByte, 'a'.toByte, 'b'.toByte, 'c'.toByte, 'd'.toByte, 'e'.toByte, 'f'.toByte,
    'g'.toByte, 'h'.toByte, 'i'.toByte, 'j'.toByte, 'k'.toByte, 'l'.toByte, 'm'.toByte, 'n'.toByte,
    'o'.toByte, 'p'.toByte, 'q'.toByte, 'r'.toByte, 's'.toByte, 't'.toByte, 'u'.toByte, 'v'.toByte,
    'w'.toByte, 'x'.toByte, 'y'.toByte, 'z'.toByte, '0'.toByte, '1'.toByte, '2'.toByte, '3'.toByte,
    '4'.toByte, '5'.toByte, '6'.toByte, '7'.toByte, '8'.toByte, '9'.toByte, '-'.toByte, '_'.toByte
  )

  private val decode64Table: Array[Byte] = Array(
    // Starts at index 9, given in groups of 9
    64, 64, -1, -1, 64, -1, -1, -1, -1,  //  9-17
    -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 18-26
    -1, -1, -1, -1, -1, 64, -1, -1, -1,  // 27-35
    -1, -1, -1, -1, -1, -1, -1, 62, -1,  // 36-44
    62, -1, 63, 52, 53, 54, 55, 56, 57,  // 45-53
    58, 59, 60, 61, -1, -1, -1, 64, -1,  // 54-62
    -1, -1,  0,  1,  2,  3,  4,  5,  6,  // 63-71
     7,  8,  9, 10, 11, 12, 13, 14, 15,  // 72-80
    16, 17, 18, 19, 20, 21, 22, 23, 24,  // 81-89
    25, -1, -1, -1, -1, 63, -1, 26, 27,  // 90-98
    28, 29, 30, 31, 32, 33, 34, 35, 36,  // 99-107
    37, 38, 39, 40, 41, 42, 43, 44, 45,  //108-115
    46, 47, 48, 49, 50, 51               //117-122
  )

  def encodeUrlRange(raw: Array[Byte], i0: Int, iN: Int, lineLength: Int = Int.MaxValue): Array[Byte] =
    if i0 < 0 || iN > raw.length then throw new RuntimeException(s"Invalid input range: $i0 until $iN in input of length ${raw.length}")
    var l = ((iN - i0)*4 + 2)/3
    val n = if lineLength < 4 then 1 else lineLength/4
    if l > 4*n then l += (l-1)/(4*n)
    if l > Int.MaxValue - 7 then throw new RuntimeException(s"Overly long Base64 encode: would require $l bytes")
    val a = new Array[Byte](l.toInt)
    var i = i0
    var j = 0
    var k = 0
    while i+2 < iN do
      k += 1
      if k > n then
        k = 1
        a(j) = '\n'.toByte
        j += 1
      val chunk = ((raw(i) & 0xFF) << 16) | ((raw(i+1) & 0xFF) << 8) | (raw(i+2) & 0xFF)
      a(j  ) = encode64urlTable( chunk >>> 18        )
      a(j+1) = encode64urlTable((chunk >>> 12) & 0x3F)
      a(j+2) = encode64urlTable((chunk >>>  6) & 0x3F)
      a(j+3) = encode64urlTable( chunk         & 0x3F)
      i += 3
      j += 4
    if i < iN then
      if k >= n then
        a(j) = '\n'.toByte
        j += 1
      if i + 1 == iN then
        val chunk = (raw(i) & 0xFF) << 16
        a(j  ) = encode64urlTable( chunk >>> 18)
        a(j+1) = encode64urlTable((chunk >>> 12) & 0x3F)
      else
        val chunk = ((raw(i) & 0xFF) << 16) | ((raw(i+1) & 0xFF) << 8)
        a(j  ) = encode64urlTable( chunk >>> 18        )
        a(j+1) = encode64urlTable((chunk >>> 12) & 0x3F)
        a(j+2) = encode64urlTable((chunk >>>  6) & 0x3F)
    a
  inline def encodeUrlRange(raw: Array[Byte], inline rg: Rg): Array[Byte] =
    val iv = Iv of rg
    encodeUrlRange(raw, iv.i0, iv.iN)
  inline def encodeUrlRange(raw: Array[Byte], inline rg: Rg, lineLength: Int): Array[Byte] =
    val iv = Iv of rg
    encodeUrlRange(raw, iv.i0, iv.iN, lineLength)
  inline def encodeUrlRange(raw: Array[Byte], inline v: Iv | PIv): Array[Byte] =
    val iv = Iv.of(v, raw)
    encodeUrlRange(raw, iv.i0, iv.iN)
  inline def encodeUrlRange(raw: Array[Byte], inline v: Iv | PIv, lineLength: Int): Array[Byte] =
    val iv = Iv.of(v, raw)
    encodeUrlRange(raw, iv.i0, iv.iN, lineLength)

  def encodeUrl(raw: Array[Byte], lineLength: Int = Int.MaxValue): Array[Byte] =
    encodeUrlRange(raw, 0, raw.length, lineLength)

  def stringEncodeUrl(raw: Array[Byte], lineLength: Int = Int.MaxValue): String =
    new String(encodeUrlRange(raw, 0, raw.length, lineLength), ISO_8859_1)

  def decodeRangeInto(encoded: Array[Byte], i0: Int, iN: Int)(target: Array[Byte], index: Int): Int Or Err = Or.Ret:
    var bits = 0
    var nb = 0
    var i = i0
    if i0 < 0 || iN > encoded.length then Err.break(s"Input $i0 until $iN not within input of length ${encoded.length}")
    if index < 0 then Err.break(s"Output index is negative: $index")
    var j = index
    while i < iN do
      val b = encoded(i)
      val y =
        if b >= 9 && b <= 122 then decode64Table(b-9)
        else -1
      if y == -1 then Err.break(s"Invalid Base64 character ${b.toChar} at index $i")
      if y != 64 then 
        bits = (bits << 6) | y
        nb += 6
      if nb >= 24 then
        if j > target.length-2 then Err.break(s"Insufficient space in decoding array with ${4+(iN-i)} input bytes remaining")
        target(j) = bits.byte(2); j += 1
        target(j) = bits.byte(1); j += 1
        target(j) = bits.byte(0); j += 1
        bits = 0
        nb = 0
      i += 1
    if nb >= 8 then
      if j >= target.length then Err.break(s"Insufficient space in decoding array with $nb input bits remaining")
      bits = bits << (24 - nb)
      target(j) = bits.byte(2)
      bits = bits & 0xFFFF
      j += 1
      if nb >= 16 then
        if j >= target.length then Err.break(s"Insufficient space in decoding array with ${nb - 8} input bits remaining")
        target(j) = bits.byte(1)
        bits = bits & 0xFF
        j += 1
    if bits != 0 then Err.break(s"Trailing bits: ${if bits.byte(1) != 0 then bits.byte(1).bitString else bits.byte(0).bitString}; Base64 data must be incomplete")
    j - index
  inline def decodeRangeInto(encoded: Array[Byte], i0: Int, iN: Int)(target: Array[Byte]): Int Or Err =
    decodeRangeInto(encoded, i0, iN)(target, 0)
  inline def decodeRangeInto(encoded: Array[Byte], inline rg: Rg)(target: Array[Byte], index: Int): Int Or Err =
    val iv = Iv of rg
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, index)
  inline def decodeRangeInto(encoded: Array[Byte], inline v: Iv | PIv)(target: Array[Byte], index: Int): Int Or Err =
    val iv = Iv.of(v, encoded)
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, index)
  inline def decodeRangeInto(encoded: Array[Byte], inline rg: Rg)(target: Array[Byte]): Int Or Err =
    val iv = Iv of rg
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, 0)
  inline def decodeRangeInto(encoded: Array[Byte], inline v: Iv | PIv)(target: Array[Byte]): Int Or Err =
    val iv = Iv.of(v, encoded)
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, 0)

  def decodeInto(encoded: Array[Byte])(target: Array[Byte], index: Int): Int Or Err =
    decodeRangeInto(encoded, 0, encoded.length)(target, index)
  inline def decodeInto(encoded: Array[Byte])(target: Array[Byte]): Int Or Err =
    decodeInto(encoded)(target, 0)

  def decodeRange(encoded: Array[Byte], i0: Int, iN: Int): Array[Byte] Or Err =
    if i0 < 0 || iN > encoded.length then Err.or(s"Base64 decode range $i0 until $iN not within array of length ${encoded.length}")
    else
      val a = new Array[Byte]((((iN - i0).toLong*3 + 2) / 4).toInt)
      decodeRangeInto(encoded, i0, iN)(a, 0).map(i => a.shrinkCopy(i))
  inline def decodeRange(encoded: Array[Byte], inline rg: Rg): Array[Byte] Or Err =
    val iv = Iv of rg
    decodeRange(encoded, iv.i0, iv.iN)
  inline def decodeRange(encoded: Array[Byte], inline v: Iv | PIv): Array[Byte] Or Err =
    val iv = Iv.of(v, encoded)
    decodeRange(encoded, iv.i0, iv.iN)

  def decode(encoded: Array[Byte]): Array[Byte] Or Err =
    val a = new Array[Byte](((encoded.length.toLong*3 + 2)/4).toInt)
    decodeRangeInto(encoded, 0, encoded.length)(a, 0).map(i => a.shrinkCopy(i))

  def decodeRangeInto(encoded: String, i0: Int, iN: Int)(target: Array[Byte], index: Int): Int Or Err = Or.Ret:
    var bits = 0
    var nb = 0
    var i = i0
    if iN > encoded.length then Err.break(s"Input length exceeded: $i0 until $iN not within ${encoded.length}")
    if index < 0 then Err.break(s"Output index is negative: $index")
    var j = index
    while i < iN do
      val c = encoded.charAt(i)
      val y =
        if c >= 9 && c <= 122 then decode64Table(c-9)
        else -1
      if y == -1 then Err.break(s"Invalid Base64 character $c at index $i")
      if y != 64 then 
        bits = (bits << 6) | y
        nb += 6
      if nb >= 24 then
        if j > target.length-2 then Err.break(s"Insufficient space in decoding array with ${4+(iN-i)} input bytes remaining")
        target(j) = bits.byte(2); j += 1
        target(j) = bits.byte(1); j += 1
        target(j) = bits.byte(0); j += 1
        bits = 0
        nb = 0
      i += 1
    if nb >= 8 then
      if j >= target.length then Err.break(s"Insufficient space in decoding array with $nb input bits remaining")
      bits = bits << (24 - nb)
      target(j) = bits.byte(2)
      bits = bits & 0xFFFF
      j += 1
      if nb >= 16 then
        if j >= target.length then Err.break(s"Insufficient space in decoding array with ${nb - 8} input bits remaining")
        target(j) = bits.byte(1)
        bits = bits & 0xFF
        j += 1
    if bits != 0 then Err.break(s"Trailing bits: ${if bits.byte(1) != 0 then bits.byte(1).bitString else bits.byte(0).bitString}; Base64 data must be incomplete")
    j - index
  inline def decodeRangeInto(encoded: String, i0: Int, iN: Int)(target: Array[Byte]): Int Or Err =
    decodeRangeInto(encoded, i0, iN)(target, 0)
  inline def decodeRangeInto(encoded: String, inline rg: Rg)(target: Array[Byte], index: Int): Int Or Err =
    val iv = Iv of rg
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, index)
  inline def decodeRangeInto(encoded: String, inline v: Iv | PIv)(target: Array[Byte], index: Int): Int Or Err =
    val iv = Iv.of(v, encoded)
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, index)
  inline def decodeRangeInto(encoded: String, inline rg: Rg)(target: Array[Byte]): Int Or Err =
    val iv = Iv of rg
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, 0)
  inline def decodeRangeInto(encoded: String, inline v: Iv | PIv)(target: Array[Byte]): Int Or Err =
    val iv = Iv.of(v, encoded)
    decodeRangeInto(encoded, iv.i0, iv.iN)(target, 0)

  def decodeInto(encoded: String)(target: Array[Byte], index: Int): Int Or Err =
    decodeRangeInto(encoded, 0, encoded.length)(target, index)
  inline def decodeInto(encoded: String)(target: Array[Byte]): Int Or Err =
    decodeInto(encoded)(target, 0)

  def decodeRange(encoded: String, i0: Int, iN: Int): Array[Byte] Or Err =
    if i0 < 0 || iN > encoded.length then Err.or(s"Base64 decode range $i0 until $iN not within string of length ${encoded.length}")
    else
      val a = new Array[Byte]((((iN - i0).toLong*3) / 4).toInt)
      decodeRangeInto(encoded, i0, iN)(a, 0).map(i => a.shrinkCopy(i))
  inline def decodeRange(encoded: String, inline rg: Rg): Array[Byte] Or Err =
    val iv = Iv of rg
    decodeRange(encoded, iv.i0, iv.iN)
  inline def decodeRange(encoded: String, inline v: Iv | PIv): Array[Byte] Or Err =
    val iv = Iv.of(v, encoded)
    decodeRange(encoded, iv.i0, iv.iN)

  def decode(encoded: String): Array[Byte] Or Err =
    val a = new Array[Byte](((encoded.length.toLong*3)/4).toInt)
    decodeRangeInto(encoded, 0, encoded.length)(a, 0).map(i => a.shrinkCopy(i))
}


object EioBase85 {
  private def encodeTable_to_decodeTable(encoder: Array[Byte]): Array[Byte] =
    if encoder.length == 0 then encoder
    else
      var bmin = '\t'.toByte
      var bmax = ' '.toByte
      encoder.peek(){ b =>
        if b < bmin then bmin = b
        if b > bmax then bmax = b
      }
      if bmin < '\t' then throw new Exception(s"Tried to decode a table with indices smaller than tab: $bmin")
      val decoder: Array[Byte] = Array.fill[Byte](1 + bmax - 9)(-1)
      encoder.visit(){ (b, i) =>
        if decoder(b - 9) != -1 then throw new Exception(s"Encoder irreversible because of double mapping to $b")
        decoder(b - 9) = i.toByte
      }
      if decoder( 0) == -1 then decoder( 0) = (encoder.length).toByte
      if decoder( 1) == -1 then decoder( 1) = (encoder.length).toByte
      if decoder( 3) == -1 then decoder( 3) = (encoder.length).toByte
      if decoder(23) == -1 then decoder(23) = (encoder.length).toByte
      decoder

  private val encodeAsciiTable: Array[Byte] = Array.tabulate(85)(i => (i+33).toByte)

  private val encodeZmqTable: Array[Byte] = Array(
    '0'.toByte, '1'.toByte, '2'.toByte, '3'.toByte, '4'.toByte, '5'.toByte, '6'.toByte, '7'.toByte, '8'.toByte, '9'.toByte,
    'a'.toByte, 'b'.toByte, 'c'.toByte, 'd'.toByte, 'e'.toByte, 'f'.toByte, 'g'.toByte, 'h'.toByte, 'i'.toByte, 'j'.toByte,
    'k'.toByte, 'l'.toByte, 'm'.toByte, 'n'.toByte, 'o'.toByte, 'p'.toByte, 'q'.toByte, 'r'.toByte, 's'.toByte, 't'.toByte,
    'u'.toByte, 'v'.toByte, 'w'.toByte, 'x'.toByte, 'y'.toByte, 'z'.toByte, 'A'.toByte, 'B'.toByte, 'C'.toByte, 'D'.toByte,
    'E'.toByte, 'F'.toByte, 'G'.toByte, 'H'.toByte, 'I'.toByte, 'J'.toByte, 'K'.toByte, 'L'.toByte, 'M'.toByte, 'N'.toByte,
    'O'.toByte, 'P'.toByte, 'Q'.toByte, 'R'.toByte, 'S'.toByte, 'T'.toByte, 'U'.toByte, 'V'.toByte, 'W'.toByte, 'X'.toByte,
    'Y'.toByte, 'Z'.toByte, '.'.toByte, '-'.toByte, ':'.toByte, '+'.toByte, '='.toByte, '^'.toByte, '!'.toByte, '/'.toByte,
    '*'.toByte, '?'.toByte, '&'.toByte, '<'.toByte, '>'.toByte, '('.toByte, ')'.toByte, '['.toByte, ']'.toByte, '{'.toByte,
    '}'.toByte, '@'.toByte, '%'.toByte, '$'.toByte, '#'.toByte
  )

  private val decodeZmqTable = encodeTable_to_decodeTable(encodeZmqTable)

  private val decodeAsciiTable = encodeTable_to_decodeTable(encodeAsciiTable)

  private def encodeRangeWithTable(raw: Array[Byte], i0: Int, iN: Int, table: Array[Byte]): Array[Byte] =
    if i0 < 0 || iN > raw.length then throw new RuntimeException(s"Range $i0 until $iN out of bounds of array of length ${raw.length}")
    val l = ((iN - i0)*5 + 3)/4
    if l > Int.MaxValue - 7 then throw new RuntimeException(s"Overly long Base85 encode: would require $l bytes")
    val result = new Array[Byte](l.toInt)
    var code = 0
    var i = i0
    var j = 0
    while i+3 < iN do
      code = Pack.I(raw(i+3), raw(i+2), raw(i+1), raw(i))
      i += 4
      var c85 = (code.u / 85.u).s
      result(j+4) = table(code - 85*c85)
      code = c85
      c85 = c85 / 85
      result(j+3) = table(code - 85*c85)
      code = c85
      c85 = c85 / 85
      result(j+2) = table(code - 85*c85)
      code = c85
      c85 = c85 / 85
      result(j+1) = table(code - 85*c85)
      result(j) = table(c85)
      j += 5
    if i < iN then
      val overflow = iN - i
      code = 0
      while i < iN do
        code = (code << 8) | (raw(i) & 0xFF)
        i += 1
      code = code << (8 * (4 - overflow))
      var c = (code.u / 52200625.u).s
      result(j) = table(c)
      code = code - 52200625*c
      c = code / 614125
      result(j+1) = table(c)
      if overflow > 1 then
        code = code - 614125*c
        c = code / 7225
        result(j+2) = table(c)
        if overflow > 2 then
          code = code - 7225*c
          c = code / 85
          result(j+3) = table(c)
    result

  def encodeZmqRange(raw: Array[Byte], i0: Int, iN: Int): Array[Byte] = encodeRangeWithTable(raw, i0, iN, encodeZmqTable)
  inline def encodeZmqRange(raw: Array[Byte], inline rg: Rg): Array[Byte] =
    val iv = Iv of rg
    encodeZmqRange(raw, iv.i0, iv.iN)
  inline def encodeZmqRange(raw: Array[Byte], inline v: Iv | PIv): Array[Byte] =
    val iv = Iv.of(v, raw)
    encodeZmqRange(raw, iv.i0, iv.iN)

  def encodeZmq(raw: Array[Byte]): Array[Byte] = encodeRangeWithTable(raw, 0, raw.length, encodeZmqTable)

  def encodeZmq(raw: Array[Int]): Array[Byte] = encodeRangeWithTable(raw.unpackBytes, 0, raw.length*4, encodeZmqTable)

  def stringEncodeZmq(raw: Array[Byte]): String = new String(encodeZmq(raw), ISO_8859_1)

  def encodeAsciiRange(raw: Array[Byte], i0: Int, iN: Int): Array[Byte] = encodeRangeWithTable(raw, i0, iN, encodeAsciiTable)
  inline def encodeAsciiRange(raw: Array[Byte], inline rg: Rg): Array[Byte] =
    val iv = Iv of rg
    encodeAsciiRange(raw, iv.i0, iv.iN)
  inline def encodeAsciiRange(raw: Array[Byte], inline v: Iv | PIv): Array[Byte] =
    val iv = Iv.of(v, raw)
    encodeAsciiRange(raw, iv.i0, iv.iN)

  def encodeAscii(raw: Array[Byte]): Array[Byte] =
    inline def table(i: Int) = (i + 33).toByte
    val l = (raw.length.toLong*5 + 3)/4
    if l > Int.MaxValue - 7 then throw new RuntimeException(s"Overly long Base85 encode: would require $l bytes")
    val result = new Array[Byte](l.toInt)
    var code = 0
    var i = 0
    var j = 0
    while i+3 < raw.length do
      code = Pack.I(raw(i+3), raw(i+2), raw(i+1), raw(i))
      i += 4
      var c85 = (code.u / 85.u).s
      result(j+4) = table(code - 85*c85)
      code = c85
      c85 = c85 / 85
      result(j+3) = table(code - 85*c85)
      code = c85
      c85 = c85 / 85
      result(j+2) = table(code - 85*c85)
      code = c85
      c85 = c85 / 85
      result(j+1) = table(code - 85*c85)
      result(j) = table(c85)
      j += 5
    if i < raw.length then
      val overflow = raw.length - i
      code = 0
      while i < raw.length do
        code = (code << 8) | (raw(i) & 0xFF)
        i += 1
      code = code << (8 * (4 - overflow))
      var c = (code.u / 52200625.u).s
      result(j) = table(c)
      code = code - 52200625*c
      c = code / 614125
      result(j+1) = table(c)
      if overflow > 1 then
        code = code - 614125*c
        c = code / 7225
        result(j+2) = table(c)
        if overflow > 2 then
          code = code - 7225*c
          c = code / 85
          result(j+3) = table(c)
    result

  def stringEncodeAscii(raw: Array[Byte]): String = new String(encodeAscii(raw), ISO_8859_1)


  private def decodeRangeWithTable(encoded: Array[Byte], i0: Int, iN: Int, table: Array[Byte]): Array[Byte] Or Err = Or.Ret:
    if i0 < 0 || iN > encoded.length then Err.break(s"Invalid decoding range: $i0 until $iN in array of length ${encoded.length}")
    val l = ((iN - i0).toLong*4)/5
    var a = new Array[Byte](l.toInt)
    var lead: Int = 0
    var rest: Int = 0
    var stored = 0
    var i = i0
    var j = 0
    val bmax = 9 + table.length - 1
    while i < iN do
      val b = encoded(i)
      val y =
        if b >= 9 && b <= bmax then table(b - 9)
        else -1
      if y < 0 then Err.break(s"Invalid Base85 character for this encoding: '${b.toChar}' at $i")
      else if y < 85 then
        if stored == 0 then
          lead = y
          stored = 1
        else
          rest = 85*rest + y
          stored += 1
          if stored == 5 then
            stored = 0
            if lead > 82 || (lead == 82 && rest > 14516045) then Err.break(s"Over-full Base85 number $lead*85^4 + $rest ending at $i")
            rest = lead*52200625 + rest
            a(j) =   ( rest >>> 24        ).toByte
            a(j+1) = ((rest >>> 16) & 0xFF).toByte
            a(j+2) = ((rest >>>  8) & 0xFF).toByte
            a(j+3) = ( rest         & 0xFF).toByte
            rest = 0
            j += 4
      i += 1
    if stored > 0 then
      val n = stored
      while stored < 5 do
        rest = rest*85 + 84
        stored += 1
      if lead > 82 || (lead == 82 && rest > 14516045) then Err.break(s"Over-full Base85 number $lead*85^4 + $rest at end of input")
      rest = lead*52200625 + rest
      a(j) = (rest >>> 24).toByte
      j += 1
      if n > 2 then
        a(j) = ((rest >>> 16) & 0xFF).toByte
        j += 1
        if n > 3 then
          a(j) = ((rest >>> 8) & 0xFF).toByte
          j += 1
    a.shrinkCopy(j)

  def decodeZmqRange(encoded: Array[Byte], i0: Int, iN: Int): Array[Byte] Or Err = decodeRangeWithTable(encoded, i0, iN, decodeZmqTable)
  inline def decodeZmqRange(encoded: Array[Byte], inline rg: Rg): Array[Byte] Or Err =
    val iv = Iv of rg
    decodeZmqRange(encoded, iv.i0, iv.iN)
  inline def decodeZmqRange(encoded: Array[Byte], inline v: Iv | PIv): Array[Byte] Or Err =
    val iv = Iv.of(v, encoded)
    decodeZmqRange(encoded, iv.i0, iv.iN)

  def decodeAsciiRange(encoded: Array[Byte], i0: Int, iN: Int): Array[Byte] Or Err = decodeRangeWithTable(encoded, i0, iN, decodeAsciiTable)
  inline def decodeAsciiRange(encoded: Array[Byte], inline rg: Rg): Array[Byte] Or Err =
    val iv = Iv of rg
    decodeAsciiRange(encoded, iv.i0, iv.iN)
  inline def decodeAsciiRange(encoded: Array[Byte], inline v: Iv | PIv): Array[Byte] Or Err =
    val iv = Iv.of(v, encoded)
    decodeAsciiRange(encoded, iv.i0, iv.iN)

  def decodeZmq(encoded: Array[Byte]): Array[Byte] Or Err = decodeRangeWithTable(encoded, 0, encoded.length, decodeZmqTable)

  def decodeAscii(encoded: Array[Byte]): Array[Byte] Or Err = decodeRangeWithTable(encoded, 0, encoded.length, decodeAsciiTable)

  private def decodeRangeWithTable(encoded: String, i0: Int, iN: Int, table: Array[Byte]): Array[Byte] Or Err = Or.Ret:
    if i0 < 0 || iN > encoded.length then Err.break(s"Invalid decoding range: $i0 until $iN in string of length ${encoded.length}")
    val l = ((iN - i0).toLong*4)/5
    var a = new Array[Byte](l.toInt)
    var lead: Int = 0
    var rest: Int = 0
    var stored = 0
    var i = i0
    var j = 0
    val bmax = 9 + table.length - 1
    while i < iN do
      val b = encoded.charAt(i)
      val y =
        if b >= 9 && b <= bmax then table(b - 9)
        else -1
      if y < 0 then Err.break(s"Invalid Base85 character for this encoding: '${b.toChar}' at $i")
      else if y < 85 then
        if stored == 0 then
          lead = y
          stored = 1
        else
          rest = 85*rest + y
          stored += 1
          if stored == 5 then
            stored = 0
            if lead > 82 || (lead == 82 && rest > 14516045) then Err.break(s"Over-full Base85 number $lead*85^4 + $rest ending at $i")
            rest = lead*52200625 + rest
            a(j) =   ( rest >>> 24        ).toByte
            a(j+1) = ((rest >>> 16) & 0xFF).toByte
            a(j+2) = ((rest >>>  8) & 0xFF).toByte
            a(j+3) = ( rest         & 0xFF).toByte
            rest = 0
            j += 4
      i += 1
    if stored > 0 then
      val n = stored
      while stored < 5 do
        rest = rest*85 + 84
        stored += 1
      if lead > 82 || (lead == 82 && rest > 14516045) then Err.break(s"Over-full Base85 number $lead*85^4 + $rest at end of input")
      rest = lead*52200625 + rest
      a(j) = (rest >>> 24).toByte
      j += 1
      if n > 2 then
        a(j) = ((rest >>> 16) & 0xFF).toByte
        j += 1
        if n > 3 then
          a(j) = ((rest >>> 8) & 0xFF).toByte
          j += 1
    a.shrinkCopy(j)

  def decodeZmqRange(encoded: String, i0: Int, iN: Int): Array[Byte] Or Err = decodeRangeWithTable(encoded, i0, iN, decodeZmqTable)
  inline def decodeZmqRange(encoded: String, inline rg: Rg): Array[Byte] Or Err =
    val iv = Iv of rg
    decodeZmqRange(encoded, iv.i0, iv.iN)
  inline def decodeZmqRange(encoded: String, inline v: Iv | PIv): Array[Byte] Or Err =
    val iv = Iv.of(v, encoded)
    decodeZmqRange(encoded, iv.i0, iv.iN)

  def decodeAsciiRange(encoded: String, i0: Int, iN: Int): Array[Byte] Or Err = decodeRangeWithTable(encoded, i0, iN, decodeAsciiTable)
  inline def decodeAsciiRange(encoded: String, inline rg: Rg): Array[Byte] Or Err =
    val iv = Iv of rg
    decodeAsciiRange(encoded, iv.i0, iv.iN)
  inline def decodeAsciiRange(encoded: String, inline v: Iv | PIv): Array[Byte] Or Err =
    val iv = Iv.of(v, encoded)
    decodeAsciiRange(encoded, iv.i0, iv.iN)

  def decodeZmq(encoded: String): Array[Byte] Or Err = decodeRangeWithTable(encoded, 0, encoded.length, decodeZmqTable)

  def decodeAscii(encoded: String): Array[Byte] Or Err = decodeRangeWithTable(encoded, 0, encoded.length, decodeAsciiTable)
}

extension (underlying: Array[Byte]) {
  inline def utf8 = new String(underlying, UTF_8)
  inline def ascii = new String(underlying, US_ASCII)
  inline def rawString = new String(underlying, ISO_8859_1)
  inline def iso8859_1 = new String(underlying, ISO_8859_1)
  inline def hasBOM: Boolean =
    underlying.length >= 3 && underlying(0) == -17 && underlying(1) == -69 && underlying(2) == -65
  inline def bomlessUtf8: String =
    if hasBOM then new String(underlying, 3, underlying.length - 3, UTF_8)
    else new String(underlying, UTF_8)

  inline def stringEncode64 = Base64.getUrlEncoder.encodeToString(underlying)
  inline def stringEncode64basic = Base64.getEncoder.encodeToString(underlying)
  inline def stringEncode64url = Base64.getUrlEncoder.encodeToString(underlying)
  inline def stringEncode64mime = Base64.getMimeEncoder.encodeToString(underlying)
  inline def stringEncode64lines = EioBase64.stringEncodeUrl(underlying, 76)

  inline def stringEncode85 = EioBase85.stringEncodeZmq(underlying)
  inline def stringEncode85zmq = EioBase85.stringEncodeZmq(underlying)
  inline def stringEncode85ascii = EioBase85.stringEncodeAscii(underlying)

  inline def encode64 = Base64.getUrlEncoder.encode(underlying)
  inline def encode64basic = Base64.getEncoder().encode(underlying)
  inline def encode64url = Base64.getUrlEncoder.encode(underlying)
  inline def encode64mime = Base64.getMimeEncoder().encode(underlying)
  inline def encode64lines = EioBase64.encodeUrl(underlying, 76)

  inline def decode64 = EioBase64.decode(underlying)
  inline def decode64basic = safe{ Base64.getDecoder().decode(underlying) }.mapAlt(t => Err(t.getMessage))
  inline def decode64url = safe{ Base64.getUrlDecoder().decode(underlying) }.mapAlt(t => Err(t.getMessage))
  inline def decode64mime = safe{ Base64.getMimeDecoder().decode(underlying) }.mapAlt(t => Err(t.getMessage))

  inline def encode85 = EioBase85.encodeZmq(underlying)
  inline def encode85zmq = EioBase85.encodeZmq(underlying)
  inline def encode85ascii = EioBase85.encodeAscii(underlying)

  inline def decode85 = EioBase85.decodeZmq(underlying)
  inline def decode85zmq = EioBase85.decodeZmq(underlying)
  inline def decode85ascii = EioBase85.decodeAscii(underlying)
}

extension (underlying: String) {
  inline def bytes: Array[Byte] = underlying.getBytes(UTF_8)

  inline def decode64 = EioBase64.decode(underlying)
  inline def decode64basic = safe{ Base64.getDecoder().decode(underlying) }.mapAlt(t => Err(t.getMessage))
  inline def decode64url = safe{ Base64.getUrlDecoder().decode(underlying) }.mapAlt(t => Err(t.getMessage))
  inline def decode64mime = safe{ Base64.getMimeDecoder().decode(underlying) }.mapAlt(t => Err(t.getMessage))

  inline def decode85 = EioBase85.decodeZmq(underlying)
  inline def decode85zmq = EioBase85.decodeZmq(underlying)
  inline def decode85ascii = EioBase85.decodeAscii(underlying)
}



extension (underlying: java.util.zip.ZipEntry) {
  def cleanName =
    val n = underlying.getName
    val i = n.indexOf('/')
    val j = n.indexOf('\\')
    if i < 0 && j > 0 then n.replace('\\', '/') else n

  def cleanPath =
    val n = underlying.cleanName
    val fs = FileSystems.getDefault
    fs.getPath(if fs.getSeparator == "/" then n else n.replace("/", fs.getSeparator))
}


//////////////////////////////////////////////////////////////
// Adaptors between different ways to hold/view binary data //
//////////////////////////////////////////////////////////////

trait CountedTransfer[S, T] {
  def apply(source: S, offset: Int)(target: T, index: Int)(count: Int): Int
}
object CountedTransfer {
  val array2buffer: CountedTransfer[Array[Byte], ByteBuffer] = new CountedTransfer[Array[Byte], ByteBuffer] {
    def apply(source: Array[Byte], offset: Int)(target: ByteBuffer, index: Int)(count: Int): Int =
      target.put(source, offset, count)
      count
  }
  val array2array: CountedTransfer[Array[Byte], Array[Byte]] = new CountedTransfer[Array[Byte], Array[Byte]] {
    def apply(source: Array[Byte], offset: Int)(target: Array[Byte], index: Int)(count: Int): Int =
      System.arraycopy(source, offset, target, index, count)
      count
  }
  val buffer2array: CountedTransfer[ByteBuffer, Array[Byte]] = new CountedTransfer[ByteBuffer, Array[Byte]] {
    def apply(source: ByteBuffer, offset: Int)(target: Array[Byte], index: Int)(count: Int): Int =
      source.get(target, index, count)
      count
  }
  val array2stream: CountedTransfer[Array[Byte], OutputStream] = new CountedTransfer[Array[Byte], OutputStream] {
    def apply(source: Array[Byte], offset: Int)(target: OutputStream, index: Int)(count: Int): Int =
      target.write(source, offset, count)
      count
  }
  val stream2array: CountedTransfer[InputStream, Array[Byte]] = new CountedTransfer[InputStream, Array[Byte]] {
    def apply(source: InputStream, offset: Int)(target: Array[Byte], index: Int)(count: Int): Int =
      source.read(target, index, count)
  }
  val array2channel: CountedTransfer[Array[Byte], WritableByteChannel] = new CountedTransfer[Array[Byte], WritableByteChannel] {
    def apply(source: Array[Byte], offset: Int)(target: WritableByteChannel, index: Int)(count: Int): Int =
      target.write(ByteBuffer.wrap(source, offset, count))
  }
  val channel2array: CountedTransfer[ReadableByteChannel, Array[Byte]] = new CountedTransfer[ReadableByteChannel, Array[Byte]] {
    def apply(source: ReadableByteChannel, offset: Int)(target: Array[Byte], index: Int)(count: Int): Int =
      source.read(ByteBuffer.wrap(target, index, count))
  }
  val array2seeker: CountedTransfer[Array[Byte], SeekableByteChannel] = new CountedTransfer[Array[Byte], SeekableByteChannel] {
    def apply(source: Array[Byte], offset: Int)(target: SeekableByteChannel, index: Int)(count: Int): Int =
      target.write(ByteBuffer.wrap(source, offset, count))
  }
  val seeker2array: CountedTransfer[SeekableByteChannel, Array[Byte]] = new CountedTransfer[SeekableByteChannel, Array[Byte]] {
    def apply(source: SeekableByteChannel, offset: Int)(target: Array[Byte], index: Int)(count: Int): Int =
      source.read(ByteBuffer.wrap(target, index, count))
  }
  val array2multi: CountedTransfer[Array[Byte], MultiArrayChannel] = new CountedTransfer[Array[Byte], MultiArrayChannel] {
    def apply(source: Array[Byte], offset: Int)(target: MultiArrayChannel, index: Int)(count: Int): Int =
      target.write(source, offset)(count)
  }
  val multi2array: CountedTransfer[MultiArrayChannel, Array[Byte]] = new CountedTransfer[MultiArrayChannel, Array[Byte]] {
    def apply(source: MultiArrayChannel, offset: Int)(target: Array[Byte], index: Int)(count: Int): Int =
      source.read(target, index)(count)
  }
}


extension (underlying: Array[Byte])
  inline def buffer = ByteBuffer.wrap(underlying).order(ByteOrder.LITTLE_ENDIAN)
  inline def input = new ByteArrayInputStream(underlying)
  inline def output = new FixedArrayOutputStream(underlying, 0, underlying.length)
  inline def readChannel = MultiArrayChannel.fixedBuffer(underlying, underlying.length)
  inline def writeChannel = MultiArrayChannel.fixedBuffer(underlying, 0)
  inline def growCopy = MultiArrayChannel.copyOf(underlying)
  inline def growCopyBy(chunk: Int) = MultiArrayChannel.chunked(chunk).copyOf(underlying)
  inline def growCopyMax(maxSize: Long) = MultiArrayChannel.copyOf(underlying, maxSize)
  inline def growCopyMaxBy(maxSize: Long, chunk: Int) = MultiArrayChannel.chunked(chunk).copyOf(underlying, maxSize)

final class FixedArrayOutputStream(val buffer: Array[Byte], val i0: Int, val iN: Int) extends OutputStream {
  private var isNowOpen = true
  private var index = i0
  private def checkOpen(): Unit = if !isNowOpen then throw new IOException("OutputStream is closed")

  override def close: Unit =
    isNowOpen = false
  def write(b: Int): Unit =
    checkOpen()
    if index >= iN then throw new IOException("FixedArrayOutputStream is full")
    buffer(index) = (b & 0xFF).toByte
    index += 1
  override def write(ab: Array[Byte], offset: Int, length: Int): Unit =
    if length > 0 then
      checkOpen()
      if index >= iN then throw new IOException("FixedArrayOutputStream is full")
      if iN - index < 0 || iN - index < length then throw new IOException("Buffer underflow for FixedArrayOutputStream")
      System.arraycopy(ab, offset, buffer, index, length)
      index += length
  override def write(ab: Array[Byte]) = write(ab, 0, ab.length)

  def count: Int = index - i0
}

final class MultiArrayChannel private (val growthLimit: Long, val maxChunkSize: Int, existing: Array[Array[Byte]])
extends SeekableByteChannel {
  private val initialLimit =
    var n = 0L
    existing.peek(){ ai => n += ai.length }
    n
  private var limit = initialLimit
  private var storage: Array[Array[Byte]] =
    if initialLimit > 0 then
      var emptyCount = 0
      existing.peek(){ ai => if ai.length == 0 then emptyCount += 1 }
      val a = new Array[Array[Byte]](existing.length - emptyCount)
      var j = 0
      existing.peek(){ ai => 
        if ai.length > 0 then
          a(j) = ai
          j += 1
      }
      a
    else Array(new Array[Byte](256))
  private var active: Array[Byte] = storage(0)
  private var iactive: Int = 0
  private var zero: Long = 0L
  private var allocated: Long = if initialLimit > 0 then initialLimit else 256L
  private var index: Long = 0L
  private var isNowOpen = true

  private def checkOpen(): Unit =
    if !isNowOpen then throw new java.nio.channels.ClosedChannelException()
  private def zeroToIndexOrEnd(): Unit =
    if (active ne null) && limit >= zero then active.fillRange((limit - zero).toInt, (index - zero).toInt)(0)
    else
      if (active ne null) && index > zero then active.fillRange(0, (index - zero).toInt)(0)
      var z = zero
      var i = iactive
      while z > limit && i > 0 do
        i -= 1
        val a = storage(i)
        z -= a.length
        if limit > z then a.fillRange((limit - z).toInt, a.length)(0)
        else a.fill(0)
  private def ensureExtraCapacity(m: Int): Unit =
    var needed = (m + index) - allocated
    if storage(End).length < maxChunkSize then
      val h = storage(End).length
      val most = initialLimit + growthLimit - allocated
      val expand: Long = if h < 128 then 256 else if storage.length > 3 then maxChunkSize else 2L*h
      val l = ((most min expand) min maxChunkSize).toInt
      val extra = l - h
      if extra > 0 then
        needed -= extra
        storage(End) = storage(End).copyToSize(l)
        allocated += extra
        if iactive >= storage.length then
          if index >= allocated then zero = allocated
          else
            active = storage(End)
            zero = allocated - active.length
        else if iactive == storage.length - 1 then
          active = storage(End)
    while needed > 0 && allocated < initialLimit + growthLimit do
      val most = initialLimit + growthLimit - allocated
      val expand: Long = if storage.length > 3 then maxChunkSize else if needed < 128 then 256 else 2L*needed
      val l = ((most min expand) min maxChunkSize).toInt
      storage = storage.copyToSize(storage.length + 1)
      storage(End) = new Array[Byte](l)
      allocated += l
      needed -= l
      if iactive >= storage.length - 1 then
        if index >= allocated then
          iactive = storage.length
          zero = allocated
        else
          iactive = storage.length - 1
          active = storage(iactive)
          zero = allocated - active.length

  def isOpen: Boolean = isNowOpen
  def close(): Unit = isNowOpen = false

  def position: Long = index
  def position(p: Long): this.type =
    checkOpen()
    if p < 0 then throw new IllegalArgumentException(s"Negative channel position $p")
    else if p > growthLimit + initialLimit then throw new IllegalArgumentException(s"Position $p exceeds maximum possible channel capacity")
    else
      if p >= allocated then
        zero = allocated
        active = null
        iactive = storage.length
      else if p < zero then
        while iactive > 0 && p < zero do
          iactive -= 1
          zero -= storage(iactive).length
        active = storage(iactive)
      else
        while iactive < storage.length && p >= zero + active.length do
          zero += active.length
          iactive += 1
          if iactive < storage.length then active = storage(iactive)
      index = p
    this
  def size: Long = limit
  def truncate(p: Long): this.type =
    checkOpen()
    if p < 0 then throw new IllegalArgumentException(s"Cannot truncate channel to negative size $p")
    if p < limit then
      if p < index then
        position(p)
      limit = p
    this

  def unsafeDirectBufferAccess: RotatingBuffer[Byte] = new RotatingBuffer[Byte] {
    private var i = 0
    private var consumed = 0L
    private val storageAtCreation = storage
    private val limitAtCreation = limit
    def rotate(buffer: Mu[Array[Byte] Or Unit], interval: Mu.T[Iv]): Unit =
      if consumed >= limitAtCreation then
        interval.value = Iv(0, 0)
        buffer.value = Alt.unit
      else
        val a = storage(i)
        val iv = if limitAtCreation - consumed < a.length then Iv(0, (limitAtCreation - consumed).toInt) else Iv.of(a)
        consumed += iv.length
        interval.value = iv
        buffer.value = Is(a)
    override def recycling: Boolean = false
  }

  def readTo[T](target: T, start: Int)(count: Int)(using transfer: CountedTransfer[Array[Byte], T]): Int =
    if count <= 0 then 0
    else
      checkOpen()
      if index >= limit then -1
      else
        var m = count
        var n = 0
        while index < limit && m > 0 do
          val i = (index - zero).toInt
          val r = (limit - index).toInt min (active.length - i)
          var h = transfer(active, i)(target, n + start)(if m < r then m else r)
          if h < 0 then
            h = 0
            if m == count then n = -1
          index += h
          if i + h == active.length then
            zero += active.length
            iactive += 1
            active = if iactive < storage.length then storage(iactive) else null
          n += h
          m -= h
          if h == 0 then m = 0
        n
  private def readAsMuchAsPossible[T](target: T)(using transfer: CountedTransfer[Array[Byte], T]): Long =
    var n: Long = 0
    var m = 0
    var cycles = 0L
    while m >= 0 && cycles <= n do
      val count =
        if index >= limit || iactive >= storage.length then 1
        else availableToRead.clampToInt min active.length - (index - zero).toInt
      m = readTo(target, 0)(count)
      if m > 0 then n += m
      if m >= 0 then cycles += 1
    if cycles == 0 then -1L else n
  def read(buffer: Array[Byte]): Int =
    readTo(buffer, 0)(buffer.length)(using CountedTransfer.array2array)
  def read(buffer: Array[Byte], offset: Int)(count: Int): Int =
    readTo(buffer, offset)(count)(using CountedTransfer.array2array)
  def read(buffer: ByteBuffer): Int =
    readTo[ByteBuffer](buffer, 0)(buffer.remaining)(using CountedTransfer.array2buffer)
  def read(target: OutputStream): Long =
    readAsMuchAsPossible(target)(using CountedTransfer.array2stream)
  def read(target: OutputStream)(count: Int): Int =
    readTo(target, 0)(count)(using CountedTransfer.array2stream)
  def read(target: WritableByteChannel): Long = target match
    case that: MultiArrayChannel => readAsMuchAsPossible(that  )(using CountedTransfer.array2multi)
    case _                       => readAsMuchAsPossible(target)(using CountedTransfer.array2channel)
  def read(target: WritableByteChannel)(count: Int): Int = target match
    case that: MultiArrayChannel => readTo(that,   0)(count)(using CountedTransfer.array2multi)
    case _                       => readTo(target, 0)(count)(using CountedTransfer.array2channel)
  def writeFrom[S](source: S, start: Int)(count: Int)(using transfer: CountedTransfer[S, Array[Byte]]): Int =
    if count <= 0 then 0
    else
      checkOpen()
      if index >= initialLimit + growthLimit then return -1
      var n = 0
      var m = count
      if limit < index then zeroToIndexOrEnd()
      if allocated - index < m then ensureExtraCapacity(m)
      while index < allocated && m > 0 do
        val i = (index - zero).toInt
        val r = active.length - i
        var h = transfer(source, start + n)(active, i)(if m < r then m else r)
        if h < 0 then
          h = 0
          if m == count then n = -1
        index += h
        if index > limit then limit = index
        if h == r then
          zero += active.length
          iactive += 1
          active = if iactive < storage.length then storage(iactive) else null
        n += h
        m -= h
        if h == 0 then m = 0
      n
  def writeAsMuchAsPossible[S](source: S)(available: S => Long)(using transfer: CountedTransfer[S, Array[Byte]]): Long =
    var n: Long = 0
    var attempt = 4096 min maxChunkSize
    var m = 0
    var cycles = 0L
    while m >= 0 do
      val a = available(source)
      val count =
        if a > 0 && !(cycles > 16 && (n >> 4) < cycles) then a.clampToInt
        else if index >= allocated then attempt
        else attempt min active.length - (index - zero).toInt
      m = writeFrom(source, 0)(count)
      if m > 0 then
        n += m
        if n >= attempt then
          attempt = if attempt > (maxChunkSize >> 1) then maxChunkSize else attempt * 2
        if index >= initialLimit + growthLimit then m = -1
        cycles += 1
      else if m == 0 then cycles += 1
    if cycles == 0 then -1L else n
  def write(buffer: Array[Byte]): Int =
    writeFrom[Array[Byte]](buffer, 0)(buffer.length)(using CountedTransfer.array2array)
  def write(buffer: Array[Byte], offset: Int)(count: Int): Int =
    writeFrom[Array[Byte]](buffer, offset)(count)(using CountedTransfer.array2array)
  def write(buffer: ByteBuffer): Int =
    writeFrom[ByteBuffer](buffer, 0)(buffer.remaining)(using CountedTransfer.buffer2array)
  def write(source: InputStream): Long =
    writeAsMuchAsPossible(source)(_.available)(using CountedTransfer.stream2array)
  def write(source: InputStream)(count: Int): Int =
    writeFrom(source, 0)(count)(using CountedTransfer.stream2array)
  def write(source: ReadableByteChannel): Long = source match
    case that: MultiArrayChannel => writeAsMuchAsPossible(that  )(_.availableToRead       )(using CountedTransfer.multi2array)
    case sb: SeekableByteChannel => writeAsMuchAsPossible(sb    )(c => c.size - c.position)(using CountedTransfer.seeker2array)
    case _                       => writeAsMuchAsPossible(source)(_ => 0                  )(using CountedTransfer.channel2array)

  private def compactImpl(before: Boolean, after: Boolean): Unit =
    val high = index max limit
    val low = index min limit
    var li = iactive
    var lz = zero
    while low < lz && li > 0 do
      li -= 1
      lz -= storage(li).length
    var hi = iactive
    var hz = zero
    while hi < storage.length && high >= hz + storage(hi).length do
      hz += storage(hi).length
      hi += 1
    if hi > li && before && li > 0 then
      storage = storage.copyOfRange(li, storage.length)
      iactive -= li
      zero -= lz
      index -= lz
      limit -= lz
      allocated -= lz
      hi -= li
      hz -= lz
      li = 0
      lz = 0
    if hi > li && after && hi+1 < storage.length then
      val dropped = allocated - (hz + storage(hi).length)
      storage = storage.copyOfRange(0, hi+1)
      allocated -= dropped
    if storage.length == 1 && before then
      if limit == low then
        index -= limit
        limit = 0
      else if index >= limit-index then
        System.arraycopy(active, index.toInt, active, 0, (limit - index).toInt)
        limit -= index
        index = 0
  def releasePassed(): Long =
    val i = index
    compactImpl(true, false)
    i - index
  def releaseUnused(): Unit = compactImpl(false, true)
  def compact(): this.type =
    compactImpl(true, true)
    this

  def availableToRead: Long =
    if !isNowOpen then -1L
    else if index > limit then 0L else limit - index
  def bufferAvailableToWrite: Long =
    if !isNowOpen then -1L
    else if index > allocated then 0L else allocated - index
  def maxAvailableToWrite: Long =
    if !isNowOpen then -1L
    else initialLimit + growthLimit - index
  def canWrite(buffer: ByteBuffer): Boolean =
    buffer.remaining == 0 || (maxAvailableToWrite >= buffer.remaining)
  def getBytes: Array[Byte] Or Array[Byte] =
    if isNowOpen then throw new IllegalArgumentException("Cannot get bytes from open channel")
    if storage eq null then throw new IllegalArgumentException("Buffers already detatched")
    if limit == 0 then Is(MultiArrayChannel.noBytes)
    else
      if storage.length == 1 || limit <= storage(0).length then Is(storage(0).copyToSize(limit.toInt))
      else
        val a = new Array[Byte](if limit > Int.MaxValue - 7 then Int.MaxValue - 7 else limit.toInt)
        var m = 0
        var i = 0
        while m < a.length do
          val ai = storage(i)
          val h = (a.length - m) min ai.length
          System.arraycopy(ai, 0, a, m, h)
          m += h
          i += 1
        a.isIf(_.length == limit)
  def detatchBuffers(): Array[Array[Byte]] =
    if isNowOpen then throw new IllegalArgumentException("Cannot detatch buffers from open channel")
    if storage eq null then throw new IllegalArgumentException("Buffers already detatched")
    if limit <= 0 then
      storage = null
      Array.empty[Array[Byte]]
    else if limit == allocated then
      val ans = storage
      storage = null
      ans
    else if index == limit then
      val a = new Array[Array[Byte]](iactive + 1)
      nFor(iactive){ i => a(i) = storage(i) }
      a(iactive) = active.shrinkCopy((index - zero).toInt)
      storage = null
      a
    else
      var nb = limit
      var na = 0
      while na < storage.length && nb > 0 do
        nb -= storage(na).length
        na += 1
      val a = new Array[Array[Byte]](na)
      nb = limit
      nFor(na - 1){ i => val si = storage(i); nb -= si.length; a(i) = si }
      a(na - 1) = storage(na - 1).shrinkCopy(nb.toInt)
      storage = null
      a
}
object MultiArrayChannel {
  val noInitialArrays = Array.empty[Array[Byte]]
  val noBytes = Array.empty[Byte]

  inline val defaultMaxSize = 0x200000000L
  inline val defaultChunkSize = 0x20000000

  private def make(chunk: Int, xs: Array[Byte]) =
    new MultiArrayChannel(defaultMaxSize - xs.length, chunk, if xs == null || xs.length == 0 then noInitialArrays else Array(xs))
  private def make(limit: Long, chunk: Int, xs: Array[Byte]) =
    new MultiArrayChannel(0L max limit, chunk, if xs == null || xs.length == 0 then noInitialArrays else Array(xs))

  def empty() = make(defaultChunkSize, null)
  def empty(growthLimit: Long) = make(growthLimit, defaultChunkSize, null)

  def fixedBuffer(b: Array[Byte], readableLength: Int) =
    val sgbc = new MultiArrayChannel(0L, b.length, Array(b))
    if readableLength < 0 then sgbc.truncate(0L)
    else if readableLength < b.length then sgbc.truncate(readableLength)
    sgbc

  def of(b: Array[Byte]) = make(defaultChunkSize, b)
  def of(b: Array[Byte], growthLimit: Long) = make(growthLimit, defaultChunkSize, b)

  def copyOf(b: Array[Byte]) = make(defaultChunkSize, b.copy)
  def copyOf(b: Array[Byte], growthLimit: Long) = make(growthLimit, defaultChunkSize, b.copy)
  def copyOf(bb: ByteBuffer) = make(defaultChunkSize, bb.getBytes)
  def copyOf(bb: ByteBuffer, growthLimit: Long) = make(growthLimit, defaultChunkSize, bb.getBytes)

  def chunked(size: Int): kse.eio.MultiArrayChannel.ChannelChunkSize = ChannelChunkSize(size)

  opaque type ChannelChunkSize = Int
  object ChannelChunkSize {
    inline def apply(size: Int): ChannelChunkSize = size

    extension (chunkSize: ChannelChunkSize)
      def value: Int = (chunkSize: Int) max 0x8

    extension (chunkSize: kse.eio.MultiArrayChannel.ChannelChunkSize) {
      def empty() = make(chunkSize.value, null)
      def empty(growthLimit: Long) = make(growthLimit, chunkSize.value, null)

      def of(b: Array[Byte]) = make(chunkSize.value, b)
      def of(b: Array[Byte], growthLimit: Long) = make(growthLimit, chunkSize.value, b)

      def copyOf(b: Array[Byte]) = make(chunkSize.value, b.copy)
      def copyOf(b: Array[Byte], growthLimit: Long) = make(growthLimit, chunkSize.value, b)
      def copyOf(bb: ByteBuffer) = make(chunkSize.value, bb.getBytes)
      def copyOf(bb: ByteBuffer, growthLimit: Long) = make(growthLimit, chunkSize.value, bb.getBytes)
    }
  }
}



final class ByteBufferOutputStream(val buffer: ByteBuffer) extends OutputStream {
  private var isNowOpen = true
  private def checkOpen(): Unit = if !isNowOpen then throw new IOException("OutputStream is closed")

  override def close(): Unit =
    isNowOpen = false
  def write(b: Int): Unit =
    checkOpen()
    buffer put (b & 0xFF).toByte
  override def write(b: Array[Byte]): Unit =
    checkOpen()
    buffer put b
  override def write(b: Array[Byte], off: Int, len: Int): Unit =
    checkOpen()
    buffer.put(b, off, len)
}

final class ByteBufferInputStream(val buffer: ByteBuffer) extends InputStream {
  private var isNowOpen = true
  private def checkOpen(): Unit = if !isNowOpen then throw new IOException("InputStream is closed")

  override def available(): Int =
    checkOpen()
    buffer.remaining
  override def close(): Unit =
    isNowOpen = false
  override def mark(readlimit: Int): Unit =
    checkOpen()
    buffer.mark()
  override def markSupported = true
  def read(): Int =
    checkOpen()
    buffer.get & 0xFF
  override def read(b: Array[Byte]): Int = read(b, 0, b.length)
  override def read(b: Array[Byte], offset: Int, len: Int): Int =
    if len <= 0 then 0
    else
      checkOpen()
      val n = buffer.remaining
      if n < len then
        if n == 0 then -1
        else
          buffer.get(b, offset, n)
          n
      else
        buffer.get(b, offset, len)
        len
  override def reset: Unit =
    checkOpen()
    buffer.reset
  override def skip(n: Long): Long =
    if n < 0 then 0L
    else
      checkOpen()
      if n > buffer.remaining then
        val i = buffer.remaining
        buffer.position(buffer.limit)
        i
      else
        buffer.position((n + buffer.position).toInt)
        n
}

object ByteBufferCompanion {
  val emptyBytes = Array.empty[Byte]
}

extension (buffer: ByteBuffer)
  inline def output = new ByteBufferOutputStream(buffer)
  inline def input = new ByteBufferInputStream(buffer)
  def getBytes: Array[Byte] =
    if buffer.remaining == 0 then ByteBufferCompanion.emptyBytes
    else
      val a = new Array[Byte](buffer.remaining)
      buffer.get(a)
      a



final class RandomAccessFileOutputStream(val raf: RandomAccessFile) extends OutputStream {
  override def close(): Unit = raf.close()
  override def write(b: Array[Byte]): Unit = raf.write(b)
  override def write(b: Array[Byte], off: Int, len: Int): Unit = raf.write(b, off, len)
  def write(b: Int): Unit = raf.writeByte(b)
}

final class RandomAccessFileInputStream(val raf: RandomAccessFile) extends InputStream {
  private var markedPosition: Long = -1L

  override def available(): Int = 
    (raf.length() - raf.getFilePointer()).clamp(0, Int.MaxValue).toInt
  override def close(): Unit = raf.close()
  override def mark(readlimit: Int): Unit =
    markedPosition = raf.getFilePointer()
  override def markSupported = true
  def read(): Int = raf.read()
  override def read(b: Array[Byte]): Int = raf.read(b)
  override def read(b: Array[Byte], offset: Int, len: Int): Int = raf.read(b, offset, len)
  override def reset: Unit =
    if markedPosition >= 0 then
      val l = raf.length()
      if markedPosition > l then throw new IOException(s"Reset to $markedPosition in file shortened to $l")
      else raf.seek(markedPosition)
    else throw new IOException("Reset on unmarked stream")
  override def skip(n: Long): Long =
    if n < 0 then 0L
    else
      val l = raf.length()
      val p = raf.getFilePointer()
      if n > l - p then
        raf.seek(l)
        l - p
      else
        raf.seek(p+n)
        n
}

extension (raf: RandomAccessFile)
  inline def output = new RandomAccessFileOutputStream(raf)
  inline def input = new RandomAccessFileInputStream(raf)



final class SeekableByteChannelOutputStream(val sbc: SeekableByteChannel) extends OutputStream {
  private val oneByte = ByteBuffer.wrap(new Array[Byte](1))

  override def close(): Unit = sbc.close()
  override def write(b: Array[Byte]): Unit =
    val bb = ByteBuffer wrap b
    val n = sbc.write(bb)
    if (n < b.length) throw new IOException("Tried to write ${b.length} bytes but only could write $n")
  override def write(b: Array[Byte], off: Int, len: Int): Unit =
    if (len > 0) {
      val bb = ByteBuffer wrap b
      bb.position(off)
      bb.limit(off +# len)
      val n = sbc.write(bb)
      if (n < len) throw new IOException("Tried to write ${b.length} bytes but could only write $n")
    }
  override def write(b: Int): Unit = synchronized {
    oneByte.clear
    oneByte put b.toByte
    oneByte.flip
    val n = sbc.write(oneByte)
    if (n != 1) throw new IOException("Tried to write a byte but couldn't")
  }
}

final class SeekableByteChannelInputStream(val sbc: SeekableByteChannel) extends InputStream {
  private var markedPosition: Long = -1L
  private val oneByte = ByteBuffer.wrap(new Array[Byte](1))

  private def atEndException(): Nothing = throw new IOException("Read at end of SeekableByteChannel")
  private def noProgressException(): Nothing = throw new IOException("Read failed")
  private def checkReadSize(i: Int): Int =
    if i < 0 then atEndException()
    else if i == 0 then noProgressException()
    else i

  override def available(): Int = 
    (sbc.size - sbc.position).clamp(0, Int.MaxValue).toInt
  override def close(): Unit = sbc.close()
  override def mark(readlimit: Int): Unit =
    markedPosition = sbc.position
  override def markSupported = true
  def read(): Int =
    oneByte.clear
    checkReadSize( sbc.read(oneByte) )
    oneByte.flip
    oneByte.get & 0xFF
  override def read(b: Array[Byte]): Int =
    if b.length == 0 then 0
    else
      val bb = ByteBuffer wrap b
      checkReadSize( sbc.read(bb) )
  override def read(b: Array[Byte], offset: Int, len: Int): Int =
    if len <= 0 then 0
    else
      val bb = ByteBuffer wrap b
      bb.position(offset)
      bb.limit(offset +# len)
      checkReadSize( sbc.read(bb) )
  override def reset: Unit =
    if markedPosition >= 0 then
      val l = sbc.size
      if sbc.position > l then throw new IOException(s"Reset to $markedPosition in SeekableByteChannel shortened to $l")
      else sbc.position(markedPosition)
    else throw new IOException("Reset on unmarked stream")
  override def skip(n: Long): Long =
    if n < 0 then 0L
    else
      val l = sbc.size - sbc.position 
      if n > l then
        sbc.position(sbc.size)
        l
      else
        sbc.position(sbc.position + n)
        n
}

final class SeekableByteChannelRotatingBuffer(val sbc: SeekableByteChannel, bufferSize: Int = 16384) extends RotatingBuffer[Byte] {
  private var consumed = 0L
  private var totalSize = sbc.size - sbc.position
  def rotate(buffer: Mu[Array[Byte] Or Unit], interval: Mu.T[Iv]): Unit =
    val remaining = totalSize - consumed
    if remaining <= 0 || !sbc.isOpen then
      interval.value = Iv(0, 0)
      buffer.value = Alt.unit
    else
      val b = buffer.value.
        flatMap(buf => if buf.length >= 1024 || buf.length >= remaining then Is(buf) else Alt.unit).
        getOrElse{ _ =>
          var n = math.min(bufferSize, Int.MaxValue - 7)
          if remaining <= n then n = remaining.toInt
          else if (remaining + 7)/2 < n then n = ((remaining + 7)/2).toInt
          new Array[Byte](n)        
        }
      val k = sbc.read(ByteBuffer wrap b)
      if k < 0 then
        totalSize = consumed
        interval.value = Iv(0, 0)
        buffer.value = Alt.unit
      else
        consumed += k
        interval.value = Iv(0, k)
        buffer.value = Is(b)
}

extension (sbc: SeekableByteChannel)
  inline def output = new SeekableByteChannelOutputStream(sbc)
  inline def input = new SeekableByteChannelInputStream(sbc)
  inline def rotating() = new SeekableByteChannelRotatingBuffer(sbc, 16384)
  inline def rotating(bufferSize: Int) = new SeekableByteChannelRotatingBuffer(sbc, bufferSize)


final class InputStreamByteChannel(input: InputStream) extends ReadableByteChannel {
  private val certainlyClosed = java.util.concurrent.atomic.AtomicBoolean(false)
  private val backingBuffer: java.util.concurrent.atomic.AtomicReference[Array[Byte]] = java.util.concurrent.atomic.AtomicReference(null)
  def close =
    input.close
    certainlyClosed.set(true)
  def isOpen = !certainlyClosed.get
  def read(bb: ByteBuffer): Int =
    if certainlyClosed.get then -1
    else
      if bb.hasArray then
        val a = bb.array
        val pos = bb.position
        val avail = bb.remaining
        val n = input.read(a, pos, avail)
        if n > 0 then bb.position(pos + n)
        if n < 0 then certainlyClosed.set(true)
        n
      else
        var a = backingBuffer.get
        if (a eq null) || (a.length < bb.remaining) then
          a = new Array[Byte](bb.remaining max 16)
          backingBuffer.set(a)
        val n = input.read(a, 0, bb.remaining)
        if n > 0 then bb.put(a, 0, n)
        if n < 0 then certainlyClosed.set(true)
        n
}

final class InputStreamRotatingBuffer(input: InputStream, bufferSize: Int = 16384) extends RotatingBuffer[Byte] {
  private var completed = false
  private var believeAvailable = true
  def rotate(buffer: Mu[Array[Byte] Or Unit], interval: Mu.T[Iv]): Unit =
    if completed then
      interval.value = Iv(0, 0)
      buffer.value = Alt.unit
    else
      val av =
        if believeAvailable then
          believeAvailable = false
          input.available()
        else 0
      val b = buffer.value.
        flatMap(buf => if buf.length >= 1024 || (av > 0 && buf.length >= av) then Is(buf) else Alt.unit).
        getOrElse{ _ =>
          var n = math.min(bufferSize, Int.MaxValue - 7)
          if av > 0 && av < n then n = av.toInt
          new Array[Byte](n)
        }
      val k = input.read(b)
      if k < 0 then
        completed = true
        interval.value = Iv(0, 0)
        buffer.value = Alt.unit
      else
        interval.value = Iv(0, k)
        buffer.value = Is(b)
}

extension (is: InputStream)
  inline def channel = new InputStreamByteChannel(is)
  inline def rotating() = new InputStreamRotatingBuffer(is, 16384)
  inline def rotating(bufferSize: Int) = new InputStreamRotatingBuffer(is, bufferSize)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy