Aurel Bílý 5 gadi atpakaļ
vecāks
revīzija
e896285b66
43 mainītis faili ar 2709 papildinājumiem un 0 dzēšanām
  1. 52 0
      std/asys/AsyncFileSystem.hx
  2. 54 0
      std/asys/CurrentProcess.hx
  3. 17 0
      std/asys/DirectoryEntry.hx
  4. 16 0
      std/asys/FileAccessMode.hx
  5. 23 0
      std/asys/FileCopyFlags.hx
  6. 80 0
      std/asys/FileOpenFlags.hx
  7. 90 0
      std/asys/FilePermissions.hx
  8. 40 0
      std/asys/FileStat.hx
  9. 222 0
      std/asys/FileSystem.hx
  10. 47 0
      std/asys/FileWatcher.hx
  11. 14 0
      std/asys/FileWatcherEvent.hx
  12. 71 0
      std/asys/Net.hx
  13. 178 0
      std/asys/Process.hx
  14. 16 0
      std/asys/ProcessExit.hx
  15. 10 0
      std/asys/ProcessIO.hx
  16. 9 0
      std/asys/SymlinkType.hx
  17. 34 0
      std/asys/Timer.hx
  18. 47 0
      std/asys/io/AsyncFile.hx
  19. 88 0
      std/asys/io/File.hx
  20. 42 0
      std/asys/io/FileInput.hx
  21. 46 0
      std/asys/io/FileOutput.hx
  22. 20 0
      std/asys/io/FileReadStream.hx
  23. 13 0
      std/asys/io/FileWriteStream.hx
  24. 21 0
      std/asys/io/IpcMessage.hx
  25. 50 0
      std/asys/io/IpcSerializer.hx
  26. 85 0
      std/asys/io/IpcUnserializer.hx
  27. 21 0
      std/asys/net/Address.hx
  28. 223 0
      std/asys/net/AddressTools.hx
  29. 24 0
      std/asys/net/Dns.hx
  30. 16 0
      std/asys/net/DnsLookupOptions.hx
  31. 9 0
      std/asys/net/IpFamily.hx
  32. 61 0
      std/asys/net/Server.hx
  33. 247 0
      std/asys/net/Socket.hx
  34. 15 0
      std/asys/net/SocketAddress.hx
  35. 41 0
      std/asys/net/SocketOptions.hx
  36. 171 0
      std/asys/net/UdpSocket.hx
  37. 14 0
      std/asys/uv/UVConstants.hx
  38. 12 0
      std/asys/uv/UVDirentType.hx
  39. 388 0
      std/asys/uv/UVErrorType.hx
  40. 7 0
      std/asys/uv/UVFsEventType.hx
  41. 18 0
      std/asys/uv/UVProcessSpawnFlags.hx
  42. 7 0
      std/asys/uv/UVRunMode.hx
  43. 50 0
      std/asys/uv/UVStat.hx

+ 52 - 0
std/asys/AsyncFileSystem.hx

@@ -0,0 +1,52 @@
+package asys;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.Callback;
+import haxe.io.Bytes;
+import haxe.io.FilePath;
+import asys.*;
+
+/**
+	This class provides methods for asynchronous operations on files and
+	directories. For synchronous operations, see `asys.FileSystem`.
+
+	All methods here are asynchronous versions of the functions in
+	`asys.FileSystem`. Please see them for a description of the arguments and
+	use of each method.
+
+	Any synchronous method that returns no value (`Void` return type) has an
+	extra `callback:Callback<NoData>` argument.
+
+	Any synchronous method that returns a value has an extra
+	`callback:Callback<T>` argument, where `T` is the return type of the
+	synchronous method.
+
+	Errors are communicated through the callbacks or in some cases thrown
+	immediately.
+**/
+extern class AsyncFileSystem {
+	static function access(path:FilePath, ?mode:FileAccessMode = FileAccessMode.Ok, callback:Callback<NoData>):Void;
+	static function chmod(path:FilePath, mode:FilePermissions, ?followSymLinks:Bool = true, callback:Callback<NoData>):Void;
+	static function chown(path:FilePath, uid:Int, gid:Int, ?followSymLinks:Bool = true, callback:Callback<NoData>):Void;
+	static function copyFile(src:FilePath, dest:FilePath, ?flags:FileCopyFlags, callback:Callback<NoData>):Void;
+	static function exists(path:FilePath, callback:Callback<Bool>):Void;
+	static function link(existingPath:FilePath, newPath:FilePath, callback:Callback<NoData>):Void;
+	static function mkdir(path:FilePath, ?recursive:Bool, ?mode:FilePermissions, callback:Callback<NoData>):Void;
+	static function mkdtemp(prefix:FilePath, callback:Callback<FilePath>):Void;
+	static function readdir(path:FilePath, callback:Callback<Array<FilePath>>):Void;
+	static function readdirTypes(path:FilePath, callback:Callback<Array<DirectoryEntry>>):Void;
+	static function readlink(path:FilePath, callback:Callback<FilePath>):Void;
+	static function realpath(path:FilePath, callback:Callback<FilePath>):Void;
+	static function rename(oldPath:FilePath, newPath:FilePath, callback:Callback<NoData>):Void;
+	static function rmdir(path:FilePath, callback:Callback<NoData>):Void;
+	static function stat(path:FilePath, ?followSymLinks:Bool = true, callback:Callback<FileStat>):Void;
+	static function symlink(target:FilePath, path:FilePath, ?type:String, callback:Callback<NoData>):Void;
+	static function truncate(path:FilePath, len:Int, callback:Callback<NoData>):Void;
+	static function unlink(path:FilePath, callback:Callback<NoData>):Void;
+	static function utimes(path:FilePath, atime:Date, mtime:Date, callback:Callback<NoData>):Void;
+	static function appendFile(path:FilePath, data:Bytes, ?flags:FileOpenFlags, ?mode:FilePermissions, callback:Callback<NoData>):Void;
+	static function open(path:FilePath, ?flags:FileOpenFlags, ?mode:FilePermissions, ?binary:Bool = true, callback:Callback<sys.io.File>):Void;
+	static function readFile(path:FilePath, ?flags:FileOpenFlags, callback:Callback<Bytes>):Void;
+	static function writeFile(path:FilePath, data:Bytes, ?flags:FileOpenFlags, ?mode:FilePermissions, callback:Callback<NoData>):Void;
+}

+ 54 - 0
std/asys/CurrentProcess.hx

@@ -0,0 +1,54 @@
+package asys;
+
+import haxe.async.*;
+import asys.net.Socket;
+import asys.io.*;
+
+/**
+	Methods to control the current process and IPC interaction with the parent
+	process.
+**/
+class CurrentProcess {
+	/**
+		Emitted when a message is received over IPC. `initIpc` must be called first
+		to initialise the IPC channel.
+	**/
+	public static final messageSignal:Signal<IpcMessage> = new ArraySignal();
+
+	static var ipc:Socket;
+	static var ipcOut:IpcSerializer;
+	static var ipcIn:IpcUnserializer;
+
+	/**
+		Initialise the IPC channel on the given file descriptor `fd`. This should
+		only be used when the current process was spawned with `Process.spawn` from
+		another Haxe process. `fd` should correspond to the index of the `Ipc`
+		entry in `options.stdio`.
+	**/
+	public static function initIpc(fd:Int):Void {
+		if (ipc != null)
+			throw "IPC already initialised";
+		ipc = Socket.create();
+		ipcOut = @:privateAccess new IpcSerializer(ipc);
+		ipcIn = @:privateAccess new IpcUnserializer(ipc);
+		ipc.connectFd(true, fd);
+		ipc.errorSignal.on(err -> trace("IPC error", err));
+		ipcIn.messageSignal.on(message -> messageSignal.emit(message));
+	}
+
+	/**
+		Sends a message over IPC. `initIpc` must be called first to initialise the
+		IPC channel.
+	**/
+	public static function send(message:IpcMessage):Void {
+		if (ipc == null)
+			throw "IPC not connected";
+		ipcOut.write(message);
+	}
+
+	extern public static function initUv():Void;
+
+	extern public static function runUv(?mode:asys.uv.UVRunMode = RunDefault):Bool;
+
+	extern public static function stopUv():Void;
+}

+ 17 - 0
std/asys/DirectoryEntry.hx

@@ -0,0 +1,17 @@
+package asys;
+
+import haxe.io.FilePath;
+
+/**
+	An entry returned from `asys.FileSystem.readdirTypes`.
+**/
+interface DirectoryEntry {
+	var name(get, never):FilePath;
+	function isBlockDevice():Bool;
+	function isCharacterDevice():Bool;
+	function isDirectory():Bool;
+	function isFIFO():Bool;
+	function isFile():Bool;
+	function isSocket():Bool;
+	function isSymbolicLink():Bool;
+}

+ 16 - 0
std/asys/FileAccessMode.hx

@@ -0,0 +1,16 @@
+package asys;
+
+/**
+	Wrapper for file access modes. See `asys.FileSystem.access`.
+**/
+enum abstract FileAccessMode(Int) {
+	var Ok = 0;
+	var Execute = 1 << 0;
+	var Write = 1 << 1;
+	var Read = 1 << 2;
+
+	inline function get_raw():Int return this;
+
+	@:op(A | B)
+	inline function join(other:FileAccessMode) return this | other.get_raw();
+}

+ 23 - 0
std/asys/FileCopyFlags.hx

@@ -0,0 +1,23 @@
+package asys;
+
+enum abstract FileCopyFlags(Int) {
+	/**
+		Fail if destination exists.
+	**/
+	var FailIfExists = 1 << 0;
+
+	/**
+		Copy-on-write reflink if possible.
+	**/
+	var COWClone = 1 << 1;
+
+	/**
+		Copy-on-write reflink or fail.
+	**/
+	var COWCloneForce = 1 << 2;
+
+	inline function get_raw():Int return this;
+
+	@:op(A | B)
+	inline function join(other:FileCopyFlags) return this | other.get_raw();
+}

+ 80 - 0
std/asys/FileOpenFlags.hx

