5 * (c) 2018 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the Trivial IP Encryption (TrIPE) Android app.
12 * TrIPE is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
17 * TrIPE is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 * You should have received a copy of the GNU General Public License
23 * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
26 package uk.org.distorted.tripe; package object sys {
28 /*----- Imports -----------------------------------------------------------*/
30 import scala.collection.mutable.HashSet;
32 import java.io.{Closeable, File};
33 import java.nio.{ByteBuffer, CharBuffer};
34 import java.nio.charset.Charset;
35 import java.util.Date;
37 /*----- Some magic for C strings ------------------------------------------*/
39 type CString = Array[Byte];
41 /* We do this by hand, rather than relying on the JNI's built-in conversions,
42 * because we use the default encoding taken from the locale settings, rather
43 * than the ridiculous `modified UTF-8' which is (a) insensitive to the
44 * user's chosen locale and (b) not actually UTF-8 either.
47 class InvalidCStringException(msg: String) extends Exception(msg);
49 object StringImplicits {
50 implicit class ConvertJStringToCString(s: String) {
51 /* Magic to convert a string into a C string (null-terminated bytes). */
53 def toCString: CString = {
54 /* Convert the receiver to a C string. */
56 val enc = Charset.defaultCharset.newEncoder;
57 val in = CharBuffer.wrap(s);
58 var sz: Int = (s.length*enc.averageBytesPerChar + 1).toInt;
59 var out = ByteBuffer.allocate(sz);
62 /* If there's still stuff to encode, then encode it. Otherwise,
63 * there must be some dregs left in the encoder, so flush them out.
65 val r = if (in.hasRemaining) enc.encode(in, out, true)
68 /* Sift through the wreckage to figure out what to do. */
69 if (r.isError) r.throwException();
70 else if (r.isOverflow) {
71 /* No space in the buffer. Make it bigger. */
74 val newout = ByteBuffer.allocate(sz);
75 out.flip(); newout.put(out);
77 } else if (r.isUnderflow) {
78 /* All done. Check that there are no unexpected zero bytes -- so
79 * this will indeed be a valid C string -- and convert into a byte
80 * array that the C code will be able to pick apart.
83 out.flip(); val n = out.limit; val u = out.array;
84 if ({val z = u.indexOf(0); 0 <= z && z < n})
85 throw new InvalidCStringException("null byte in encoding");
86 val v = new Array[Byte](n + 1);
87 out.array.copyToArray(v, 0, n);
93 /* Placate the type checker. */
94 unreachable("unreachable");
98 implicit class ConvertCStringToJString(v: CString) {
99 /* Magic to convert a C string into a `proper' string. */
101 def toJString: String = {
102 /* Convert the receiver to a C string.
104 * We do this by hand, rather than relying on the JNI's built-in
105 * conversions, because we use the default encoding taken from the
106 * locale settings, rather than the ridiculous `modified UTF-8' which
107 * is (a) insensitive to the user's chosen locale and (b) not actually
111 val inlen = v.indexOf(0) match {
115 val dec = Charset.defaultCharset.newDecoder;
116 val in = ByteBuffer.wrap(v, 0, inlen);
117 dec.decode(in).toString
121 import StringImplicits._;
123 /*----- Main code ---------------------------------------------------------*/
125 /* Import the native code library. */
126 System.loadLibrary("toy");
128 /* Exception indicating that a wrapped native object has been clobbered. */
129 class NativeObjectTypeException(msg: String) extends RuntimeException(msg);
130 type Wrapper = Array[Byte];
132 /*----- Error codes -------------------------------------------------------*/
134 protected case class ErrorEntry(val tag: String, val err: Int);
135 @native protected def errtab: Array[ErrorEntry];
136 @native protected def strerror(err: Int): CString;
138 object Errno extends Enumeration {
139 private val tagmap = {
140 val b = Map.newBuilder[String, Int];
141 for (ErrorEntry(tag, err) <- errtab) b += tag -> err;
144 private var wrong = -255;
145 private val seen = HashSet[Int]();
147 class ErrnoVal private[Errno](tag: String, val code: Int, id: Int)
148 extends Val(id, tag) {
149 def message: String = strerror(code).toJString;
152 private def err(tag: String, code: Int): ErrnoVal = {
153 if (seen contains code) { wrong -= 1; new ErrnoVal(tag, code, wrong) }
154 else { seen += code; new ErrnoVal(tag, code, code) }
156 private def err(tag: String): ErrnoVal = err(tag, tagmap(tag));
158 val OK = err("OK", 0);
161 ;;; The errno name table is very boring to type. To make life less
162 ;;; awful, put the errno names in this list and evaluate the code to
163 ;;; get Emacs to regenerate it.
165 (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF
166 ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST
167 EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY
168 ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM
171 EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP
172 EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST
173 ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO
174 EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME
175 ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM
176 EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ
177 EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC
178 EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ
179 EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT
180 ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT
181 EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET
182 ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN
183 ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN
184 EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM
185 ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE
186 ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED
187 EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON)))
189 (goto-char (point-min))
190 (search-forward (concat "***" "BEGIN errtab" "***"))
191 (beginning-of-line 2)
192 (delete-region (point)
194 (search-forward "***END***")
198 (insert (format " val %s = err(\"%s\");\n" err err)))))
201 val EPERM = err("EPERM");
202 val ENOENT = err("ENOENT");
203 val ESRCH = err("ESRCH");
204 val EINTR = err("EINTR");
205 val EIO = err("EIO");
206 val ENXIO = err("ENXIO");
207 val E2BIG = err("E2BIG");
208 val ENOEXEC = err("ENOEXEC");
209 val EBADF = err("EBADF");
210 val ECHILD = err("ECHILD");
211 val EAGAIN = err("EAGAIN");
212 val ENOMEM = err("ENOMEM");
213 val EACCES = err("EACCES");
214 val EFAULT = err("EFAULT");
215 val ENOTBLK = err("ENOTBLK");
216 val EBUSY = err("EBUSY");
217 val EEXIST = err("EEXIST");
218 val EXDEV = err("EXDEV");
219 val ENODEV = err("ENODEV");
220 val ENOTDIR = err("ENOTDIR");
221 val EISDIR = err("EISDIR");
222 val EINVAL = err("EINVAL");
223 val ENFILE = err("ENFILE");
224 val EMFILE = err("EMFILE");
225 val ENOTTY = err("ENOTTY");
226 val ETXTBSY = err("ETXTBSY");
227 val EFBIG = err("EFBIG");
228 val ENOSPC = err("ENOSPC");
229 val ESPIPE = err("ESPIPE");
230 val EROFS = err("EROFS");
231 val EMLINK = err("EMLINK");
232 val EPIPE = err("EPIPE");
233 val EDOM = err("EDOM");
234 val ERANGE = err("ERANGE");
235 val EDEADLK = err("EDEADLK");
236 val ENAMETOOLONG = err("ENAMETOOLONG");
237 val ENOLCK = err("ENOLCK");
238 val ENOSYS = err("ENOSYS");
239 val ENOTEMPTY = err("ENOTEMPTY");
240 val ELOOP = err("ELOOP");
241 val EWOULDBLOCK = err("EWOULDBLOCK");
242 val ENOMSG = err("ENOMSG");
243 val EIDRM = err("EIDRM");
244 val ECHRNG = err("ECHRNG");
245 val EL2NSYNC = err("EL2NSYNC");
246 val EL3HLT = err("EL3HLT");
247 val EL3RST = err("EL3RST");
248 val ELNRNG = err("ELNRNG");
249 val EUNATCH = err("EUNATCH");
250 val ENOCSI = err("ENOCSI");
251 val EL2HLT = err("EL2HLT");
252 val EBADE = err("EBADE");
253 val EBADR = err("EBADR");
254 val EXFULL = err("EXFULL");
255 val ENOANO = err("ENOANO");
256 val EBADRQC = err("EBADRQC");
257 val EBADSLT = err("EBADSLT");
258 val EDEADLOCK = err("EDEADLOCK");
259 val EBFONT = err("EBFONT");
260 val ENOSTR = err("ENOSTR");
261 val ENODATA = err("ENODATA");
262 val ETIME = err("ETIME");
263 val ENOSR = err("ENOSR");
264 val ENONET = err("ENONET");
265 val ENOPKG = err("ENOPKG");
266 val EREMOTE = err("EREMOTE");
267 val ENOLINK = err("ENOLINK");
268 val EADV = err("EADV");
269 val ESRMNT = err("ESRMNT");
270 val ECOMM = err("ECOMM");
271 val EPROTO = err("EPROTO");
272 val EMULTIHOP = err("EMULTIHOP");
273 val EDOTDOT = err("EDOTDOT");
274 val EBADMSG = err("EBADMSG");
275 val EOVERFLOW = err("EOVERFLOW");
276 val ENOTUNIQ = err("ENOTUNIQ");
277 val EBADFD = err("EBADFD");
278 val EREMCHG = err("EREMCHG");
279 val ELIBACC = err("ELIBACC");
280 val ELIBBAD = err("ELIBBAD");
281 val ELIBSCN = err("ELIBSCN");
282 val ELIBMAX = err("ELIBMAX");
283 val ELIBEXEC = err("ELIBEXEC");
284 val EILSEQ = err("EILSEQ");
285 val ERESTART = err("ERESTART");
286 val ESTRPIPE = err("ESTRPIPE");
287 val EUSERS = err("EUSERS");
288 val ENOTSOCK = err("ENOTSOCK");
289 val EDESTADDRREQ = err("EDESTADDRREQ");
290 val EMSGSIZE = err("EMSGSIZE");
291 val EPROTOTYPE = err("EPROTOTYPE");
292 val ENOPROTOOPT = err("ENOPROTOOPT");
293 val EPROTONOSUPPORT = err("EPROTONOSUPPORT");
294 val ESOCKTNOSUPPORT = err("ESOCKTNOSUPPORT");
295 val EOPNOTSUPP = err("EOPNOTSUPP");
296 val EPFNOSUPPORT = err("EPFNOSUPPORT");
297 val EAFNOSUPPORT = err("EAFNOSUPPORT");
298 val EADDRINUSE = err("EADDRINUSE");
299 val EADDRNOTAVAIL = err("EADDRNOTAVAIL");
300 val ENETDOWN = err("ENETDOWN");
301 val ENETUNREACH = err("ENETUNREACH");
302 val ENETRESET = err("ENETRESET");
303 val ECONNABORTED = err("ECONNABORTED");
304 val ECONNRESET = err("ECONNRESET");
305 val ENOBUFS = err("ENOBUFS");
306 val EISCONN = err("EISCONN");
307 val ENOTCONN = err("ENOTCONN");
308 val ESHUTDOWN = err("ESHUTDOWN");
309 val ETOOMANYREFS = err("ETOOMANYREFS");
310 val ETIMEDOUT = err("ETIMEDOUT");
311 val ECONNREFUSED = err("ECONNREFUSED");
312 val EHOSTDOWN = err("EHOSTDOWN");
313 val EHOSTUNREACH = err("EHOSTUNREACH");
314 val EALREADY = err("EALREADY");
315 val EINPROGRESS = err("EINPROGRESS");
316 val ESTALE = err("ESTALE");
317 val EUCLEAN = err("EUCLEAN");
318 val ENOTNAM = err("ENOTNAM");
319 val ENAVAIL = err("ENAVAIL");
320 val EISNAM = err("EISNAM");
321 val EREMOTEIO = err("EREMOTEIO");
322 val EDQUOT = err("EDQUOT");
323 val ENOMEDIUM = err("ENOMEDIUM");
324 val EMEDIUMTYPE = err("EMEDIUMTYPE");
325 val ECANCELED = err("ECANCELED");
326 val ENOKEY = err("ENOKEY");
327 val EKEYEXPIRED = err("EKEYEXPIRED");
328 val EKEYREVOKED = err("EKEYREVOKED");
329 val EKEYREJECTED = err("EKEYREJECTED");
330 val EOWNERDEAD = err("EOWNERDEAD");
331 val ENOTRECOVERABLE = err("ENOTRECOVERABLE");
332 val ERFKILL = err("ERFKILL");
333 val EHWPOISON = err("EHWPOISON");
336 import Errno.{Value => _, _};
339 def apply(err: Errno.Value, what: String): SystemError =
340 new SystemError(err, what);
341 def unapply(e: Exception): Option[(Errno.Value, String)] = e match {
342 case e: SystemError => Some((e.err, e.what))
347 class SystemError private[this](val err: Errno.ErrnoVal, val what: String)
349 def this(err: Errno.Value, what: String)
350 { this(err.asInstanceOf[Errno.ErrnoVal], what); }
351 private def this(err: Int, what: CString)
352 { this(Errno(err), what.toJString); }
353 override def getMessage(): String = s"$what: ${err.message}";
356 /*----- Basic file operations ---------------------------------------------*/
358 @native protected def unlink(path: CString);
359 def unlink(path: String) { unlink(path.toCString); }
360 @native protected def rmdir(path: CString);
361 def rmdir(path: String) { rmdir(path.toCString); }
362 @native protected def mkdir(path: CString, mode: Int);
363 def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
364 @native protected def mkfile(path: CString, mode: Int);
365 def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
366 @native protected def rename(from: CString, to: CString);
367 def rename(from: String, to: String)
368 { rename(from.toCString, to.toCString); }
370 /*----- File status information -------------------------------------------*/
372 /* These are the traditional values, but the C code carefully arranges to
373 * return them regardless of what your kernel actually thinks.
376 val S_IFIFO = 0x1000;
377 val S_IFCHR = 0x2000;
378 val S_IFDIR = 0x4000;
379 val S_IFBLK = 0x6000;
380 val S_IFREG = 0x8000;
381 val S_IFLNK = 0xa000;
382 val S_IFSOCK = 0xc000;
384 @native protected def stat(path: CString): sys.FileInfo;
385 def stat(path: String): sys.FileInfo = stat(path.toCString);
386 @native protected def lstat(path: CString): sys.FileInfo;
387 def lstat(path: String): sys.FileInfo = lstat(path.toCString);
389 object FileInfo extends Enumeration {
390 val FIFO, CHR, DIR, BLK, REG, LNK, SOCK, UNK = Value;
396 class FileInfo private[this](val devMajor: Int, val devMinor: Int,
397 val ino: Long, val mode: Int, val nlink: Int,
398 val uid: Int, val gid: Int,
399 _rdevMinor: Int, _rdevMajor: Int,
401 val blksize: Int, val blocks: Long,
402 val atime: Date, val mtime: Date,
404 private def this(devMajor: Int, devMinor: Int, ino: Long,
405 mode: Int, nlink: Int, uid: Int, gid: Int,
406 rdevMinor: Int, rdevMajor: Int,
407 size: Long, blksize: Int, blocks: Long,
408 atime: Long, mtime: Long, ctime: Long) {
409 this(devMajor, devMinor, ino, mode, nlink, uid, gid,
410 rdevMajor, rdevMinor, size, blksize, blocks,
411 new Date(atime), new Date(mtime), new Date(ctime));
414 def perms: Int = mode&0xfff;
416 def ftype: Type = (mode&S_IFMT) match {
423 case S_IFSOCK => SOCK
427 private[this] def mustBeDevice() {
429 case CHR | BLK => ();
430 case _ => throw new IllegalArgumentException("Object is not a device");
433 def rdevMajor: Int = { mustBeDevice(); _rdevMajor }
434 def rdevMinor: Int = { mustBeDevice(); _rdevMinor }
437 /*----- Listing directories -----------------------------------------------*/
439 @native protected def opendir(path: CString): Wrapper;
440 @native protected def readdir(path: CString, dir: Wrapper): CString;
441 @native protected def closedir(path: CString, dir: Wrapper);
443 protected abstract class BaseDirIterator[T](cpath: CString)
444 extends LookaheadIterator[T] with Closeable {
445 def this(path: String) { this(path.toCString); }
446 def this(dir: File) { this(dir.getPath); }
447 override def close() { closedir(cpath, dir); }
448 override protected def finalize() { super.finalize(); close(); }
449 private[this] val dir = opendir(cpath);
450 protected def mangle(file: String): T;
451 override protected def fetch(): Option[T] = readdir(cpath, dir) match {
453 case f => f.toJString match {
454 case "." | ".." => fetch()
455 case jf => Some(mangle(jf))
460 class DirIterator(val path: String) extends BaseDirIterator[String](path) {
461 def this(dir: File) { this(dir.getPath); }
462 override protected def mangle(file: String): String = file;
465 class DirFilesIterator private[this](val dir: File, cpath: CString)
466 extends BaseDirIterator[File](cpath) {
467 def this(dir: File) { this(dir, dir.getPath.toCString); }
468 def this(path: String) { this(new File(path), path.toCString); }
469 override protected def mangle(file: String): File = new File(dir, file);
472 /*----- File locking ------------------------------------------------------*/
476 @native protected def lock(path: CString, flags: Int): Wrapper;
477 @native protected def unlock(lock: Wrapper);
479 class FileLock(path: String, flags: Int) extends Closeable {
480 def this(file: File, flags: Int) { this(file.getPath, flags); }
481 def this(path: String) { this(path, LKF_EXCL); }
482 def this(file: File) { this(file.getPath, LKF_EXCL); }
483 private[this] val lk = lock(path.toCString, flags);
484 override def close() { unlock(lk); }
485 override protected def finalize() { super.finalize(); close(); }
488 /*----- File implicits ----------------------------------------------------*/
490 object FileImplicits {
491 implicit class FileOps(file: File) {
493 def unlink_!() { unlink(file.getPath); }
494 def rmdir_!() { rmdir(file.getPath); }
495 def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
496 def mkdir_!() { mkdir_!(0x1ff); }
497 def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
498 def mkfile_!() { mkfile_!(0x1b6); }
500 def withFilesIterator[T](body: DirFilesIterator => T): T = {
501 val iter = new DirFilesIterator(file.getPath);
502 try { body(iter) } finally { iter.close(); }
504 def files_! : Seq[File] = withFilesIterator { _.toSeq };
506 def stat_! : FileInfo = stat(file.getPath);
507 def lstat_! : FileInfo = lstat(file.getPath);
509 private[this] def statish[T](statfn: String => FileInfo,
510 ifexists: FileInfo => T,
511 ifmissing: => T): T =
512 (try { statfn(file.getPath) }
513 catch { case SystemError(ENOENT, _) => null }) match {
514 case null => ifmissing
515 case st => ifexists(st)
517 def exists_! : Boolean = statish(stat _, _ => true, false);
518 def isfifo_! : Boolean = statish(stat _, _.ftype == FIFO, false);
519 def isblk_! : Boolean = statish(stat _, _.ftype == BLK, false);
520 def isdir_! : Boolean = statish(stat _, _.ftype == DIR, false);
521 def ischr_! : Boolean = statish(stat _, _.ftype == CHR, false);
522 def isreg_! : Boolean = statish(stat _, _.ftype == REG, false);
523 def islnk_! : Boolean = statish(lstat _, _.ftype == LNK, false);
524 def issock_! : Boolean = statish(stat _, _.ftype == SOCK, false);
528 try { unlink_!(); return }
530 case SystemError(ENOENT, _) => return;
531 case SystemError(EISDIR, _) => ();
533 try { rmdir_!(); return }
535 case SystemError(ENOENT, _) => return;
536 case SystemError(ENOTDIR, _) => ();
543 if (f.isdir_!) f.withFilesIterator { _ foreach(walk _) };
549 def withLock[T](flags: Int)(body: => T): T = {
550 val lk = new FileLock(file.getPath, flags);
551 try { body } finally { lk.close(); }
553 def withLock[T](body: => T): T = withLock(LKF_EXCL) { body };
556 import FileImplicits._;
558 /*----- Miscellaneous file hacks ------------------------------------------*/
560 def freshFile(d: File): File = {
561 /* Return the name of a freshly created file in directory D. */
563 val buf = new Array[Byte](6);
564 val b = new StringBuilder;
567 /* Keep going until we find a fresh one. */
569 /* Provide a prefix. Mostly this is to prevent the file starting with
570 * an unfortunate character like `-'.
574 /* Generate some random bytes. */
577 /* Now turn the bytes into a filename. This is a cheesy implementation
578 * of Base64 encoding.
584 a = (a << 8) | x; n += 8;
586 val y = (a >> n - 6)&0x3f; n -= 6;
587 b += (if (y < 26) 'A' + y
588 else if (y < 52) 'a' + (y - 26)
589 else if (y < 62) '0' + (y - 52)
590 else if (y == 62) '+'
595 /* Make the filename, and try to create the file. If we succeed, we
598 val f = new File(d, b.result); b.clear();
599 try { f.mkfile_!(); return f; }
600 catch { case SystemError(EEXIST, _) => (); }
603 /* We shouldn't get here, but the type checker needs placating. */
604 unreachable("unreachable");
607 /*----- Connecting to a server --------------------------------------------*/
611 val CF_CLOSEMASK = CF_CLOSERD | CF_CLOSEWR;
612 @native protected def connect(path: CString): Wrapper;
613 @native protected def send(conn: Wrapper, buf: CString,
614 start: Int, len: Int);
615 @native protected def recv(conn: Wrapper, buf: CString,
616 start: Int, len: Int): Int;
617 @native def closeconn(conn: Wrapper, how: Int);
619 class Connection(path: String) extends Closeable {
620 def this(file: File) { this(file.getPath); }
621 private[this] val conn = connect(path.toCString);
622 override def close() { closeconn(conn, CF_CLOSEMASK); }
623 override protected def finalize() { super.finalize(); close(); }
625 class InputStream private[Connection] extends java.io.InputStream {
626 override def read(): Int = {
627 val buf = new Array[Byte](1);
628 val n = read(buf, 0, 1);
629 if (n < 0) -1 else buf(0)&0xff;
631 override def read(buf: Array[Byte]): Int =
632 read(buf, 0, buf.length);
633 override def read(buf: Array[Byte], start: Int, len: Int) =
634 recv(conn, buf, start, len);
635 override def close() { closeconn(conn, CF_CLOSERD); }
637 lazy val input = new InputStream;
639 class OutputStream private[Connection] extends java.io.OutputStream {
640 override def write(b: Int) { write(Array[Byte](b.toByte), 0, 1); }
641 override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
642 override def write(buf: Array[Byte], start: Int, len: Int)
643 { send(conn, buf, start, len); }
644 override def close() { closeconn(conn, CF_CLOSEWR); }
646 lazy val output = new OutputStream;
649 /*----- Crypto-library hacks ----------------------------------------------*/
651 @native def hashsz(hash: String): Int;
652 /* Return the output hash size for the named HASH function, or -1. */
654 /*----- That's all, folks -------------------------------------------------*/