//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 16 Mar 06 Brian Frank Creation
//
**
** Buf is used to model a block of bytes with random access. Buf is
** typically backed by a block of memory, but can also be backed by
** a file:
** - `Buf.make`: backed by RAM
** - `File.open`: backed by random access file
** - `File.mmap`: backed by memory mapped file
**
** Buf provides an `InStream` and `OutStream` to read and write into
** the buffer using a configurable position accessed via `Buf.pos`
** and `Buf.seek`.
**
** When using an InStream, bytes are read starting at pos where pos
** is advanced after each read. The end of stream is reached when pos
** reaches size. When using the OutStream, bytes are written starting at pos
** with pos advanced after each write. If pos is less then size then
** the existing bytes are rewritten and size is not advanced, otherwise
** the buffer is automatically grown and size is advanced as bytes are
** appended. It is common to write bytes into the buffer using the
** OutStream, then call `Buf.flip` to prepare the buffer to be used for reading.
**
** Memory bufs may be made immutable by calling `Obj.toImmutable`. When a
** buf is made immutable, the original buffer's data is cleared (to avoid
** copying the backing array). All write operations on an immutable buf will
** raise a 'ReadonlyErr'. Reads may be performed by acquiring an InStream via
** the `in` method. However, read operations which require a mutable Buf
** pos will raise ReadonlyErr too, including methods such as `seek` or `read`.
** Use `dup` to copy an immutable buf back into a mutable buf.
**
class Buf
{
//////////////////////////////////////////////////////////////////////////
// Factory
//////////////////////////////////////////////////////////////////////////
**
** Allocate a byte buffer in RAM with the initial given capacity.
**
static new make(Int capacity := 1024)
**
** Generate a random series of bytes.
**
** Example:
** Buf.random(8).toHex => "d548b54989028b90"
**
static Buf random(Int size)
**
** Buf cannot be subclassed outside of sys since we do
** much optimization under the covers in Java and C#.
**
internal new internalMake()
//////////////////////////////////////////////////////////////////////////
// Identity
//////////////////////////////////////////////////////////////////////////
**
** Buf equality is based on reference equality using the === operator.
**
override Bool equals(Obj? that)
**
** Return if the buffer contents are the same size and same bytes.
** Note this could be an extremely expensive call for non-memory buffers.
**
Bool bytesEqual(Buf that)
**
** Return string summary of the buffer.
**
override Str toStr()
//////////////////////////////////////////////////////////////////////////
// Access
//////////////////////////////////////////////////////////////////////////
**
** Return if size() == 0.
**
Bool isEmpty()
**
** Return the total number of bytes in the buffer. If the size is
** set greater than capacity then the buffer's capacity is automatically
** grown, otherwise capacity remains the same. Setting size does not
** actually change any bytes in the buffer. A mmap buffer can never
** be increased from its initial size.
**
Int size
**
** The number of bytes this buffer can hold without allocating more
** memory. Capacity is always greater or equal to size. If adding a
** large number of bytes, it may be more efficient to manually set
** capacity. See the `trim` method to automatically set capacity to
** size. Throw ArgErr if attempting to set capacity less than size.
** This method is ignored on a file buffer, and unsupported on mmap.
**
Int capacity
**
** Return the current position for the next read or write. The
** position is always between 0 and `size`. If pos is less than
** size then future writes will rewrite the existing bytes without
** growing size. Change the position with `seek`.
**
Int pos()
**
** Return the remaining number of bytes to read: size-pos.
**
Int remaining()
**
** Return if more bytes are available to read: remaining() > 0.
**
Bool more()
**
** Set the current position to the specified byte offset. A
** negative index may be used to access from the end of the buffer.
** For example seek(-1) is translated into seek(size-1).
** Return this.
**
This seek(Int pos)
**
** Flip a buffer from write-mode to read-mode. This method sets
** total size to current position, and position to 0. Return this.
**
This flip()
**
** Get the byte at the specified absolute index. A negative index
** may be used to access from the end of the buffer. For example
** get(-1) is translated into get(size-1). This method accesses
** the buffer absolutely independent of current position. The get
** method is accessed via the [] shortcut operator. Throw IndexErr
** if index out of range.
**
@Operator Int get(Int index)
**
** Return a new buffer containing the bytes in the specified absolute
** range. Negative indexes may be used to access from the end of
** the buf. This method accesses the buffer absolutely independent
** of current position. This method is accessed via the [] operator.
** Throw IndexErr if range illegal.
**
** Examples:
** buf := Buf.make
** buf.write(0xaa).write(0xbb).write(0xcc).write(0xdd)
** buf[0..2] => 0x[aabbcc]
** buf[3..3] => 0x[dd]
** buf[-2..-1] => 0x[ccdd]
** buf[0..<2] => 0x[aabb]
** buf[1..-2] => 0x[bbcc]
**
@Operator Buf getRange(Range range)
**
** Create a new buffer in memory which deeply clones this buffer.
** The resulting buf is read/write.
**
Buf dup()
//////////////////////////////////////////////////////////////////////////
// Modification
//////////////////////////////////////////////////////////////////////////
**
** Set is used to overwrite the byte at the specified index. A
** negative index may be used to access an index from the end of the
** buffer. The set method is accessed via the []= shortcut operator.
** Return this. Throw IndexErr if index is out of range.
**
@Operator This set(Int index, Int byte)
**
** Read the buffer for a fresh read by reseting the buffer's pos
** and size to zero. The buffer's capacity remains the same.
** Return this.
**
This clear()
**
** Trim the capacity such that the underlying storage is optimized
** for the current size. Return this.
**
This trim()
**
** If this buffer is backed by a file, then close it. If a memory
** buffer then do nothing. This method is guaranteed to never
** throw an IOErr. Return true if the buffer was closed
** successfully or false if closed abnormally.
**
Bool close()
**
** Obsolete call to `sync`. In the future this method may be
** relaxed to flush only memory buffers, but not force an fsync.
**
@Deprecated { msg = "Use sync" }
This flush()
**
** If this Buf is backed by a file, then fsync all changes to
** the storage device. Throw IOErr on error. Return this.
**
This sync()
**
** Byte order mode for both OutStream and InStream.
** Default is `Endian.big` (network byte order).
**
Endian endian
**
** Character set for both the OutStream and InStream.
**
Charset charset
**
** Write the specified byte to the end of the buffer using given count.
**
** Examples:
** Buf().fill(0xff, 4) => 0xffffffff
**
This fill(Int byte, Int times)
//////////////////////////////////////////////////////////////////////////
// OutStream
//////////////////////////////////////////////////////////////////////////
**
** Get the OutStream which writes to this buffer.
** This method always returns the same instance.
** If this buffer is backed by a file, then 'out.close'
** will not close the file - you must use `Buf.close`.
**
OutStream out()
**
** Convenience for [out.write]`OutStream.write`
** Return this.
**
This write(Int byte)
**
** Convenience for [out.writeBuf]`OutStream.writeBuf`
** Return this.
**
This writeBuf(Buf buf, Int n := buf.remaining)
**
** Convenience for [out.writeI2]`OutStream.writeI2`
** Return this.
**
This writeI2(Int n)
**
** Convenience for [out.writeI4]`OutStream.writeI4`
** Return this.
**
This writeI4(Int n)
**
** Convenience for [out.writeI8]`OutStream.writeI8`
** Return this.
**
This writeI8(Int n)
**
** Convenience for [out.writeF4]`OutStream.writeF4`
** Return this.
**
This writeF4(Float r)
**
** Convenience for [out.writeF8]`OutStream.writeF8`
** Return this.
**
This writeF8(Float r)
**
** Convenience for [out.writeDecimal]`OutStream.writeDecimal`
** Return this.
**
This writeDecimal(Decimal d)
**
** Convenience for [out.writeBool]`OutStream.writeBool`
** Return this.
**
This writeBool(Bool b)
**
** Convenience for [out.writeUtf]`OutStream.writeUtf`
** Return this.
**
This writeUtf(Str s)
**
** Convenience for [out.writeChar]`OutStream.writeChar`
** Return this.
**
This writeChar(Int char)
**
** Convenience for [out.writeChars]`OutStream.writeChars`
** Return this.
**
This writeChars(Str str, Int off := 0, Int len := str.size-off)
**
** Convenience for [out.print]`OutStream.print`
** Return this.
**
This print(Obj? s)
**
** Convenience for [out.printLine]`OutStream.printLine`
** Return this.
**
This printLine(Obj? obj := "")
**
** Convenience for [out.writeProps]`OutStream.writeProps`
** Return this.
**
This writeProps(Str:Str props)
**
** Convenience for [out.writeObj]`OutStream.writeObj`
** Return this.
**
This writeObj(Obj? obj, [Str:Obj]? options := null)
**
** Convenience for [out.writeXml]`OutStream.writeXml`
** Return this.
**
This writeXml(Str s, Int flags := 0)
//////////////////////////////////////////////////////////////////////////
// InStream
//////////////////////////////////////////////////////////////////////////
**
** Get the InStream which reads from this buffer.
** This method always returns the same instance.
** If this buffer is backed by a file, then 'in.close'
** will not close the file - you must use `Buf.close`.
**
InStream in()
**
** Convenience for [in.read]`InStream.read`
**
Int? read()
**
** Convenience for [in.readBuf]`InStream.readBuf`
**
Int? readBuf(Buf buf, Int n)
**
** Convenience for [in.unread]`InStream.unread`
** Memory backed buffers support a stack based pushback model
** like IO streams. File backed buffers will simply rewrite
** the last position in the file. Return this.
**
This unread(Int b)
**
** Convenience for [in.readAllBuf]`InStream.readAllBuf`
**
Buf readAllBuf()
**
** Convenience for [in.readBufFully]`InStream.readBufFully`
**
Buf readBufFully(Buf? buf, Int n)
**
** Convenience for [in.peek]`InStream.peek`
**
Int? peek()
**
** Convenience for [in.readU1]`InStream.readU1`
**
Int readU1()
**
** Convenience for [in.readS1]`InStream.readS1`
**
Int readS1()
**
** Convenience for [in.readU2]`InStream.readU2`
**
Int readU2()
**
** Convenience for [in.readS2]`InStream.readS2`
**
Int readS2()
**
** Convenience for [in.readU4]`InStream.readU4`
**
Int readU4()
**
** Convenience for [in.readS4]`InStream.readS4`
**
Int readS4()
**
** Convenience for [in.readS8]`InStream.readS8`
**
Int readS8()
**
** Convenience for [in.readF4]`InStream.readF4`
**
Float readF4()
**
** Convenience for [in.readF8]`InStream.readF8`
**
Float readF8()
**
** Convenience for [in.readDecimal]`InStream.readDecimal`
**
Decimal readDecimal()
**
** Convenience for [in.readBool]`InStream.readBool`
**
Bool readBool()
**
** Convenience for [in.readUtf]`InStream.readUtf`
**
Str readUtf()
**
** Convenience for [in.readChar]`InStream.readChar`
**
Int? readChar()
**
** Convenience for [in.unreadChar]`InStream.unreadChar`
** Memory backed buffers support a stack based pushback model
** like IO streams. File backed buffers will simply rewrite
** the last position in the file. Return this.
**
This unreadChar(Int b)
**
** Convenience for [in.peekChar]`InStream.peekChar`
**
Int? peekChar()
**
** Convenience for [in.readChars]`InStream.readChars`
**
Str readChars(Int n)
**
** Convenience for [in.readLine]`InStream.readLine`
**
Str? readLine(Int? max := 4096)
**
** Convenience for [in.readStrToken]`InStream.readStrToken`
**
Str? readStrToken(Int? max := null, |Int ch->Bool|? c := null)
**
** Convenience for [in.readAllLines]`InStream.readAllLines`
**
Str[] readAllLines()
**
** Convenience for [in.eachLine]`InStream.eachLine`
**
Void eachLine(|Str line| f)
**
** Convenience for [in.readAllStr]`InStream.readAllStr`
**
Str readAllStr(Bool normalizeNewlines := true)
**
** Convenience for [in.readProps]`InStream.readProps`
**
Str:Str readProps()
**
** Convenience for [in.readObj]`InStream.readObj`
**
Obj? readObj([Str:Obj]? options := null)
//////////////////////////////////////////////////////////////////////////
// Conversions
//////////////////////////////////////////////////////////////////////////
**
** Create an in-memory File instance for this buffer with the given
** file URI. The buffer must be a RAM based buffer which is converted
** to an immutable buffer via 'Obj.toImmutable' semantics. The current
** time is used for the file's modified time.
**
File toFile(Uri uri)
**
** Encode the buffer contents from 0 to size into a
** hexadecimal string. This method is unsupported for
** mmap buffers.
**
** Example:
** Buf.make.print("\r\n").toHex => "0d0a"
** Buf.fromHex("0d0a").readAllStr => "\r\n"
**
Str toHex()
**
** Decode the specified hexadecimal string into its binary
** contents. Any characters which are not included in the
** set "0-9, a-f, A-F" are ignored as long as they appear
** between bytes (hi and lo nibbles must be contiguous).
**
** Example:
** Buf.make.print("\r\n").toHex => "0d0a"
** Buf.fromHex("0d0a").readAllStr => "\r\n"
**
static Buf fromHex(Str s)
**
** Encode the buffer contents from 0 to size to a Base64
** string as defined by MIME RFC 2045. No line breaks are
** added. This method is only supported by memory-backed
** buffers; file-backed buffers will throw UnsupportedErr.
**
** Example:
** Buf.make.print("Fan").toBase64 => "RmFu"
** Buf.fromBase64("RmFu").readAllStr => "Fan"
**
Str toBase64()
**
** Encode the buffer contents from 0 to size to a
** Uri-safe Base64 string as defined by RFC 4648.
** This means '+' is encoded as '-', and '/' is
** encoded as '_'. Additionally, no padding is applied.
** This method is only supported by memory-backed buffers;
** file-backed buffers will throw UnsupportedErr.
**
** Example:
** Buf.make.print("safe base64~~").toBase64 => "c2FmZSBiYXNlNjR+fg=="
** Buf.make.print("safe base64~~").toBase64Uri => "c2FmZSBiYXNlNjR-fg"
**
Str toBase64Uri()
**
** Decode the specified Base64 string into its binary contents.
** Both MIME RFC 2045 and URI-safe RFC 4648 encodings are supported.
** Any characters which are not included in the Base64 character
** set are safely ignored.
**
** Example:
** Buf.make.print("Fan").toBase64 => "RmFu"
** Buf.fromBase64("RmFu").readAllStr => "Fan"
**
static Buf fromBase64(Str s)
**
** Apply the specified message digest algorithm to this buffer's
** contents from 0 to size and return the resulting hash. Digests
** are secure one-way hash functions which input an arbitrary sized
** buffer and return a fixed sized buffer. Common algorithms include:
** "MD5", "SHA-1", and "SHA-256"; the full list supported is platform
** dependent. On the Java VM, the algorithm maps to those available
** via the 'java.security.MessageDigest' API. Throw ArgErr if the
** algorithm is not available. This method is unsupported for mmap
** buffers.
**
** Example:
** Buf.make.print("password").print("salt").toDigest("MD5").toHex
** => "b305cadbb3bce54f3aa59c64fec00dea"
**
Buf toDigest(Str algorithm)
**
** Compute a cycle redundancy check code using this buffer's contents
** from 0 to size. The supported algorithm names:
** - "CRC-16": also known as CRC-16-ANSI, CRC-16-IBM; used by
** USB, ANSI X3.28, and Modbus
** - "CRC-32": used by Ethernet, MPEG-2, PKZIP, Gzip, PNG
** - "CRC-32-Adler": used by Zlib
**
** Raise ArgErr if algorithm is not available. This method is
** only supported for memory based buffers.
**
Int crc(Str algorithm)
**
** Generate an HMAC message authentication as specified by RFC 2104.
** This buffer is the data input, 'algorithm' specifies the hash digest,
** and 'key' represents the secret key:
** - 'H': specified by algorthim parameter - "MD5" or "SHA1"
** - 'K': secret key specified by key parameter
** - 'B': fixed at 64
** - 'text': this instance
**
** The HMAC is computed using:
** ipad = the byte 0x36 repeated B times
** opad = the byte 0x5C repeated B times
** H(K XOR opad, H(K XOR ipad, text))
**
** Throw ArgErr if the algorithm is not available. This method is
** only supported for memory buffers.
**
** Examples:
** "hi there".toBuf.hmac("MD5", "secret".toBuf)
**
Buf hmac(Str algorithm, Buf key)
**
** Generate a password based cryptographic key. Supported algorithms:
** - "PBKDF2WithHmacSHA1"
** - "PBKDF2WithHmacSHA256"
**
** Parameters:
** - password: secret used to generate resulting cryptographic key
** - salt: cryptographic salt
** - iterations: number of iterations (the 'c' term)
** - keyLen: desired length of key in bytes (not bits!)
**
** Throw ArgErr if the algorithm is not available. This method is
** only supported for memory buffers.
**
static Buf pbk(Str algorithm, Str password, Buf salt, Int iterations, Int keyLen)
}
**************************************************************************
** MemBuf
**************************************************************************
internal class MemBuf : Buf
{
private new init()
}
**************************************************************************
** ConstBuf
**************************************************************************
internal class ConstBuf : Buf
{
private new init()
}
**************************************************************************
** FileBuf
**************************************************************************
internal class FileBuf : Buf
{
private new init()
}
**************************************************************************
** MmapBuf
**************************************************************************
// C# only right now (and not even implemented)
internal class MmapBuf : Buf
{
private new init()
}
**************************************************************************
** NioBuf
**************************************************************************
// Java only
internal class NioBuf : Buf
{
private new init()
}