@@ -0,0 +1,80 @@
+package asys;
+
+class FileOpenFlagsImpl {
+	public static function fromString(flags:String):FileOpenFlags {
+		return (switch (flags) {
+			case "r": ReadOnly;
+			case "r+": ReadWrite;
+			case "rs+": ReadWrite | Sync;
+			case "sr+": ReadWrite | Sync;
+			case "w": Truncate | Create | WriteOnly;
+			case "w+": Truncate | Create | ReadWrite;
+			case "a": Append | Create | WriteOnly;
+			case "a+": Append | Create | ReadWrite;
+			case "wx": Truncate | Create | WriteOnly | Excl;
+			case "xw": Truncate | Create | WriteOnly | Excl;
+			case "wx+": Truncate | Create | ReadWrite | Excl;
+			case "xw+": Truncate | Create | ReadWrite | Excl;
+			case "ax": Append | Create | WriteOnly | Excl;
+			case "xa": Append | Create | WriteOnly | Excl;
+			case "as": Append | Create | WriteOnly | Sync;
+			case "sa": Append | Create | WriteOnly | Sync;
+			case "ax+": Append | Create | ReadWrite | Excl;
+			case "xa+": Append | Create | ReadWrite | Excl;
+			case "as+": Append | Create | ReadWrite | Sync;
+			case "sa+": Append | Create | ReadWrite | Sync;
+			case _: throw "invalid file open flags";
+		});
+	}
+}
+
+/**
+	Flags used when opening a file with `asys.FileSystem.open` or other file
+	functions. Specify whether the opened file:
+
+	- will be readable
+	- will be writable
+	- will be truncated (all data lost) first
+	- will be in append mode
+	- will be opened exclusively by this process
+
+	Instances of this type can be created by combining flags with the bitwise or
+	operator:
+
+	```haxe
+	Truncate | Create | WriteOnly
+	```
+
+	Well-known combinations of flags can be specified with a string. The
+	supported modes are: `r`, `r+`, `rs+`, `sr+`, `w`, `w+`, `a`, `a+`, `wx`,
+	`xw`, `wx+`, `xw+`, `ax`, `xa`, `as`, `sa`, `ax+`, `xa+`, `as+`, `sa+`.
+**/
+@:native("asys.FileOpenFlagsImpl")
+extern enum abstract FileOpenFlags(Int) {
+	@:from public static function fromString(flags:String):FileOpenFlags;
+
+	inline function new(value:Int)
+		this = value;
+
+	inline function get_raw():Int return this;
+
+	@:op(A | B)
+	inline function join(other:FileOpenFlags):FileOpenFlags return new FileOpenFlags(this | other.get_raw());
+
+	// TODO: some of these don't make sense in Haxe-wrapped libuv
+	var Append;
+	var Create;
+	var Direct;
+	var Directory;
+	var Dsync;
+	var Excl;
+	var NoAtime;
+	var NoCtty;
+	var NoFollow;
+	var NonBlock;
+	var ReadOnly;
+	var ReadWrite;
+	var Sync;
+	var Truncate;
+	var WriteOnly;
+}

+ 90 - 0
std/asys/FilePermissions.hx

@@ -0,0 +1,90 @@
+package asys;
+
+/**
+	File permissions in specify whether a file can be read, written, or executed
+	by its owner, its owning group, and everyone else. Instances of this type
+	can be constructed by combining individual file permissions with the `|`
+	operator:
+
+	```haxe
+	ReadOwner | WriteOwner | ReadGroup | ReadOthers
+	```
+
+	Alternatively, file permissions may be specified as a string with exactly 9
+	characters, in the format `rwxrwxrwx`, where each letter may instead be a
+	`-` character. The first three characters represent the permissions of the
+	owner, the second three characters represent the permissions of the owning
+	group, and the last three characters represent the permissions of everyone
+	else.
+
+	```haxe
+	"rw-r--r--"
+	```
+
+	Finally, file permissions may be constructed from an octal representation
+	using the `fromOctal` function.
+
+	```haxe
+	FilePermissions.fromOctal("644")
+	```
+**/
+enum abstract FilePermissions(Int) {
+	@:from public static function fromString(s:String):FilePermissions {
+		inline function bit(cc:Int, expect:Int):Int {
+			return (if (cc == expect)
+				1;
+			else if (cc == "-".code)
+				0;
+			else
+				throw "invalid file permissions string");
+		}
+		switch (s.length) {
+			case 9: // rwxrwxrwx
+				return new FilePermissions(bit(s.charCodeAt(0), "r".code) << 8
+					| bit(s.charCodeAt(1), "w".code) << 7
+					| bit(s.charCodeAt(2), "x".code) << 6
+					| bit(s.charCodeAt(3), "r".code) << 5
+					| bit(s.charCodeAt(4), "w".code) << 4
+					| bit(s.charCodeAt(5), "x".code) << 3
+					| bit(s.charCodeAt(6), "r".code) << 2
+					| bit(s.charCodeAt(7), "w".code) << 1
+					| bit(s.charCodeAt(8), "x".code));
+			case _:
+				throw "invalid file permissions string";
+		}
+	}
+
+	public static function fromOctal(s:String):FilePermissions {
+		inline function digit(n:Int):Int {
+			if (n >= "0".code && n <= "7".code) return n - "0".code;
+			throw "invalid octal file permissions";
+		}
+		switch (s.length) {
+			case 3: // 777
+				return new FilePermissions(digit(s.charCodeAt(0)) << 6
+					| digit(s.charCodeAt(1)) << 3
+					| digit(s.charCodeAt(2)));
+			case _:
+				throw "invalid octal file permissions";
+		}
+	}
+
+	var None = 0;
+	var ExecuteOthers = 1 << 0;
+	var WriteOthers = 1 << 1;
+	var ReadOthers = 1 << 2;
+	var ExecuteGroup = 1 << 3;
+	var WriteGroup = 1 << 4;
+	var ReadGroup = 1 << 5;
+	var ExecuteOwner = 1 << 6;
+	var WriteOwner = 1 << 7;
+	var ReadOwner = 1 << 8;
+
+	inline function new(value:Int)
+		this = value;
+
+	inline function get_raw():Int return this;
+
+	@:op(A | B)
+	inline function join(other:FilePermissions) return new FilePermissions(this | other.get_raw());
+}

+ 40 - 0
std/asys/FileStat.hx

@@ -0,0 +1,40 @@
+package asys;
+
+typedef FileStatData = {
+	var atime:Date;
+	var ctime:Date;
+	var dev:Int;
+	var gid:Int;
+	var ino:Int;
+	var mode:Int;
+	var mtime:Date;
+	var nlink:Int;
+	var rdev:Int;
+	var size:Int;
+	var uid:Int;
+
+	var blksize:Int;
+	var blocks:Int;
+	var atimeMs:Float;
+	var ctimeMs:Float;
+	var mtimeMs:Float;
+	var birthtime:Date;
+	var birthtimeMs:Float;
+};
+
+@:forward
+abstract FileStat(FileStatData) from FileStatData {
+	public function isBlockDevice():Bool return false;
+
+	public function isCharacterDevice():Bool return false;
+
+	public function isDirectory():Bool return false;
+
+	public function isFIFO():Bool return false;
+
+	public function isFile():Bool return false;
+
+	public function isSocket():Bool return false;
+
+	public function isSymbolicLink():Bool return false;
+}

+ 222 - 0
std/asys/FileSystem.hx

@@ -0,0 +1,222 @@
+package asys;
+
+import haxe.Error;
+import haxe.io.Bytes;
+import haxe.io.FilePath;
+import asys.io.*;
+
+typedef FileReadStreamCreationOptions = {
+	?flags:FileOpenFlags,
+	?mode:FilePermissions
+} &
+	asys.io.FileReadStream.FileReadStreamOptions;
+
+/**
+	This class provides methods for synchronous operations on files and
+	directories. For asynchronous operations, see `asys.async.FileSystem`.
+
+	Passing `null` as a path to any of the functions in this class will result
+	in unspecified behaviour.
+**/
+extern class FileSystem {
+	public static inline final async = asys.AsyncFileSystem;
+
+	/**
+		Tests specific user permissions for the file specified by `path`. If the
+		check fails, throws an exception. `mode` is one or more `FileAccessMode`
+		values:
+
+		- `FileAccessMode.Ok` - file is visible to the calling process (it exists)
+		- `FileAccessMode.Execute` - file can be executed by the calling proces
+		- `FileAccessMode.Write` - file can be written to by the calling proces
+		- `FileAccessMode.Read` - file can be read from by the calling proces
+
+		Mode values can be combined with the bitwise or operator, e.g. calling
+		`access` with the `mode`:
+
+		```haxe
+		FileAccessMode.Execute | FileAccessMode.Read
+		```
+
+		will check that the file is both readable and executable.
+
+		The result of this call should not be used in a condition before a call to
+		e.g. `open`, because this would introduce a race condition (the file could
+		be deleted after the `access` call, but before the `open` call). Instead,
+		the latter function should be called immediately and errors should be
+		handled with a `try ... catch` block.
+	**/
+	static function access(path:FilePath, ?mode:FileAccessMode = FileAccessMode.Ok):Void;
+
+	/**
+		Appends `data` at the end of the file located at `path`.
+	**/
+	static function appendFile(path:FilePath, data:Bytes, ?flags:FileOpenFlags /* a */, ?mode:FilePermissions /* 0666 */):Void;
+
+	/**
+		Changes the permissions of the file specific by `path` to `mode`.
+
+		If `path` points to a symbolic link, this function will change the
+		permissions of the target file, not the symbolic link itself, unless
+		`followSymLinks` is set to `false`.
+
+		TODO: `followSymLinks == false` is not implemented and will throw.
+	**/
+	static function chmod(path:FilePath, mode:FilePermissions, ?followSymLinks:Bool = true):Void;
+
+	/**
+		Changes the owner and group of the file specific by `path` to `uid` and
+		`gid`, respectively.
+
+		If `path` points to a symbolic link, this function will change the
+		permissions of the target file, not the symbolic link itself, unless
+		`followSymLinks` is set to `false`.
+
+		TODO: `followSymLinks == false` is not implemented and will throw.
+	**/
+	static function chown(path:FilePath, uid:Int, gid:Int, ?followSymLinks:Bool = true):Void;
+
+	/**
+		Copies the file at `src` to `dest`. If `dest` exists, it is overwritten.
+	**/
+	static function copyFile(src:FilePath, dest:FilePath /* , ?flags:FileCopyFlags */):Void;
+
+	/**
+		Creates a read stream (an instance of `IReadable`) for the given path.
+		`options` can be used to specify how the file is opened, as well as which
+		part of the file will be read by the stream.
+
+		- `options.flags` - see `open`.
+		- `options.mode` - see `open`.
+		- `options.autoClose` - whether the file should be closed automatically
+			once the stream is fully consumed.
+		- `options.start` - starting position in bytes (inclusive).
+		- `options.end` - end position in bytes (non-inclusive).
+	**/
+	static function createReadStream(path:FilePath, ?options:FileReadStreamCreationOptions):FileReadStream;
+
+	// static function createWriteStream(path:FilePath, ?options:{?flags:FileOpenFlags, ?mode:FilePermissions, ?autoClose:Bool, ?start:Int}):FileWriteStream;
+
+	/**
+		Returns `true` if the file or directory specified by `path` exists.
+
+		The result of this call should not be used in a condition before a call to
+		e.g. `open`, because this would introduce a race condition (the file could
+		be deleted after the `exists` call, but before the `open` call). Instead,
+		the latter function should be called immediately and errors should be
+		handled with a `try ... catch` block.
+	**/
+	static function exists(path:FilePath):Bool;
+
+	static function link(existingPath:FilePath, newPath:FilePath):Void;
+
+	/**
+		Creates a directory at the path `path`, with file mode `mode`.
+
+		If `recursive` is `false` (default), this function can only create one
+		directory at a time, the last component of `path`. If `recursive` is `true`,
+		intermediate directories will be created as needed.
+	**/
+	static function mkdir(path:FilePath, ?recursive:Bool = false, ?mode:FilePermissions /* 0777 */):Void;
+
+	/**
+		Creates a unique temporary directory. `prefix` should be a path template
+		ending in six `X` characters, which will be replaced with random characters.
+		Returns the path to the created directory.
+
+		The generated directory needs to be manually deleted by the process.
+	**/
+	static function mkdtemp(prefix:FilePath):FilePath;
+
+	/**
+		Opens the file located at `path`.
+	**/
+	static function open(path:FilePath, ?flags:FileOpenFlags /* a */, ?mode:FilePermissions /* 0666 */, ?binary:Bool = true):File;
+
+	/**
+		Reads the contents of a directory specified by `path`. Returns an array of
+		`FilePath`s relative to the specified directory (i.e. the paths are not
+		absolute). The array will not include `.` or `..`.
+	**/
+	static function readdir(path:FilePath):Array<FilePath>;
+
+	/**
+		Same as `readdir`, but returns an array of `DirectoryEntry` values instead.
+	**/
+	static function readdirTypes(path:FilePath):Array<DirectoryEntry>;
+
+	/**
+		Reads all the bytes of the file located at `path`.
+	**/
+	static function readFile(path:FilePath, ?flags:FileOpenFlags /* r */):Bytes;
+
+	/**
+		Returns the contents (target path) of the symbolic link located at `path`.
+	**/
+	static function readlink(path:FilePath):FilePath;
+
+	/**
+		Returns the canonical path name of `path` (which may be a relative path)
+		by resolving `.`, `..`, and symbolic links.
+	**/
+	static function realpath(path:FilePath):FilePath;
+
+	/**
+		Renames the file or directory located at `oldPath` to `newPath`. If a file
+		already exists at `newPath`, it is overwritten. If a directory already
+		exists at `newPath`, an exception is thrown.
+	**/
+	static function rename(oldPath:FilePath, newPath:FilePath):Void;
+
+	/**
+		Deletes the directory located at `path`. If the directory is not empty or
+		cannot be deleted, an error is thrown.
+	**/
+	static function rmdir(path:FilePath):Void;
+
+	/**
+		Returns information about the file located at `path`.
+
+		If `path` points to a symbolic link, this function will return information
+		about the target file, not the symbolic link itself, unless `followSymLinks`
+		is set to `false`.
+	**/
+	static function stat(path:FilePath, ?followSymLinks:Bool = true):asys.FileStat;
+
+	/**
+		Creates a symbolic link at `path`, pointing to `target`.
+
+		The `type` argument is ignored on all platforms except `Windows`.
+	**/
+	static function symlink(target:FilePath, path:FilePath, ?type:SymlinkType = SymlinkType.SymlinkDir):Void;
+
+	/**
+		Truncates the file located at `path` to exactly `len` bytes. If the file was
+		larger than `len` bytes, the extra data is lost. If the file was smaller
+		than `len` bytes, the file is extended with null bytes.
+	**/
+	static function truncate(path:FilePath, ?len:Int = 0):Void;
+
+	/**
+		Deletes the file located at `path`.
+	**/
+	static function unlink(path:FilePath):Void;
+
+	/**
+		Modifies the system timestamps of the file located at `path`.
+	**/
+	static function utimes(path:FilePath, atime:Date, mtime:Date):Void;
+
+	/**
+		Creates a file watcher for `path`.
+
+		@param recursive If `true`, the file watcher will signal for changes in
+			sub-directories of `path` as well.
+	**/
+	static function watch(path:FilePath, ?recursive:Bool = false):FileWatcher;
+
+	/**
+		Writes `data` to the file located at `path`.
+	**/
+	static function writeFile(path:FilePath, data:Bytes, ?flags:FileOpenFlags /* w */, ?mode:FilePermissions /* 0666 */):Void;
+}

+ 47 - 0
std/asys/FileWatcher.hx

@@ -0,0 +1,47 @@
+package asys;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+import haxe.io.FilePath;
+
+/**
+	File watchers can be obtained with the `asys.FileSystem.watch` method.
+	Instances of this class will emit signals whenever any file in their watched
+	path is modified.
+**/
+class FileWatcher {
+	/**
+		Emitted when a watched file is modified.
+	**/
+	public final changeSignal:Signal<FileWatcherEvent> = new ArraySignal();
+
+	/**
+		Emitted when `this` watcher is fully closed. No further signals will be
+		emitted.
+	**/
+	public final closeSignal:Signal<NoData> = new ArraySignal();
+
+	/**
+		Emitted when an error occurs.
+	**/
+	public final errorSignal:Signal<Error> = new ArraySignal();
+
+	private var native:FileWatcherNative;
+
+	private function new(filename:FilePath, recursive:Bool) {}
+
+	/**
+		Closes `this` watcher. This operation is asynchronous and will emit the
+		`closeSignal` once done. If `listener` is given, it will be added to the
+		`closeSignal`.
+	**/
+	public function close(?listener:Listener<NoData>):Void {
+		if (listener != null)
+			closeSignal.once(listener);
+	}
+
+	extern public function ref():Void;
+
+	extern public function unref():Void;
+}

+ 14 - 0
std/asys/FileWatcherEvent.hx

@@ -0,0 +1,14 @@
+package asys;
+
+import haxe.io.FilePath;
+
+/**
+	Events emitted by the `changeSignal` of a `sys.FileWatcher`. Any file change
+	consists of a name change (`Rename`), a content change (`Change`), or both
+	(`RenameChange`).
+**/
+enum FileWatcherEvent {
+	Rename(newPath:FilePath);
+	Change(path:FilePath);
+	RenameChange(path:FilePath);
+}

+ 71 - 0
std/asys/Net.hx

@@ -0,0 +1,71 @@
+package asys;
+
+import haxe.NoData;
+import haxe.async.*;
+import asys.net.*;
+import asys.net.SocketOptions.SocketConnectTcpOptions;
+import asys.net.SocketOptions.SocketConnectIpcOptions;
+import asys.net.Server.ServerOptions;
+import asys.net.Server.ServerListenTcpOptions;
+import asys.net.Server.ServerListenIpcOptions;
+
+enum SocketConnect {
+	Tcp(options:SocketConnectTcpOptions);
+	Ipc(options:SocketConnectIpcOptions);
+}
+
+enum ServerListen {
+	Tcp(options:ServerListenTcpOptions);
+	Ipc(options:ServerListenIpcOptions);
+}
+
+typedef SocketCreationOptions = SocketOptions & {?connect:SocketConnect};
+
+typedef ServerCreationOptions = ServerOptions & {?listen:ServerListen};
+
+/**
+	Network utilities.
+**/
+class Net {
+	/**
+		Constructs a socket with the given `options`. If `options.connect` is
+		given, an appropriate `connect` method is called on the socket. If `cb` is
+		given, it is passed to the `connect` method, so it will be called once the
+		socket successfully connects or an error occurs during connecting.
+
+		The `options` object is given both to the `Socket` constructor and to the
+		`connect` method.
+	**/
+	public static function createConnection(options:SocketCreationOptions, ?cb:Callback<NoData>):Socket {
+		var socket = Socket.create(options);
+		if (options.connect != null)
+			switch (options.connect) {
+				case Tcp(options):
+					socket.connectTcp(options, cb);
+				case Ipc(options):
+					socket.connectIpc(options, cb);
+			}
+		return socket;
+	}
+
+	/**
+		Constructs a server with the given `options`. If `options.listen` is
+		given, an appropriate `listen` method is called on the server. If `cb` is
+		given, it is passed to the `listen` method, so it will be called for each
+		client that connects to the server.
+
+		The `options` object is given both to the `Server` constructor and to the
+		`listen` method.
+	**/
+	public static function createServer(?options:ServerCreationOptions, ?listener:Listener<Socket>):Server {
+		var server = new Server(options);
+		if (options.listen != null)
+			switch (options.listen) {
+				case Tcp(options):
+					server.listenTcp(options, listener);
+				case Ipc(options):
+					server.listenIpc(options, listener);
+			}
+		return server;
+	}
+}

+ 178 - 0
std/asys/Process.hx

@@ -0,0 +1,178 @@
+package asys;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+import haxe.io.*;
+import asys.net.Socket;
+import asys.io.*;
+import asys.uv.UVProcessSpawnFlags;
+
+/**
+	Options for spawning a process. See `Process.spawn`.
+**/
+typedef ProcessSpawnOptions = {
+	?cwd:FilePath,
+	?env:Map<String, String>,
+	?argv0:String,
+	?stdio:Array<ProcessIO>,
+	?detached:Bool,
+	?uid:Int,
+	?gid:Int,
+	// ?shell:?,
+	?windowsVerbatimArguments:Bool,
+	?windowsHide:Bool
+};
+
+/**
+	Class representing a spawned process.
+**/
+class Process {
+	/**
+		Execute the given `command` with `args` (none by default). `options` can be
+		specified to change the way the process is spawned.
+
+		`options.stdio` is an optional array of `ProcessIO` specifications which
+		can be used to define the file descriptors for the new process:
+
+		- `Ignore` - skip the current position. No stream or pipe will be open for
+			this index.
+		- `Inherit` - inherit the corresponding file descriptor from the current
+			process. Shares standard input, standard output, and standard error in
+			index 0, 1, and 2, respectively. In index 3 or higher, `Inherit` has the
+			same effect as `Ignore`.
+		- `Pipe(readable, writable, ?pipe)` - create or use a pipe. `readable` and
+			`writable` specify whether the pipe will be readable and writable from
+			the point of view of the spawned process. If `pipe` is given, it is used
+			directly, otherwise a new pipe is created.
+		- `Ipc` - create an IPC (inter-process comunication) pipe. Only one may be
+			specified in `options.stdio`. This special pipe will not have an entry in
+			the `stdio` array of the resulting process; instead, messages can be sent
+			using the `send` method, and received over `messageSignal`. IPC pipes
+			allow sending and receiving structured Haxe data, as well as connected
+			sockets and pipes.
+
+		Pipes are made available in the `stdio` array afther the process is
+		spawned. Standard file descriptors have their own variables:
+
+		- `stdin` - set to point to a pipe in index 0, if it exists and is
+			read-only for the spawned process.
+		- `stdout` - set to point to a pipe in index 1, if it exists and is
+			write-only for the spawned process.
+		- `stderr` - set to point to a pipe in index 2, if it exists and is
+			write-only for the spawned process.
+
+		If `options.stdio` is not given,
+		`[Pipe(true, false), Pipe(false, true), Pipe(false, true)]` is used as a
+		default.
+
+		@param options.cwd Path to the working directory. Defaults to the current
+			working directory if not given.
+		@param options.env Environment variables. Defaults to the environment
+			variables of the current process if not given.
+		@param options.argv0 First entry in the `argv` array for the spawned
+			process. Defaults to `command` if not given.
+		@param options.stdio Array of `ProcessIO` specifications, see above.
+		@param options.detached When `true`, creates a detached process which can
+			continue running after the current process exits. Note that `unref` must
+			be called on the spawned process otherwise the event loop of the current
+			process is kept allive.
+		@param options.uid User identifier.
+		@param options.gid Group identifier.
+		@param options.windowsVerbatimArguments (Windows only.) Do not perform
+			automatic quoting or escaping of arguments.
+		@param options.windowsHide (Windows only.) Automatically hide the window of
+			the spawned process.
+	**/
+	extern public static function spawn(command:String, ?args:Array<String>, ?options:ProcessSpawnOptions):Process;
+
+	/**
+		Emitted when `this` process and all of its pipes are closed.
+	**/
+	public final closeSignal:Signal<NoData> = new ArraySignal();
+
+	// public final disconnectSignal:Signal<NoData> = new ArraySignal(); // IPC
+
+	/**
+		Emitted when an error occurs during communication with `this` process.
+	**/
+	public final errorSignal:Signal<Error> = new ArraySignal();
+
+	/**
+		Emitted when `this` process exits, potentially due to a signal.
+	**/
+	public final exitSignal:Signal<ProcessExit> = new ArraySignal();
+
+	/**
+		Emitted when a message is received over IPC. The process must be created
+		with an `Ipc` entry in `options.stdio`; see `Process.spawn`.
+	**/
+	public var messageSignal(default, null):Signal<IpcMessage>;
+
+	public var connected(default, null):Bool = false;
+	public var killed:Bool;
+
+	extern private function get_pid():Int;
+
+	/**
+		Process identifier of `this` process. A PID uniquely identifies a process
+		on its host machine for the duration of its lifetime.
+	**/
+	public var pid(get, never):Int;
+
+	/**
+		Standard input. May be `null` - see `options.stdio` in `spawn`.
+	**/
+	public var stdin:IWritable;
+
+	/**
+		Standard output. May be `null` - see `options.stdio` in `spawn`.
+	**/
+	public var stdout:IReadable;
+
+	/**
+		Standard error. May be `null` - see `options.stdio` in `spawn`.
+	**/
+	public var stderr:IReadable;
+
+	/**
+		Pipes created between the current (host) process and `this` (spawned)
+		process. The order corresponds to the `ProcessIO` specifiers in
+		`options.stdio` in `spawn`. This array can be used to access non-standard
+		pipes, i.e. file descriptors 3 and higher, as well as file descriptors 0-2
+		with non-standard read/write access.
+	**/
+	public var stdio:Array<Socket>;
+
+	var ipc:Socket;
+	var ipcOut:asys.io.IpcSerializer;
+	var ipcIn:asys.io.IpcUnserializer;
+
+	// public function disconnect():Void; // IPC
+
+	/**
+		Send a signal to `this` process.
+	**/
+	extern public function kill(?signal:Int = 7):Void;
+
+	/**
+		Close `this` process handle and all pipes in `stdio`.
+	**/
+	extern public function close(?cb:Callback<NoData>):Void;
+
+	/**
+		Send `data` to the process over the IPC channel. The process must be
+		created with an `Ipc` entry in `options.stdio`; see `Process.spawn`.
+	**/
+	public function send(message:IpcMessage):Void {
+		if (!connected)
+			throw "IPC not connected";
+		ipcOut.write(message);
+	}
+
+	extern public function ref():Void;
+
+	extern public function unref():Void;
+
+	private function new() {}
+}

+ 16 - 0
std/asys/ProcessExit.hx

@@ -0,0 +1,16 @@
+package asys;
+
+/**
+	Represents how a process exited.
+**/
+typedef ProcessExit = {
+	/**
+		Exit code of the process. Non-zero values usually indicate an error.
+		Specific meanings of exit codes differ from program to program.
+	**/
+	var code:Int;
+	/**
+		Signal that cause the process to exit, or zero if none.
+	**/
+	var signal:Int;
+};

+ 10 - 0
std/asys/ProcessIO.hx

@@ -0,0 +1,10 @@
+package asys;
+
+enum ProcessIO {
+	Ignore;
+	Inherit;
+	Pipe(readable:Bool, writable:Bool, ?pipe:asys.net.Socket);
+	Ipc;
+	// Stream(_);
+	// Fd(_);
+}

+ 9 - 0
std/asys/SymlinkType.hx

@@ -0,0 +1,9 @@
+package asys;
+
+enum abstract SymlinkType(Int) {
+	var SymlinkFile = 0;
+	var SymlinkDir = 1;
+	var SymlinkJunction = 2; // Windows only
+
+	inline function get_raw():Int return this;
+}

+ 34 - 0
std/asys/Timer.hx

@@ -0,0 +1,34 @@
+package asys;
+
+class Timer {
+	public static function delay(f:() -> Void, timeMs:Int):Timer {
+		var t = new Timer(timeMs);
+		t.run = function() {
+			t.stop();
+			f();
+		};
+		return t;
+	}
+
+	public static function measure<T>(f:()->T, ?pos:haxe.PosInfos):T {
+		var t0 = stamp();
+		var r = f();
+		haxe.Log.trace((stamp() - t0) + "s", pos);
+		return r;
+	}
+
+	public static function stamp():Float {
+		// TODO: libuv?
+		return Sys.time();
+	}
+
+	public function new(timeMs:Int) {}
+
+	public dynamic function run():Void {}
+
+	extern public function stop():Void;
+
+	extern public function ref():Void;
+
+	extern public function unref():Void;
+}

+ 47 - 0
std/asys/io/AsyncFile.hx

@@ -0,0 +1,47 @@
+package asys.io;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+import haxe.io.Bytes;
+import haxe.io.Encoding;
+import asys.*;
+
+/**
+	This class provides methods for asynchronous operations on files instances.
+	For synchronous operations, see `asys.io.File`. To obtain an instance of
+	this class, use the `async` field of `asys.io.File`.
+
+	```haxe
+	var file = asys.FileSystem.open("example.txt", "r");
+	file.async.readFile(contents -> trace(contents.toString()));
+	```
+
+	All methods here are asynchronous versions of the functions in
+	`asys.io.File`. Please see them for a description of the arguments and
+	use of each method.
+
+	Any synchronous method that returns no value (`Void` return type) has an
+	extra `callback:Callback<NoData>` argument.
+
+	Any synchronous method that returns a value has an extra
+	`callback:Callback<T>` argument, where `T` is the return type of the
+	synchronous method.
+
+	Errors are communicated through the callbacks or in some cases thrown
+	immediately.
+**/
+extern class AsyncFile {
+	function chmod(mode:FilePermissions, callback:Callback<NoData>):Void;
+	function chown(uid:Int, gid:Int, callback:Callback<NoData>):Void;
+	function close(callback:Callback<NoData>):Void;
+	function datasync(callback:Callback<NoData>):Void;
+	function readBuffer(buffer:Bytes, offset:Int, length:Int, position:Int, callback:Callback<{bytesRead:Int, buffer:Bytes}>):Void;
+	function readFile(callback:Callback<Bytes>):Void;
+	function stat(callback:Callback<FileStat>):Void;
+	function sync(callback:Callback<NoData>):Void;
+	function truncate(?len:Int = 0, callback:Callback<NoData>):Void;
+	function utimes(atime:Date, mtime:Date, callback:Callback<NoData>):Void;
+	function writeBuffer(buffer:Bytes, offset:Int, length:Int, position:Int, callback:Callback<{bytesWritten:Int, buffer:Bytes}>):Void;
+	function writeString(str:String, ?position:Int, ?encoding:Encoding, callback:Callback<{bytesWritten:Int, buffer:Bytes}>):Void;
+}

+ 88 - 0
std/asys/io/File.hx

@@ -0,0 +1,88 @@
+package asys.io;
+
+import haxe.Error;
+import haxe.io.Bytes;
+import haxe.io.Encoding;
+import asys.*;
+
+/**
+	Class representing an open file. Some methods in this class are instance
+	variants of the same methods in `asys.FileSystem`.
+**/
+extern class File {
+	private function get_async():AsyncFile;
+
+	var async(get, never):AsyncFile;
+
+	/**
+		See `asys.FileSystem.chmod`.
+	**/
+	function chmod(mode:FilePermissions):Void;
+
+	/**
+		See `asys.FileSystem.chown`.
+	**/
+	function chown(uid:Int, gid:Int):Void;
+
+	/**
+		Closes the file. Any operation after this method is called is invalid.
+	**/
+	function close():Void;
+
+	/**
+		Same as `sync`, but metadata is not flushed unless needed for subsequent
+		data reads to be correct. E.g. changes to the modification times are not
+		flushed, but changes to the filesize do.
+	**/
+	function datasync():Void;
+
+	/**
+		Reads a part of `this` file into the given `buffer`.
+
+		@param buffer Buffer to which data will be written.
+		@param offset Position in `buffer` at which to start writing.
+		@param length Number of bytes to read from `this` file.
+		@param position Position in `this` file at which to start reading.
+	**/
+	function readBuffer(buffer:Bytes, offset:Int, length:Int, position:Int):{bytesRead:Int, buffer:Bytes};
+
+	/**
+		Reads the entire contents of `this` file.
+	**/
+	function readFile():Bytes;
+
+	/**
+		See `asys.FileSystem.stat`.
+	**/
+	function stat():FileStat;
+
+	/**
+		Flushes all modified data and metadata of `this` file to the disk.
+	**/
+	function sync():Void;
+
+	/**
+		See `asys.FileSystem.truncate`.
+	**/
+	function truncate(?len:Int = 0):Void;
+
+	/**
+		See `asys.FileSystem.utimes`.
+	**/
+	function utimes(atime:Date, mtime:Date):Void;
+
+	/**
+		Writes a part of the given `buffer` into `this` file.
+
+		@param buffer Buffer from which data will be read.
+		@param offset Position in `buffer` at which to start reading.
+		@param length Number of bytes to write to `this` file.
+		@param position Position in `this` file at which to start writing.
+	**/
+	function writeBuffer(buffer:Bytes, offset:Int, length:Int, position:Int):{bytesWritten:Int, buffer:Bytes};
+
+	/**
+		Writes a string to `this` file at `position`.
+	**/
+	function writeString(str:String, ?position:Int, ?encoding:Encoding):{bytesWritten:Int, buffer:Bytes};
+}

+ 42 - 0
std/asys/io/FileInput.hx

@@ -0,0 +1,42 @@
+package asys.io;
+
+import haxe.io.Bytes;
+
+class FileInput extends haxe.io.Input {
+	final file:asys.io.File;
+	var position:Int = 0;
+
+	function new(file:asys.io.File) {
+		this.file = file;
+	}
+
+	public function seek(p:Int, pos:sys.io.FileSeek):Void {
+		position = (switch (pos) {
+			case SeekBegin: p;
+			case SeekCur: position + p;
+			case SeekEnd: file.stat().size + p;
+		});
+	}
+
+	public function tell():Int {
+		return position;
+	}
+
+	override public function readByte():Int {
+		var buf = Bytes.alloc(1);
+		file.readBuffer(buf, 0, 1, position++);
+		return buf.get(0);
+	}
+
+	override public function readBytes(buf:Bytes, pos:Int, len:Int):Int {
+		if (pos < 0 || len < 0 || pos + len > buf.length)
+			throw haxe.io.Error.OutsideBounds;
+		var read = file.readBuffer(buf, pos, len, position).bytesRead;
+		position += read;
+		return read;
+	}
+
+	override public function close():Void {
+		file.close();
+	}
+}

+ 46 - 0
std/asys/io/FileOutput.hx

@@ -0,0 +1,46 @@
+package asys.io;
+
+import haxe.io.Bytes;
+
+class FileOutput extends haxe.io.Output {
+	final file:asys.io.File;
+	var position:Int = 0;
+
+	function new(file:asys.io.File) {
+		this.file = file;
+	}
+
+	public function seek(p:Int, pos:sys.io.FileSeek):Void {
+		position = (switch (pos) {
+			case SeekBegin: p;
+			case SeekCur: position + p;
+			case SeekEnd: file.stat().size + p;
+		});
+	}
+
+	public function tell():Int {
+		return position;
+	}
+
+	override public function writeByte(byte:Int):Void {
+		var buf = Bytes.alloc(1);
+		buf.set(1, byte);
+		file.writeBuffer(buf, 0, 1, position++);
+	}
+
+	override public function writeBytes(buf:Bytes, pos:Int, len:Int):Int {
+		if (pos < 0 || len < 0 || pos + len > buf.length)
+			throw haxe.io.Error.OutsideBounds;
+		var written = file.writeBuffer(buf, pos, len, position).bytesWritten;
+		position += written;
+		return written;
+	}
+
+	override public function flush():Void {
+		file.datasync();
+	}
+
+	override public function close():Void {
+		file.close();
+	}
+}

+ 20 - 0
std/asys/io/FileReadStream.hx

@@ -0,0 +1,20 @@
+package asys.io;
+
+import haxe.NoData;
+import haxe.async.Signal;
+
+typedef FileReadStreamOptions = {
+	?autoClose:Bool,
+	?start:Int,
+	?end:Int,
+	?highWaterMark:Int
+};
+
+extern class FileReadStream extends haxe.io.Readable {
+	final openSignal:Signal<File>;
+	final readySignal:Signal<NoData>;
+
+	var bytesRead:Int;
+	var path:String;
+	var pending:Bool;
+}

+ 13 - 0
std/asys/io/FileWriteStream.hx

@@ -0,0 +1,13 @@
+package asys.io;
+
+import haxe.NoData;
+import haxe.async.Signal;
+
+extern class FileWriteStream extends haxe.io.Writable {
+	final openSignal:Signal<File>;
+	final readySignal:Signal<NoData>;
+
+	var bytesWritten:Int;
+	var path:String;
+	var pending:Bool;
+}

+ 21 - 0
std/asys/io/IpcMessage.hx

@@ -0,0 +1,21 @@
+package asys.io;
+
+import asys.net.Socket;
+
+/**
+	A message sent over an IPC channel. Sent with `Process.send` to a sub-process
+	or with `CurrentProcess.send` to the parent process. Received with
+	`Process.messageSignal` from a sub-process, or `CurrentProcess.messageSignal`
+	from the parent process.
+**/
+typedef IpcMessage = {
+	/**
+		The actual message. May be any data that is serializable with
+		`haxe.Serializer`.
+	**/
+	var message:Dynamic;
+	/**
+		Sockets and pipes associated with the message. Must be connected.
+	**/
+	var ?sockets:Array<Socket>;
+};

+ 50 - 0
std/asys/io/IpcSerializer.hx

@@ -0,0 +1,50 @@
+package asys.io;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+import haxe.io.*;
+import asys.net.Socket;
+
+/**
+	Class used internally to send messages and handles over an IPC channel. See
+	`Process.spawn` for creating an IPC channel and `Process.send` for sending
+	messages over the channel.
+**/
+class IpcSerializer {
+	static var activeSerializer:IpcSerializer = null;
+	static var dummyBuffer = Bytes.ofString("s");
+
+	final pipe:Socket;
+	// final chunkSockets:Array<Socket> = [];
+
+	function new(pipe:Socket) {
+		this.pipe = pipe;
+	}
+
+	/**
+		Sends `data` over the pipe. `data` will be serialized with a call to
+		`haxe.Serializer.run`. Objects of type `Socket` can be sent along with the
+		data if `handles` is provided.
+	**/
+	public function write(message:IpcMessage):Void {
+		activeSerializer = this;
+		if (message.sockets != null)
+			for (socket in message.sockets) {
+				if (!socket.connected)
+					throw "cannot send unconnected socket over IPC";
+				pipe.writeHandle(dummyBuffer, socket);
+			}
+		var serial = haxe.Serializer.run(message.message);
+		pipe.write(Bytes.ofString('${serial.length}:$serial'));
+		// chunkSockets.resize(0);
+		activeSerializer = null;
+	}
+
+	/**
+		// TODO: see `Socket.hxUnserialize` comment
+		Sends `data` over the pipe. `data` will be serialized with a call to
+		`haxe.Serializer.run`. However, objects of type `asys.async.net.Socket`
+		will also be correctly serialized and can be received by the other end.
+	**/
+}

+ 85 - 0
std/asys/io/IpcUnserializer.hx

@@ -0,0 +1,85 @@
+package asys.io;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+import haxe.io.*;
+import asys.net.Socket;
+
+/**
+	Class used internally to receive messages and handles over an IPC channel.
+	See `CurrentProcess.initIpc` for initialising IPC for a process.
+**/
+class IpcUnserializer {
+	static var activeUnserializer:IpcUnserializer = null;
+
+	public final messageSignal:Signal<IpcMessage> = new ArraySignal();
+	public final errorSignal:Signal<Dynamic> = new ArraySignal();
+
+	final pipe:Socket;
+	// var chunkSockets:Array<Socket> = [];
+	var chunkLenbuf:String = "";
+	var chunkBuf:StringBuf;
+	var chunkSize:Null<Int> = 0;
+	var chunkSocketCount:Int = 0;
+
+	function new(pipe:Socket) {
+		this.pipe = pipe;
+		pipe.dataSignal.on(handleData);
+	}
+
+	function handleData(data:Bytes):Void {
+		if (data.length == 0)
+			return;
+		try {
+			var data = data.toString();
+			while (data != null) {
+				if (chunkSize == 0) {
+					chunkLenbuf += data;
+					var colonPos = chunkLenbuf.indexOf(":");
+					if (colonPos != -1) {
+						chunkSocketCount = 0;
+						while (chunkLenbuf.charAt(chunkSocketCount) == "s")
+							chunkSocketCount++;
+						chunkSize = Std.parseInt(chunkLenbuf.substr(chunkSocketCount, colonPos));
+						if (chunkSize == null || chunkSize <= 0) {
+							chunkSize = 0;
+							throw "invalid chunk size received";
+						}
+						chunkBuf = new StringBuf();
+						chunkBuf.add(chunkLenbuf.substr(colonPos + 1));
+						chunkLenbuf = "";
+						// chunkSockets.resize(0);
+					}
+				} else {
+					chunkBuf.add(data);
+				}
+				data = null;
+				if (chunkSize != 0) {
+					if (chunkBuf.length >= chunkSize) {
+						var serial = chunkBuf.toString();
+						if (serial.length > chunkSize) {
+							data = serial.substr(chunkSize);
+							serial = serial.substr(0, chunkSize);
+						}
+						chunkBuf = null;
+						var chunkSockets = [];
+						if (chunkSocketCount > pipe.handlesPending)
+							throw "not enough handles received";
+						for (i in 0...chunkSocketCount)
+							chunkSockets.push(pipe.readHandle());
+						activeUnserializer = this;
+						var message = haxe.Unserializer.run(serial);
+						messageSignal.emit({message: message, sockets: chunkSockets});
+						chunkSize = 0;
+						chunkSocketCount = 0;
+						// chunkSockets.resize(0);
+						activeUnserializer = null;
+					}
+				}
+			}
+		} catch (e:Dynamic) {
+			errorSignal.emit(e);
+		}
+	}
+}

+ 21 - 0
std/asys/net/Address.hx

@@ -0,0 +1,21 @@
+package asys.net;
+
+import haxe.io.Bytes;
+
+/**
+	Represents a resolved IP address. The methods from `asys.net.AddressTools`
+	are always available on `Address` instances.
+**/
+@:using(asys.net.AddressTools)
+enum Address {
+	/**
+		32-bit IPv4 address. As an example, the IP address `127.0.0.1` is
+		represented as `Ipv4(0x7F000001)`.
+	**/
+	Ipv4(raw:Int);
+
+	/**
+		128-bit IPv6 address.
+	**/
+	Ipv6(raw:Bytes);
+}

+ 223 - 0
std/asys/net/AddressTools.hx

@@ -0,0 +1,223 @@
+package asys.net;
+
+import haxe.io.Bytes;
+import asys.net.IpFamily;
+
+/**
+	Methods for converting to and from `Address` instances.
+**/
+class AddressTools {
+	static final v4re = {
+		final v4seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
+		final v4str = '${v4seg}\\.${v4seg}\\.${v4seg}\\.${v4seg}';
+		new EReg('^${v4str}$$', "");
+	};
+
+	static final v6re = {
+		final v4seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
+		final v4str = '${v4seg}\\.${v4seg}\\.${v4seg}\\.${v4seg}';
+		final v6seg = "(?:[0-9a-fA-F]{1,4})";
+		new EReg("^("
+			+ '(?:${v6seg}:){7}(?:${v6seg}|:)|'
+			+ '(?:${v6seg}:){6}(?:${v4str}|:${v6seg}|:)|'
+			+ '(?:${v6seg}:){5}(?::${v4str}|(:${v6seg}){1,2}|:)|'
+			+ '(?:${v6seg}:){4}(?:(:${v6seg}){0,1}:${v4str}|(:${v6seg}){1,3}|:)|'
+			+ '(?:${v6seg}:){3}(?:(:${v6seg}){0,2}:${v4str}|(:${v6seg}){1,4}|:)|'
+			+ '(?:${v6seg}:){2}(?:(:${v6seg}){0,3}:${v4str}|(:${v6seg}){1,5}|:)|'
+			+ '(?:${v6seg}:){1}(?:(:${v6seg}){0,4}:${v4str}|(:${v6seg}){1,6}|:)|'
+			+ '(?::((?::${v6seg}){0,5}:${v4str}|(?::${v6seg}){1,7}|:))'
+			+ ")$", // "(%[0-9a-zA-Z]{1,})?$", // TODO: interface not supported
+			"");
+	};
+
+	/**
+		Returns the IP address representing all hosts for the given IP family.
+
+		- For IPv4, the address is `0.0.0.0`.
+		- For IPv6, the address is `::`.
+	**/
+	public static function all(family:IpFamily):Address {
+		return (switch (family) {
+			case Ipv4: Ipv4(0);
+			case Ipv6: Ipv6(Bytes.ofHex("00000000000000000000000000000000"));
+		});
+	}
+
+	/**
+		Returns the IP address representing the local hosts for the given IP family.
+
+		- For IPv4, the address is `127.0.0.1`.
+		- For IPv6, the address is `::1`.
+	**/
+	public static function localhost(family:IpFamily):Address {
+		return (switch (family) {
+			case Ipv4: Ipv4(0x7F000001);
+			case Ipv6: Ipv6(Bytes.ofHex("00000000000000000000000000000001"));
+		});
+	}
+
+	/**
+		Converts an `Address` to a `String`.
+
+		- IPv4 addresses are represented with the dotted quad format, e.g.
+			`192.168.0.1`.
+		- IPv6 addresses are represented with the standard lowercased hexadecimal
+			representation, with `::` used to mark a long stretch of zeros.
+	**/
+	public static function toString(address:Address):String {
+		return (switch (address) {
+			case Ipv4(ip):
+				'${ip >>> 24}.${(ip >> 16) & 0xFF}.${(ip >> 8) & 0xFF}.${ip & 0xFF}';
+			case Ipv6(ip):
+				var groups = [for (i in 0...8) (ip.get(i * 2) << 8) | ip.get(i * 2 + 1)];
+				var longestRun = -1;
+				var longestPos = -1;
+				for (i in 0...8) {
+					if (groups[i] != 0)
+						continue;
+					var run = 1;
+					// TODO: skip if the longest run cannot be beaten
+					for (j in i + 1...8) {
+						if (groups[j] != 0)
+							break;
+						run++;
+					}
+					if (run > longestRun) {
+						longestRun = run;
+						longestPos = i;
+					}
+				}
+				inline function hex(groups:Array<Int>):String {
+					return groups.map(value -> StringTools.hex(value, 1).toLowerCase()).join(":");
+				}
+				if (longestRun > 1) {
+					hex(groups.slice(0, longestPos)) + "::" + hex(groups.slice(longestPos + longestRun));
+				} else {
+					hex(groups);
+				}
+		});
+	}
+
+	/**
+		Returns `true` if `address` represents a valid IPv4 or IPv6 address.
+	**/
+	public static function isIp(address:String):Bool {
+		return isIpv4(address) || isIpv6(address);
+	}
+
+	/**
+		Returns `true` if `address` represents a valid IPv4 address.
+	**/
+	public static function isIpv4(address:String):Bool {
+		return v4re.match(address);
+	}
+
+	/**
+		Returns `true` if `address` represents a valid IPv6 address.
+	**/
+	public static function isIpv6(address:String):Bool {
+		return v6re.match(address);
+	}
+
+	/**
+		Tries to convert the `String` `address` to an `Address` instance. Returns
+		the parsed `Address` or `null` if `address` does not represent a valid IP
+		address.
+	**/
+	public static function toIp(address:String):Null<Address> {
+		var ipv4 = toIpv4(address);
+		return ipv4 != null ? ipv4 : toIpv6(address);
+	}
+
+	/**
+		Tries to convert the `String` `address` to an IPv4 `Address` instance.
+		Returns the parsed `Address` or `null` if `address` does not represent a
+		valid IPv4 address.
+	**/
+	public static function toIpv4(address:String):Null<Address> {
+		if (!isIpv4(address))
+			return null;
+		var components = address.split(".").map(Std.parseInt);
+		return Ipv4((components[0] << 24) | (components[1] << 16) | (components[2] << 8) | components[3]);
+	}
+
+	/**
+		Tries to convert the `String` `address` to an IPv6 `Address` instance.
+		Returns the parsed `Address` or `null` if `address` does not represent a
+		valid IPv6 address.
+	**/
+	public static function toIpv6(address:String):Null<Address> {
+		if (!isIpv6(address))
+			return null;
+		var buffer = Bytes.alloc(16);
+		buffer.fill(0, 16, 0);
+		function parse(component:String, res:Int):Void {
+			var value = Std.parseInt('0x0$component');
+			buffer.set(res, value >> 8);
+			buffer.set(res + 1, value & 0xFF);
+		}
+		var stretch = address.split("::");
+		var components = stretch[0].split(":");
+		for (i in 0...components.length)
+			parse(components[i], i * 2);
+		if (stretch.length > 1) {
+			var end = 16;
+			components = stretch[1].split(":");
+			if (isIpv4(components[components.length - 1])) {
+				end -= 4;
+				var ip = components.pop().split(".").map(Std.parseInt);
+				for (i in 0...4)
+					buffer.set(end + i, ip[i]);
+			}
+			end -= components.length * 2;
+			for (i in 0...components.length)
+				parse(components[i], end + i);
+		}
+		return Ipv6(buffer);
+	}
+
+	/**
+		Returns the IPv6 version of the given `address`. IPv6 addresses are
+		returned unmodified, IPv4 addresses are mapped to IPv6 using the
+		`:ffff:0:0/96` IPv4 transition prefix.
+
+		```haxe
+		"127.0.0.1".toIpv4().mapToIpv6().toString(); // ::ffff:7f00:1
+		```
+	**/
+	public static function mapToIpv6(address:Address):Address {
+		return (switch (address) {
+			case Ipv4(ip):
+				var buffer = Bytes.alloc(16);
+				buffer.set(10, 0xFF);
+				buffer.set(11, 0xFF);
+				buffer.set(12, ip >>> 24);
+				buffer.set(13, (ip >> 16) & 0xFF);
+				buffer.set(14, (ip >> 8) & 0xFF);
+				buffer.set(15, ip & 0xFF);
+				Ipv6(buffer);
+			case _:
+				address;
+		});
+	}
+
+	/**
+		Returns `true` if `a` and `b` are the same IP address.
+
+		If `ipv6mapped` is `true`, bot `a` and `b` are mapped to IPv6 (using
+		`mapToIpv6`) before the comparison.
+	**/
+	public static function equals(a:Address, b:Address, ?ipv6mapped:Bool = false):Bool {
+		if (ipv6mapped) {
+			return (switch [mapToIpv6(a), mapToIpv6(b)] {
+				case [Ipv6(a), Ipv6(b)]: a.compare(b) == 0;
+				case _: false; // cannot happen?
+			});
+		}
+		return (switch [a, b] {
+			case [Ipv4(a), Ipv4(b)]: a == b;
+			case [Ipv6(a), Ipv6(b)]: a.compare(b) == 0;
+			case _: false;
+		});
+	}
+}

+ 24 - 0
std/asys/net/Dns.hx

@@ -0,0 +1,24 @@
+package asys.net;
+
+import haxe.async.*;
+
+/**
+	Asynchronous Domain Name System (DNS) methods.
+**/
+extern class Dns {
+	/**
+		Looks up the given `hostname`. `callback` will be called once the operation
+		completes. In case of success, the data given to callback is an array of
+		`asys.net.Address` instances representing all the IP addresses found
+		associated with the hostname.
+
+		- `lookupOptions.family` - if not `null`, only addresses of the given IP
+			family will be returned.
+	**/
+	static function lookup(hostname:String, ?lookupOptions:DnsLookupOptions, callback:Callback<Array<Address>>):Void;
+
+	/**
+		Looks up a reverse DNS entry for the given `ip`.
+	**/
+	static function reverse(ip:Address, callback:Callback<Array<String>>):Void;
+}

+ 16 - 0
std/asys/net/DnsLookupOptions.hx

@@ -0,0 +1,16 @@
+package asys.net;
+
+typedef DnsLookupOptions = {
+	?family:IpFamily,
+	?hints:DnsHints
+};
+
+enum abstract DnsHints(Int) from Int {
+	var AddrConfig = 1 << 0;
+	var V4Mapped = 1 << 1;
+
+	inline function get_raw():Int return this;
+
+	@:op(A | B)
+	inline function join(other:DnsHints):DnsHints return this | other.get_raw();
+}

+ 9 - 0
std/asys/net/IpFamily.hx

@@ -0,0 +1,9 @@
+package asys.net;
+
+/**
+	Represents a family of the Internet Protocol (IP).
+**/
+enum IpFamily {
+	Ipv4;
+	Ipv6;
+}

+ 61 - 0
std/asys/net/Server.hx

@@ -0,0 +1,61 @@
+package asys.net;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+
+typedef ServerOptions = {
+	?allowHalfOpen:Bool,
+	?pauseOnConnect:Bool
+};
+
+typedef ServerListenTcpOptions = {
+	?port:Int,
+	?host:String,
+	?address:Address,
+	?backlog:Int,
+	?exclusive:Bool,
+	?ipv6only:Bool
+};
+
+typedef ServerListenIpcOptions = {
+	path:String,
+	?backlog:Int,
+	?exclusive:Bool,
+	?readableAll:Bool,
+	?writableAll:Bool
+};
+
+class Server {
+	public final closeSignal:Signal<NoData> = new ArraySignal<NoData>();
+	public final connectionSignal:Signal<Socket> = new ArraySignal<Socket>();
+	public final errorSignal:Signal<Error> = new ArraySignal<Error>();
+	public final listeningSignal:Signal<NoData> = new ArraySignal<NoData>();
+
+	public var listening(default, null):Bool;
+	public var maxConnections:Int; // TODO
+
+	extern function get_localAddress():Null<SocketAddress>;
+
+	public var localAddress(get, never):Null<SocketAddress>;
+
+	public function new(?options:ServerOptions) {}
+
+	// function address():SocketAddress;
+
+	extern public function close(?callback:Callback<NoData>):Void;
+
+	// function getConnections(callback:Callback<Int>):Void;
+	// function listenSocket(socket:Socket, ?backlog:Int, ?listener:Listener<NoData>):Void;
+	// function listenServer(server:Server, ?backlog:Int, ?listener:Listener<NoData>):Void;
+	// function listenFile(file:sys.io.File, ?backlog:Int, ?listener:Listener<NoData>):Void;
+	extern public function listenIpc(options:ServerListenIpcOptions, ?listener:Listener<Socket>):Void;
+
+	extern public function listenTcp(options:ServerListenTcpOptions, ?listener:Listener<Socket>):Void;
+
+	extern public function ref():Void;
+
+	extern public function unref():Void;
+
+	var listenDefer:asys.Timer;
+}

+ 247 - 0
std/asys/net/Socket.hx

@@ -0,0 +1,247 @@
+package asys.net;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+import haxe.io.*;
+import haxe.io.Readable.ReadResult;
+import asys.io.*;
+import asys.net.SocketOptions.SocketConnectTcpOptions;
+import asys.net.SocketOptions.SocketConnectIpcOptions;
+
+/**
+	Socket object, used for clients and servers for TCP communications and IPC
+	(inter-process communications) over Windows named pipes and Unix local domain
+	sockets.
+
+	An IPC pipe is a communication channel between two processes. It may be
+	uni-directional or bi-directional, depending on how it is created. Pipes can
+	be automatically created for spawned subprocesses with `Process.spawn`.
+**/
+class Socket extends Duplex {
+	/**
+		Creates an unconnected socket or pipe instance.
+
+		@param options.allowHalfOpen
+		@param options.readable Whether the socket should be readable to the
+			current process.
+		@param options.writable Whether the socket should be writable to the
+			current process.
+	**/
+	public static function create(?options:SocketOptions):Socket {
+		// TODO: use options
+		return new Socket();
+	}
+
+	/**
+		Emitted when the socket connects to a remote endpoint.
+	**/
+	public final closeSignal:Signal<NoData> = new ArraySignal();
+
+	public final connectSignal:Signal<NoData> = new ArraySignal();
+
+	// endSignal
+
+	/**
+		(TCP only.) Emitted after the IP address of the hostname given in
+		`connectTcp` is resolved, but before the socket connects.
+	**/
+	public final lookupSignal:Signal<Address> = new ArraySignal();
+
+	/**
+		Emitted when a timeout occurs. See `setTimeout`.
+	**/
+	public final timeoutSignal:Signal<NoData> = new ArraySignal();
+
+	extern private function get_localAddress():Null<SocketAddress>;
+
+	/**
+		The address of the local side of the socket connection, or `null` if not
+		connected.
+	**/
+	public var localAddress(get, never):Null<SocketAddress>;
+
+	extern private function get_remoteAddress():Null<SocketAddress>;
+
+	/**
+		The address of the remote side of the socket connection, or `null` if not
+		connected.
+	**/
+	public var remoteAddress(get, never):Null<SocketAddress>;
+
+	extern private function get_handlesPending():Int;
+
+	/**
+		(IPC only.) Number of pending sockets or pipes. Accessible using
+		`readHandle`.
+	**/
+	public var handlesPending(get, never):Int;
+
+	/**
+		`true` when `this` socket is connected to a remote host or an IPC pipe.
+	**/
+	public var connected(default, null):Bool = false;
+
+	/**
+		Connect `this` socket via TCP to the given remote.
+
+		If neither `options.host` nor `options.address` is specified, the host
+		`localhost` is resolved via DNS and used as the address. At least one of
+		`options.host` or `options.address` must be `null`.
+
+		`options.localAddress` and `options.localPort` can be used to specify what
+		address and port to use on the local machine for the outgoing connection.
+		If `null` or not specified, an address and/or a port will be chosen
+		automatically by the system when connecting. The local address and port can
+		be obtained using the `localAddress`.
+
+		@param options.port Remote port to connect to.
+		@param options.host Hostname to connect to, will be resolved using
+			`Dns.resolve` to an address. `lookupSignal` will be emitted with the
+			resolved address before the connection is attempted.
+		@param options.address IPv4 or IPv6 address to connect to.
+		@param options.localAddress Local IPv4 or IPv6 address to connect from.
+		@param options.localPort Local port to connect from.
+		@param options.family Limit DNS lookup to the given family.
+	**/
+	extern public function connectTcp(options:SocketConnectTcpOptions, ?cb:Callback<NoData>):Void;
+
+	/**
+		Connect `this` socket to an IPC pipe.
+
+		@param options.path Pipe path.
+	**/
+	extern public function connectIpc(options:SocketConnectIpcOptions, ?cb:Callback<NoData>):Void;
+
+	/**
+		Connect `this` socket to a file descriptor. Used internally to establish
+		IPC channels between Haxe processes.
+
+		@param ipc Whether IPC features (sending sockets) should be enabled.
+	**/
+	extern public function connectFd(ipc:Bool, fd:Int):Void;
+
+	/**
+		Closes `this` socket and all underlying resources.
+	**/
+	extern public function destroy(?cb:Callback<NoData>):Void;
+
+	/**
+		(TCP only.) Enable or disable TCP keep-alive.
+
+		@param initialDelay Initial delay in seconds. Ignored if `enable` is
+			`false`.
+	**/
+	extern public function setKeepAlive(?enable:Bool = false, ?initialDelay:Int = 0):Void;
+
+	/**
+		(TCP only.) Enable or disable TCP no-delay. Enabling no-delay disables
+		Nagle's algorithm.
+	**/
+	extern public function setNoDelay(?noDelay:Bool = true):Void;
+
+	/**
+		Set a timeout for socket oprations. Any time activity is detected on the
+		socket (see below), the timer is reset to `timeout`. When the timer runs
+		out, `timeoutSignal` is emitted. Note that a timeout will not automatically
+		do anything to the socket - it is up to the `timeoutSignal` handler to
+		perform an action, e.g. ping the remote host or close the socket.
+
+		Socket activity which resets the timer:
+
+		- A chunk of data is received.
+		- An error occurs during reading.
+		- A chunk of data is written to the socket.
+		- Connection is established.
+		- (TCP only.) DNS lookup is finished (successfully or not).
+
+		@param timeout Timeout in seconds, or `0` to disable.
+	**/
+	public function setTimeout(timeout:Int, ?listener:Listener<NoData>):Void {
+		timeoutTime = timeout;
+		timeoutReset();
+		if (listener != null)
+			timeoutSignal.once(listener);
+	}
+
+	/**
+		(IPC only.) Send a socket or pipe in along with the given `data`. The
+		socket must be connected.
+	**/
+	extern public function writeHandle(data:Bytes, handle:Socket):Void;
+
+	/**
+		(IPC only.) Receive a socket or pipe. Should only be called when
+		`handlesPending` is greater than zero.
+	**/
+	extern public function readHandle():Socket;
+
+	extern public function ref():Void;
+
+	extern public function unref():Void;
+
+	var connectDefer:asys.Timer;
+	var internalReadCalled = false;
+	var readStarted = false;
+	var connectStarted = false;
+	var serverSpawn:Bool = false;
+	var timeoutTime:Int = 0;
+	var timeoutTimer:asys.Timer;
+
+	function new() {
+		super();
+	}
+
+	extern function initPipe(ipc:Bool):Void;
+
+	// TODO: keep track of pending writes for finish event emission
+	// in `internalWrite` and `writeHandle`
+	function writeDone(err:Error, nd:NoData):Void {
+		timeoutReset();
+		if (err != null)
+			errorSignal.emit(err);
+		// TODO: destroy stream and socket
+	}
+
+	function timeoutTrigger():Void {
+		timeoutTimer = null;
+		timeoutSignal.emit(new NoData());
+	}
+
+	function timeoutReset():Void {
+		if (timeoutTimer != null)
+			timeoutTimer.stop();
+		timeoutTimer = null;
+		if (timeoutTime != 0) {
+			timeoutTimer = asys.Timer.delay(timeoutTrigger, timeoutTime);
+			timeoutTimer.unref();
+		}
+	}
+
+	/*
+	// TODO: #8263 (static hxUnserialize)
+	// Automatic un/serialisation will not work here since hxUnserialize needs to
+	// call super, otherwise the socket is unusable; for now sockets are
+	// delivered separately in IPC.
+
+	@:access(asys.io.IpcSerializer)
+	private function hxSerialize(_):Void {
+		if (IpcSerializer.activeSerializer == null)
+			throw "cannot serialize socket";
+		IpcSerializer.activeSerializer.chunkSockets.push(this);
+	}
+
+	@:access(asys.io.IpcUnserializer)
+	private function hxUnserialize(_):Void {
+		if (IpcUnserializer.activeUnserializer == null)
+			throw "cannot unserialize socket";
+		trace(dataSignal, input);
+		var source:Socket = IpcUnserializer.activeUnserializer.chunkSockets.shift();
+		this.native = source.native;
+		this.nativePipe = source.nativePipe;
+		this.nativeSocket = source.nativeSocket;
+		this.connected = true;
+		trace("successfully unserialized", this.nativeSocket);
+	}
+	*/
+}

+ 15 - 0
std/asys/net/SocketAddress.hx

@@ -0,0 +1,15 @@
+package asys.net;
+
+/**
+	Reperesents the address of a connected or bound `Socket` object.
+**/
+enum SocketAddress {
+	/**
+		Address of a socket connected or bound to an IPv4 or IPv6 address and port.
+	**/
+	Network(address:Address, port:Int);
+	/**
+		Filepath of a IPC pipe (Windows named pipe or Unix local domain socket).
+	**/
+	Unix(path:String);
+}

+ 41 - 0
std/asys/net/SocketOptions.hx

@@ -0,0 +1,41 @@
+package asys.net;
+
+/**
+	See `Socket.create`.
+**/
+typedef SocketOptions = {
+	// ?file:asys.io.File, // fd in Node
+	?allowHalfOpen:Bool,
+	?readable:Bool,
+	?writable:Bool
+};
+
+/**
+	See `Socket.connectTcp`.
+**/
+typedef SocketConnectTcpOptions = {
+	port:Int,
+	?host:String,
+	?address:Address,
+	?localAddress:Address,
+	?localPort:Int,
+	?family:IpFamily
+};
+
+/**
+	See `Socket.connectIpc`.
+**/
+typedef SocketConnectIpcOptions = {
+	path:String
+};
+
+/**
+	See `UdpSocket.create`.
+**/
+typedef UdpSocketOptions = {
+	?reuseAddr:Bool,
+	?ipv6Only:Bool,
+	?recvBufferSize:Int,
+	?sendBufferSize:Int,
+	// ?lookup:DnsLookupFunction
+};

+ 171 - 0
std/asys/net/UdpSocket.hx

@@ -0,0 +1,171 @@
+package asys.net;
+
+import haxe.Error;
+import haxe.NoData;
+import haxe.async.*;
+import haxe.io.Bytes;
+import asys.net.SocketOptions.UdpSocketOptions;
+
+
+class UdpSocket {
+	public static function create(type:IpFamily, ?options:UdpSocketOptions, ?listener:Listener<UdpMessage>):UdpSocket {
+		var res = new UdpSocket(type);
+		// TODO: use other options, register listener
+		if (options == null)
+			options = {};
+		if (options.recvBufferSize != null)
+			res.recvBufferSize = options.recvBufferSize;
+		if (options.sendBufferSize != null)
+			res.sendBufferSize = options.sendBufferSize;
+		return res;
+	}
+
+	public final type:IpFamily;
+
+	/**
+		Remote address and port that `this` socket is connected to. See `connect`.
+	**/
+	public var remoteAddress(default, null):Null<SocketAddress>;
+
+	private function get_localAddress():Null<SocketAddress> {
+		return try native.getSockName() catch (e:Dynamic) null;
+	}
+
+	public var localAddress(get, never):Null<SocketAddress>;
+
+	extern function get_recvBufferSize():Int;
+
+	extern function set_recvBufferSize(size:Int):Int;
+
+	public var recvBufferSize(get, set):Int;
+
+	extern function get_sendBufferSize():Int;
+
+	extern function set_sendBufferSize(size:Int):Int;
+
+	public var sendBufferSize(get, set):Int;
+
+	// final closeSignal:Signal<NoData>;
+	// final connectSignal:Signal<NoData>;
+	// final listeningSignal:Signal<NoData>;
+
+	public final errorSignal:Signal<Error> = new ArraySignal();
+
+	/**
+		Emitted when a message is received by `this` socket. See `UdpMessage`.
+	**/
+	public final messageSignal:Signal<UdpMessage> = new ArraySignal();
+
+	/**
+		Joins the given multicast group.
+	**/
+	extern public function addMembership(multicastAddress:String, ?multicastInterface:String):Void;
+
+	/**
+		Leaves the given multicast group.
+	**/
+	extern public function dropMembership(multicastAddress:String, ?multicastInterface:String):Void;
+
+	/**
+		Binds `this` socket to a local address and port. Packets sent to the bound
+		address will arrive via `messageSignal`. Outgoing packets will be sent from
+		the given address and port. If any packet is sent without calling `bind`
+		first, an address and port is chosen automatically by the system - it can
+		be obtained with `localAddress`.
+	**/
+	extern public function bind(?address:Address, ?port:Int):Void;
+
+	/**
+		Closes `this` socket and all underlying resources.
+	**/
+	extern public function close(?cb:Callback<NoData>):Void;
+
+	/**
+		Connects `this` socket to a remote address and port. Any `send` calls after
+		`connect` is called must not specify `address` nor `port`, they will
+		automatically use the ones specified in the `connect` call.
+	**/
+	public function connect(?address:Address, port:Int):Void {
+		if (remoteAddress != null)
+			throw "already connected";
+		if (address == null)
+			address = AddressTools.localhost(type);
+		remoteAddress = Network(address, port);
+	}
+
+	/**
+		Clears any remote address and port previously set with `connect`.
+	**/
+	public function disconnect():Void {
+		if (remoteAddress == null)
+			throw "not connected";
+		remoteAddress = null;
+	}
+
+	/**
+		Sends a message.
+
+		@param msg Buffer from which to read the message data.
+		@param offset Position in `msg` at which to start reading.
+		@param length Length of message in bytes.
+		@param address Address to send the message to. Must be `null` if `this`
+			socket is connected.
+		@param port Port to send the message to. Must be `null` if `this` socket is
+			connected.
+	**/
+	extern public function send(msg:Bytes, offset:Int, length:Int, ?address:Address, ?port:Int, ?cb:Callback<NoData>):Void;
+
+	/**
+		Sets broadcast on or off.
+	**/
+	extern public function setBroadcast(flag:Bool):Void;
+
+	/**
+		Sets the multicast interface on which to send and receive data.
+	**/
+	extern public function setMulticastInterface(multicastInterface:String):Void;
+
+	/**
+		Set IP multicast loopback on or off. Makes multicast packets loop back to
+		local sockets.
+	**/
+	extern public function setMulticastLoopback(flag:Bool):Void;
+
+	/**
+		Sets the multicast TTL (time-to-live).
+	**/
+	extern public function setMulticastTTL(ttl:Int):Void;
+
+	/**
+		Sets the TTL (time-to-live) for outgoing packets.
+
+		@param ttl Number of hops.
+	**/
+	extern public function setTTL(ttl:Int):Void;
+
+	extern public function ref():Void;
+
+	extern public function unref():Void;
+
+	function new(type) {
+		this.type = type;
+	}
+}
+
+/**
+	A packet received emitted by `messageSignal` of a `UdpSocket`.
+**/
+typedef UdpMessage = {
+	/**
+		Message data.
+	**/
+	var data:Bytes;
+	/**
+		Remote IPv4 or IPv6 address from which the message originates.
+	**/
+	var remoteAddress:Address;
+	/**
+		Remote port from which the message originates.
+	**/
+	var remotePort:Int;
+};

+ 14 - 0
std/asys/uv/UVConstants.hx

@@ -0,0 +1,14 @@
+package asys.uv;
+
+class UVConstants {
+	public static inline final S_IFMT = 0xF000;
+	public static inline final S_PERM = 0x0FFF;
+
+	public static inline final S_IFBLK = 0x6000;
+	public static inline final S_IFCHR = 0x2000;
+	public static inline final S_IFDIR = 0x4000;
+	public static inline final S_IFIFO = 0x1000;
+	public static inline final S_IFLNK = 0xA000;
+	public static inline final S_IFREG = 0x8000;
+	public static inline final S_IFSOCK = 0xC000;
+}

+ 12 - 0
std/asys/uv/UVDirentType.hx

@@ -0,0 +1,12 @@
+package asys.uv;
+
+enum abstract UVDirentType(Int) {
+  var DirentUnknown = 0;
+  var DirentFile;
+  var DirentDir;
+  var DirentLink;
+  var DirentFifo;
+  var DirentSocket;
+  var DirentChar;
+  var DirentBlock;
+}

+ 388 - 0
std/asys/uv/UVErrorType.hx

@@ -0,0 +1,388 @@
+package asys.uv;
+
+extern enum abstract UVErrorType(Int) {
+	/**
+		Argument list too long.
+	**/
+	var E2BIG;
+
+	/**
+		Permission denied.
+	**/
+	var EACCES;
+
+	/**
+		Address already in use.
+	**/
+	var EADDRINUSE;
+
+	/**
+		Address not available.
+	**/
+	var EADDRNOTAVAIL;
+
+	/**
+		Address family not supported.
+	**/
+	var EAFNOSUPPORT;
+
+	/**
+		Resource temporarily unavailable.
+	**/
+	var EAGAIN;
+
+	/**
+		Address family not supported.
+	**/
+	var EAI_ADDRFAMILY;
+
+	/**
+		Temporary failure.
+	**/
+	var EAI_AGAIN;
+
+	/**
+		Bad ai_flags value.
+	**/
+	var EAI_BADFLAGS;
+
+	/**
+		Invalid value for hints.
+	**/
+	var EAI_BADHINTS;
+
+	/**
+		Request canceled.
+	**/
+	var EAI_CANCELED;
+
+	/**
+		Permanent failure.
+	**/
+	var EAI_FAIL;
+
+	/**
+		Ai_family not supported.
+	**/
+	var EAI_FAMILY;
+
+	/**
+		Out of memory.
+	**/
+	var EAI_MEMORY;
+
+	/**
+		No address.
+	**/
+	var EAI_NODATA;
+
+	/**
+		Unknown node or service.
+	**/
+	var EAI_NONAME;
+
+	/**
+		Argument buffer overflow.
+	**/
+	var EAI_OVERFLOW;
+
+	/**
+		Resolved protocol is unknown.
+	**/
+	var EAI_PROTOCOL;
+
+	/**
+		Service not available for socket type.
+	**/
+	var EAI_SERVICE;
+
+	/**
+		Socket type not supported.
+	**/
+	var EAI_SOCKTYPE;
+
+	/**
+		Connection already in progress.
+	**/
+	var EALREADY;
+
+	/**
+		Bad file descriptor.
+	**/
+	var EBADF;
+
+	/**
+		Resource busy or locked.
+	**/
+	var EBUSY;
+
+	/**
+		Operation canceled.
+	**/
+	var ECANCELED;
+
+	/**
+		Invalid Unicode character.
+	**/
+	var ECHARSET;
+
+	/**
+		Software caused connection abort.
+	**/
+	var ECONNABORTED;
+
+	/**
+		Connection refused.
+	**/
+	var ECONNREFUSED;
+
+	/**
+		Connection reset by peer.
+	**/
+	var ECONNRESET;
+
+	/**
+		Destination address required.
+	**/
+	var EDESTADDRREQ;
+
+	/**
+		File already exists.
+	**/
+	var EEXIST;
+
+	/**
+		Bad address in system call argument.
+	**/
+	var EFAULT;
+
+	/**
+		File too large.
+	**/
+	var EFBIG;
+
+	/**
+		Host is unreachable.
+	**/
+	var EHOSTUNREACH;
+
+	/**
+		Interrupted system call.
+	**/
+	var EINTR;
+
+	/**
+		Invalid argument.
+	**/
+	var EINVAL;
+
+	/**
+		I/o error.
+	**/
+	var EIO;
+
+	/**
+		Socket is already connected.
+	**/
+	var EISCONN;
+
+	/**
+		Illegal operation on a directory.
+	**/
+	var EISDIR;
+
+	/**
+		Too many symbolic links encountered.
+	**/
+	var ELOOP;
+
+	/**
+		Too many open files.
+	**/
+	var EMFILE;
+
+	/**
+		Message too long.
+	**/
+	var EMSGSIZE;
+
+	/**
+		Name too long.
+	**/
+	var ENAMETOOLONG;
+
+	/**
+		Network is down.
+	**/
+	var ENETDOWN;
+
+	/**
+		Network is unreachable.
+	**/
+	var ENETUNREACH;
+
+	/**
+		File table overflow.
+	**/
+	var ENFILE;
+
+	/**
+		No buffer space available.
+	**/
+	var ENOBUFS;
+
+	/**
+		No such device.
+	**/
+	var ENODEV;
+
+	/**
+		No such file or directory.
+	**/
+	var ENOENT;
+
+	/**
+		Not enough memory.
+	**/
+	var ENOMEM;
+
+	/**
+		Machine is not on the network.
+	**/
+	var ENONET;
+
+	/**
+		Protocol not available.
+	**/
+	var ENOPROTOOPT;
+
+	/**
+		No space left on device.
+	**/
+	var ENOSPC;
+
+	/**
+		Function not implemented.
+	**/
+	var ENOSYS;
+
+	/**
+		Socket is not connected.
+	**/
+	var ENOTCONN;
+
+	/**
+		Not a directory.
+	**/
+	var ENOTDIR;
+
+	/**
+		Directory not empty.
+	**/
+	var ENOTEMPTY;
+
+	/**
+		Socket operation on non-socket.
+	**/
+	var ENOTSOCK;
+
+	/**
+		Operation not supported on socket.
+	**/
+	var ENOTSUP;
+
+	/**
+		Operation not permitted.
+	**/
+	var EPERM;
+
+	/**
+		Broken pipe.
+	**/
+	var EPIPE;
+
+	/**
+		Protocol error.
+	**/
+	var EPROTO;
+
+	/**
+		Protocol not supported.
+	**/
+	var EPROTONOSUPPORT;
+
+	/**
+		Protocol wrong type for socket.
+	**/
+	var EPROTOTYPE;
+
+	/**
+		Result too large.
+	**/
+	var ERANGE;
+
+	/**
+		Read-only file system.
+	**/
+	var EROFS;
+
+	/**
+		Cannot send after transport endpoint shutdown.
+	**/
+	var ESHUTDOWN;
+
+	/**
+		Invalid seek.
+	**/
+	var ESPIPE;
+
+	/**
+		No such process.
+	**/
+	var ESRCH;
+
+	/**
+		Connection timed out.
+	**/
+	var ETIMEDOUT;
+
+	/**
+		Text file is busy.
+	**/
+	var ETXTBSY;
+
+	/**
+		Cross-device link not permitted.
+	**/
+	var EXDEV;
+
+	/**
+		Unknown error.
+	**/
+	var UNKNOWN;
+
+	/**
+		End of file.
+	**/
+	var EOF;
+
+	/**
+		No such device or address.
+	**/
+	var ENXIO;
+
+	/**
+		Too many links.
+	**/
+	var EMLINK;
+
+	/**
+		Host is down.
+	**/
+	var EHOSTDOWN;
+
+	/**
+		Unknown error within libuv or libuv glue code.
+	**/
+	var EOTHER;
+}

+ 7 - 0
std/asys/uv/UVFsEventType.hx

@@ -0,0 +1,7 @@
+package asys.uv;
+
+enum abstract UVFsEventType(Int) {
+	var Rename = 1;
+	var Change = 2;
+	var RenameChange = 3;
+}

+ 18 - 0
std/asys/uv/UVProcessSpawnFlags.hx

@@ -0,0 +1,18 @@
+package asys.uv;
+
+enum abstract UVProcessSpawnFlags(Int) {
+	var None = 0;
+	var SetUid = 1 << 0;
+	var SetGid = 1 << 1;
+	var WindowsVerbatimArguments = 1 << 2;
+	var Detached = 1 << 3;
+	var WindowsHide = 1 << 4;
+
+	function new(raw:Int)
+		this = raw;
+
+	inline function get_raw():Int return this;
+
+	@:op(A | B)
+	inline function join(other:UVProcessSpawnFlags) return new UVProcessSpawnFlags(this | other.get_raw());
+}

+ 7 - 0
std/asys/uv/UVRunMode.hx

@@ -0,0 +1,7 @@
+package asys.uv;
+
+enum abstract UVRunMode(Int) {
+	var RunDefault = 0;
+	var RunOnce;
+	var RunNoWait;
+}

+ 50 - 0
std/asys/uv/UVStat.hx

@@ -0,0 +1,50 @@
+package asys.uv;
+
+class UVStat {
+	public final dev:Int;
+	public final mode:Int;
+	public final nlink:Int;
+	public final uid:Int;
+	public final gid:Int;
+	public final rdev:Int;
+	public final ino:Int;
+	public final size:Int;
+	public final blksize:Int;
+	public final blocks:Int;
+	public final flags:Int;
+	public final gen:Int;
+
+	public function new(st_dev:Int, st_mode:Int, st_nlink:Int, st_uid:Int, st_gid:Int, st_rdev:Int, st_ino:Int, st_size:Int, st_blksize:Int, st_blocks:Int,
+			st_flags:Int, st_gen:Int) {
+		dev = st_dev;
+		mode = st_mode;
+		nlink = st_nlink;
+		uid = st_uid;
+		gid = st_gid;
+		rdev = st_rdev;
+		ino = st_ino;
+		size = st_size;
+		blksize = st_blksize;
+		blocks = st_blocks;
+		flags = st_flags;
+		gen = st_gen;
+	}
+
+	public function isBlockDevice():Bool return (mode & asys.uv.UVConstants.S_IFMT) == asys.uv.UVConstants.S_IFBLK;
+
+	public function isCharacterDevice():Bool return (mode & asys.uv.UVConstants.S_IFMT) == asys.uv.UVConstants.S_IFCHR;
+
+	public function isDirectory():Bool return (mode & asys.uv.UVConstants.S_IFMT) == asys.uv.UVConstants.S_IFDIR;
+
+	public function isFIFO():Bool return (mode & asys.uv.UVConstants.S_IFMT) == asys.uv.UVConstants.S_IFIFO;
+
+	public function isFile():Bool return (mode & asys.uv.UVConstants.S_IFMT) == asys.uv.UVConstants.S_IFREG;
+
+	public function isSocket():Bool return (mode & asys.uv.UVConstants.S_IFMT) == asys.uv.UVConstants.S_IFSOCK;
+
+	public function isSymbolicLink():Bool return (mode & asys.uv.UVConstants.S_IFMT) == asys.uv.UVConstants.S_IFLNK;
+
+	function get_permissions():FilePermissions return @:privateAccess new FilePermissions(mode & asys.uv.UVConstants.S_PERM);
+
+	public var permissions(get, never):FilePermissions;
+}