diff --git a/compiler/lib/inkoc/codegen/instruction.rb b/compiler/lib/inkoc/codegen/instruction.rb index 5a717d3b7..8f803d704 100644 --- a/compiler/lib/inkoc/codegen/instruction.rb +++ b/compiler/lib/inkoc/codegen/instruction.rb @@ -167,6 +167,18 @@ class Instruction StringToFloat FloatToBits ProcessIdentifier + SocketCreate + SocketWrite + SocketRead + SocketAccept + SocketReceiveFrom + SocketSendTo + SocketAddress + SocketGetOption + SocketSetOption + SocketBind + SocketListen + SocketConnect ] .each_with_index .each_with_object({}) { |(value, index), hash| hash[value] = index } diff --git a/compiler/lib/inkoc/config.rb b/compiler/lib/inkoc/config.rb index d1da3b023..10d165e11 100644 --- a/compiler/lib/inkoc/config.rb +++ b/compiler/lib/inkoc/config.rb @@ -59,6 +59,8 @@ class Config FUNCTION_CONST = 'Function' POINTER_CONST = 'Pointer' PROCESS_CONST = 'Process' + SOCKET_CONST = 'Socket' + UNIX_SOCKET_CONST = 'UnixSocket' ARRAY_TYPE_PARAMETER = 'T' OPTIONAL_CONST = 'Optional' diff --git a/compiler/lib/inkoc/lexer.rb b/compiler/lib/inkoc/lexer.rb index f3eba912d..3a59f71b1 100644 --- a/compiler/lib/inkoc/lexer.rb +++ b/compiler/lib/inkoc/lexer.rb @@ -34,6 +34,7 @@ class Lexer ).freeze NUMBER_RANGE = '0'..'9' + NUMBER_ALLOWED_LETTERS = %w[a b c d e f A B C D E F x _] # We allocate this once so we don't end up wasting allocations every time we # consume a peeked value. @@ -293,7 +294,7 @@ def number(skip_first: false) type = :float @position += next_char == '+' ? 2 : 1 - when NUMBER_RANGE, '_', 'x' + when NUMBER_RANGE, *NUMBER_ALLOWED_LETTERS @position += 1 else break @@ -378,11 +379,12 @@ def string_with_quote(quote, escaped, unescape_special = false) if has_special && unescape_special token.value.gsub!( - /\\t|\\r|\\n|\\e/, + /\\t|\\r|\\n|\\e|\\0/, '\t' => "\t", '\n' => "\n", '\r' => "\r", - '\e' => "\e" + '\e' => "\e", + '\0' => "\0" ) end diff --git a/compiler/lib/inkoc/pass/define_type.rb b/compiler/lib/inkoc/pass/define_type.rb index 5350e180e..80a3e27b3 100644 --- a/compiler/lib/inkoc/pass/define_type.rb +++ b/compiler/lib/inkoc/pass/define_type.rb @@ -1625,6 +1625,14 @@ def on_raw_get_process_prototype(*) typedb.process_type.new_instance end + def on_raw_get_socket_prototype(*) + typedb.socket_type.new_instance + end + + def on_raw_get_unix_socket_prototype(*) + typedb.unix_socket_type.new_instance + end + def on_raw_set_object_name(*) typedb.string_type.new_instance end @@ -1745,6 +1753,54 @@ def on_raw_float_to_bits(*) typedb.integer_type.new_instance end + def on_raw_socket_create(*) + TypeSystem::Dynamic.new + end + + def on_raw_socket_write(*) + typedb.integer_type.new_instance + end + + def on_raw_socket_read(*) + typedb.integer_type.new_instance + end + + def on_raw_socket_accept(*) + TypeSystem::Dynamic.new + end + + def on_raw_socket_receive_from(*) + typedb.new_array_of_type(TypeSystem::Dynamic.new) + end + + def on_raw_socket_send_to(*) + typedb.integer_type.new_instance + end + + def on_raw_socket_address(*) + typedb.new_array_of_type(TypeSystem::Dynamic.new) + end + + def on_raw_socket_get_option(*) + TypeSystem::Dynamic.new + end + + def on_raw_socket_set_option(*) + TypeSystem::Dynamic.new + end + + def on_raw_socket_bind(*) + typedb.nil_type.new_instance + end + + def on_raw_socket_connect(*) + typedb.nil_type.new_instance + end + + def on_raw_socket_listen(*) + typedb.integer_type.new_instance + end + def define_block_signature(node, scope, expected_block = nil) define_type_parameters(node, scope) define_argument_types(node, scope, expected_block) diff --git a/compiler/lib/inkoc/pass/generate_tir.rb b/compiler/lib/inkoc/pass/generate_tir.rb index eb4e7cd4a..7d6669a0e 100644 --- a/compiler/lib/inkoc/pass/generate_tir.rb +++ b/compiler/lib/inkoc/pass/generate_tir.rb @@ -1380,6 +1380,14 @@ def on_raw_get_process_prototype(node, body) builtin_prototype_instruction(PrototypeID::PROCESS, node, body) end + def on_raw_get_socket_prototype(node, body) + builtin_prototype_instruction(PrototypeID::SOCKET, node, body) + end + + def on_raw_get_unix_socket_prototype(node, body) + builtin_prototype_instruction(PrototypeID::UNIX_SOCKET, node, body) + end + def on_raw_set_object_name(node, body) loc = node.location obj = process_node(node.arguments.fetch(0), body) @@ -1505,6 +1513,54 @@ def on_raw_float_to_bits(node, body) raw_unary_instruction(:FloatToBits, node, body) end + def on_raw_socket_create(node, body) + raw_binary_instruction(:SocketCreate, node, body) + end + + def on_raw_socket_write(node, body) + raw_binary_instruction(:SocketWrite, node, body) + end + + def on_raw_socket_read(node, body) + raw_ternary_instruction(:SocketRead, node, body) + end + + def on_raw_socket_accept(node, body) + raw_unary_instruction(:SocketAccept, node, body) + end + + def on_raw_socket_receive_from(node, body) + raw_ternary_instruction(:SocketReceiveFrom, node, body) + end + + def on_raw_socket_send_to(node, body) + raw_quaternary_instruction(:SocketSendTo, node, body) + end + + def on_raw_socket_address(node, body) + raw_binary_instruction(:SocketAddress, node, body) + end + + def on_raw_socket_get_option(node, body) + raw_binary_instruction(:SocketGetOption, node, body) + end + + def on_raw_socket_set_option(node, body) + raw_ternary_instruction(:SocketSetOption, node, body) + end + + def on_raw_socket_bind(node, body) + raw_ternary_instruction(:SocketBind, node, body) + end + + def on_raw_socket_connect(node, body) + raw_ternary_instruction(:SocketConnect, node, body) + end + + def on_raw_socket_listen(node, body) + raw_binary_instruction(:SocketListen, node, body) + end + def on_return(node, body) location = node.location register = diff --git a/compiler/lib/inkoc/prototype_id.rb b/compiler/lib/inkoc/prototype_id.rb index 527215095..0314e4fc7 100644 --- a/compiler/lib/inkoc/prototype_id.rb +++ b/compiler/lib/inkoc/prototype_id.rb @@ -18,5 +18,7 @@ module PrototypeID FUNCTION = 13 POINTER = 14 PROCESS = 15 + SOCKET = 16 + UNIX_SOCKET = 17 end end diff --git a/compiler/lib/inkoc/type_system/database.rb b/compiler/lib/inkoc/type_system/database.rb index a934e5b0f..e5c9e4ecf 100644 --- a/compiler/lib/inkoc/type_system/database.rb +++ b/compiler/lib/inkoc/type_system/database.rb @@ -8,7 +8,8 @@ class Database :object_type, :hasher_type, :boolean_type, :read_only_file_type, :write_only_file_type, :read_write_file_type, :byte_array_type, :library_type, - :function_type, :pointer_type, :process_type + :function_type, :pointer_type, :process_type, + :socket_type, :unix_socket_type def initialize @object_type = new_object_type(Config::OBJECT_CONST, nil) @@ -31,6 +32,8 @@ def initialize @function_type = new_object_type(Config::FUNCTION_CONST) @pointer_type = new_object_type(Config::POINTER_CONST) @process_type = new_object_type(Config::PROCESS_CONST) + @socket_type = new_object_type(Config::SOCKET_CONST) + @unix_socket_type = new_object_type(Config::UNIX_SOCKET_CONST) @trait_id = -1 end diff --git a/compiler/spec/inkoc/lexer_spec.rb b/compiler/spec/inkoc/lexer_spec.rb index 18606d6aa..1eff73eab 100644 --- a/compiler/spec/inkoc/lexer_spec.rb +++ b/compiler/spec/inkoc/lexer_spec.rb @@ -260,6 +260,14 @@ expect(token.value).to eq('0x10') end + it 'tokenizes a hexadecimal integer with letters' do + lexer = described_class.new('0xFF') + token = lexer.number + + expect(token.type).to eq(:integer) + expect(token.value).to eq('0xFF') + end + it 'tokenizes a float using the scientific notation with a lowercase e' do lexer = described_class.new('1e2') token = lexer.number @@ -368,6 +376,14 @@ expect(token.value).to eq("\n") end + it 'tokenizes a double quoted string with a NULL byte' do + lexer = described_class.new('"\0"') + token = lexer.double_string + + expect(token.type).to eq(:string) + expect(token.value).to eq("\0") + end + it 'tokenizes a double quoted string with a carriage return' do lexer = described_class.new('"\r"') token = lexer.double_string diff --git a/runtime/src/core/prelude.inko b/runtime/src/core/prelude.inko index 7c1ea39f3..75a567045 100644 --- a/runtime/src/core/prelude.inko +++ b/runtime/src/core/prelude.inko @@ -37,6 +37,7 @@ import std::byte_array import std::process import std::vm import std::range::(Range as _Range) +import std::integer::extensions # These constants are re-exported so they're available to all modules by # default. Core types such as String should be exposed in std::globals instead. diff --git a/runtime/src/std/integer/extensions.inko b/runtime/src/std/integer/extensions.inko new file mode 100644 index 000000000..2f345cba8 --- /dev/null +++ b/runtime/src/std/integer/extensions.inko @@ -0,0 +1,73 @@ +#! Extensions for the `Integer` type that can only be defined later on in the +#! bootstrapping process. +import std::process +import std::string_buffer::StringBuffer + +## The digits to use when converting an `Integer` to a `String` using a specific +## base or radix. +## +## The order of values in this `Array` must remain as-is, as re-ordering values +## will break the code that uses this `Array`. +let INTEGER_RADIX_DIGITS = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', + 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' +] + +impl Integer { + ## Formats `self` as a `String` using the given base/radix. + ## + ## # Panics + ## + ## This method will panic if `radix` is smaller than 2, or greater than 36. + ## + ## # Examples + ## + ## Formatting an integer in base 16 (hexadecimal): + ## + ## 0x2ff.format(radix: 16) # => '2ff' + def format(radix = 10) -> String { + radix < 2 + .or { radix > 36 } + .if_true { + process.panic('The radix argument must be between 2 and 36') + } + + zero?.if_true { + return '0' + } + + let characters = [] + let mut integer = absolute + + negative?.if_true { + characters.push('-') + } + + { integer.positive? }.while_true { + characters.push(*INTEGER_RADIX_DIGITS[integer % radix]) + integer /= radix + } + + # The above operation pushes the digits from the back, resulting in our + # characters being in reverse order. For example, for 0x2ff the `characters` + # `Array` would be `['f', 'f', '2']`. Below we'll reverse the values + # in-place. + let start_at = negative?.if true: { 1 }, false: { 0 } + let mut old_index = characters.length - 1 + let mut new_index = start_at + + { old_index > new_index }.while_true { + let old = *characters[old_index] + let new = *characters[new_index] + + characters[new_index] = old + characters[old_index] = new + + old_index -= 1 + new_index += 1 + } + + StringBuffer.new(characters).to_string + } +} diff --git a/runtime/src/std/net/bits.inko b/runtime/src/std/net/bits.inko new file mode 100644 index 000000000..8eb196f0e --- /dev/null +++ b/runtime/src/std/net/bits.inko @@ -0,0 +1,211 @@ +#! Types, constants and methods reused by IP and Unix sockets. +#! +#! This module should not be used directly, instead one should use +#! `std::net::socket` and `std::net::unix`. +import std::byte_array::ByteArray +import std::io::(Error as IoError) + +## A marker trait used for values that can be directly sent across a socket. +trait SocketValue {} + +impl SocketValue for ByteArray {} +impl SocketValue for String {} + +## The domain for IPv4 sockets. +let AF_INET = 0 + +## The domain for IPv6 sockets. +let AF_INET6 = 1 + +## The domain for Unix sockets. +let AF_UNIX = 2 + +## The socket type for socket streams. +let SOCK_STREAM = 0 + +## The socket type for datagram sockets. +let SOCK_DGRAM = 1 + +## The socket type for sequential packet sockets. +let SOCK_SEQPACKET = 2 + +## The socket type for raw sockets. +let SOCK_RAW = 3 + +## The maximum value valid for a listen() call. +## +## Linux and FreeBSD do not allow for values greater than this as they +## internally use an u16, so we'll limit the backlog to this value. We don't use +## SOMAXCONN because it might be hardcoded. This means that setting +## `net.core.somaxconn` on Linux (for example) would have no effect. +let MAXIMUM_LISTEN_BACKLOG = 65_535 + +let TTL = 0 +let ONLY_V6 = 1 +let NODELAY = 2 +let BROADCAST = 3 +let LINGER = 4 +let RECV_SIZE = 5 +let SEND_SIZE = 6 +let KEEPALIVE = 7 +let MULTICAST_LOOP_V4 = 8 +let MULTICAST_LOOP_V6 = 9 +let MULTICAST_TTL_V4 = 10 +let MULTICAST_HOPS_V6 = 11 +let MULTICAST_IF_V4 = 12 +let MULTICAST_IF_V6 = 13 +let UNICAST_HOPS_V6 = 14 +let REUSE_ADDRESS = 15 +let REUSE_PORT = 16 + +let LOCAL_ADDRESS = 0 +let PEER_ADDRESS = 1 + +## Sets a socket option to the given value. +## +## The allowed value `value` differs based on the value passed to `option`. +def set_socket_option!(T)( + socket, + option: Integer, + value: T +) !! IoError -> T { + try { + _INKOC.socket_set_option(socket, option, value) as T + } else (error) { + throw IoError.new(error as String) + } +} + +## Retrieves the value of a socket option. +## +## The type of the returned value depends on the `option` argument. +def get_socket_option!(T)(socket, option: Integer) !! IoError -> T { + try { + _INKOC.socket_get_option(socket, option) as T + } else (error) { + throw IoError.new(error as String) + } +} + +## Obtains the local or peer address of a socket. +def address(socket, peer = False) !! IoError -> Array!(Dynamic) { + let kind = peer.if true: { PEER_ADDRESS }, false: { LOCAL_ADDRESS } + + try { + _INKOC.socket_address(socket, kind) + } else (error) { + throw IoError.new(error as String) + } +} + +def socket(domain: Integer, kind: Integer) !! IoError { + try { + _INKOC.socket_create(domain, kind) + } else (error) { + throw IoError.new(error as String) + } +} + +def bind(socket, address: String, port: Integer) !! IoError -> Nil { + try { + _INKOC.socket_bind(socket, address, port) + } else (error) { + throw IoError.new(error as String) + } + + Nil +} + +def connect(socket, address: String, port: Integer) !! IoError -> Nil { + try { + _INKOC.socket_connect(socket, address, port) + } else (error) { + throw IoError.new(error as String) + } + + Nil +} + +def listen(socket, backlog: Integer) !! IoError -> Integer { + try { + _INKOC.socket_listen(socket, backlog) + } else (error) { + throw IoError.new(error as String) + } +} + +def accept(socket) !! IoError { + try { + _INKOC.socket_accept(socket) + } else (error) { + throw IoError.new(error as String) + } +} + +def send_to( + socket, + message: SocketValue, + address: String, + port: Integer +) !! IoError -> Integer { + try { + _INKOC.socket_send_to(socket, message, address, port) + } else (error) { + throw IoError.new(error as String) + } +} + +def receive_from( + socket, + bytes: ByteArray, + size: Integer +) !! IoError -> Array!(Dynamic) { + try { + _INKOC.socket_receive_from(socket, bytes, size) + } else (error) { + throw IoError.new(error as String) + } +} + +def read_bytes( + socket, + bytes: ByteArray, + size: ?Integer = Nil +) !! IoError -> Integer { + try { + _INKOC.socket_read(socket, bytes, size) + } else (error) { + throw IoError.new(error as String) + } +} + +def write_bytes(socket, bytes: ByteArray) !! IoError -> Integer { + try { + _INKOC.socket_write(socket, bytes) + } else (error) { + throw IoError.new(error as String) + } +} + +def write_string(socket, string: String) !! IoError -> Integer { + try { + _INKOC.socket_write(socket, string) + } else (error) { + throw IoError.new(error as String) + } +} + +def close(socket) -> Nil { + _INKOC.drop(socket) +} + +def to_stream(socket, prototype) { + let stream = _INKOC.set_object(False, prototype) + + # Currently Inko does not support multiple constructors (e.g. via overloading) + # or directly initialising an object with its attributes. Using `std::mirror` + # would not be very efficient, so we use raw VM instructions directly. + _INKOC.set_attribute(stream, '@socket', socket) + + stream +} diff --git a/runtime/src/std/net/ip.inko b/runtime/src/std/net/ip.inko new file mode 100644 index 000000000..0a3ed0f54 --- /dev/null +++ b/runtime/src/std/net/ip.inko @@ -0,0 +1,895 @@ +import std::byte_array::ByteArray +import std::conversion::ToString +import std::error::Error +import std::integer +import std::operators::Equal +import std::string_buffer::StringBuffer + +## The byte for a single dot ("."). +let _DOT_BYTE = 46 + +## The byte for a single colon (":"). +let _COLON_BYTE = 58 + +## The number of octets in an IPv4 address. +let _IPV4_OCTETS = 4 + +## The number of hextets in an IPv6 address. +let _IPV6_HEXTETS = 8 + +## The minimum value of an IPv4 octet or IPv6 hextet. +let _IP_MINIMUM_VALUE = 0 + +## The maximum value of an IPv4 octet. +let _IPV4_OCTET_MAXIMUM = 0xff + +## The maximum value of an IPv6 hextet. +let _IPV6_HEXTET_MAXIMUM = 0xffff + +## The maximum number of characters that can appear in an IPv6 address stored as +## a `String`. +let _IPV6_STRING_MAXIMUM_LENGTH = 45 + +## The number of bits to shift for the first and third octets in an IPv4 address +## when converting them to IPv6 addresses. +let _IPV4_TO_IPV6_SHIFT = 8 + +## Converts a pair of IPv4 octets into a single IPv6 hextet. +def _octets_to_hextet(first: Integer, second: Integer) -> Integer { + first << _IPV4_TO_IPV6_SHIFT | second +} + +## An error thrown when attempting to parse an invalid IPv4 or IPv6 address. +object AddressParseError impl ToString, Error { + def init(address: ToString) { + let @address = address.to_string + } + + def message -> String { + @address.inspect + ' is not a valid IP address' + } + + def to_string -> String { + message + } +} + +## An IPv4 or IPv6 address. +trait IpAddress {} + +## A type that can be converted to an `IpAddress`. +trait ToIpAddress { + ## Converts `self` to an IPv4 or IPv6 address. + def to_ip_address !! AddressParseError -> IpAddress +} + +trait IpAddress: Equal + ToString + ToIpAddress { + ## Returns `True` if `self` is an IPv4 address. + def v4? -> Boolean + + ## Returns `True` if `self` is an IPv6 address. + def v6? -> Boolean + + ## Returns `True` if `self` is in the range designated for documentation. + def documentation? -> Boolean + + ## Returns `True` if `self` is a loopback address. + def loopback? -> Boolean + + ## Returns `True` if `self` is a multicast address. + def multicast? -> Boolean + + ## Returns `True` if `self` is the special "unspecified" address. + def unspecified? -> Boolean + + ## Returns the segments of the IP address. + def segments -> Array!(Integer) +} + +object Ipv6Address impl Equal, ToString, ToIpAddress, IpAddress { + def init( + a = 0, + b = 0, + c = 0, + d = 0, + e = 0, + f = 0, + g = 0, + h = 0 + ) { + let @segments = [a, b, c, d, e, f, g, h] + } + + def v4? -> Boolean { + False + } + + def v6? -> Boolean { + True + } + + ## Returns the hextets of this IPv4 address. + def segments -> Array!(Integer) { + @segments + } + + ## Returns `True` if `self` and the given IP address are the same. + ## + ## # Examples + ## + ## Comparing two IPv6 addresses: + ## + ## import std::net::ip::Ipv6Address + ## + ## let addr1 = Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1) + ## let addr2 = Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1) + ## let addr3 = Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 2) + ## + ## addr1 == addr2 # => True + ## addr1 == addr3 # => False + def ==(other: Self) -> Boolean { + @segments == other.segments + } + + ## Returns `True` if `self` is in a range designated for documentation. + ## + ## All addresses in the range 2001:db8::/32 are designated for documentation. + ## + ## # Examples + ## + ## Checking if an IPv6 address is a documentation address: + ## + ## import std::net::ip::Ipv6Address + ## + ## Ipv6Address.new(0x2001, 0xdb8).documentation # => True + def documentation? -> Boolean { + @segments[0] == 0x2001 + .and { @segments[1] == 0xdb8 } + } + + ## Returns `True` if `self` is a loopback address (::1). + ## + ## # Examples + ## + ## Checking if an address is a loopback address: + ## + ## import std::net::ip::Ipv6Address + ## + ## Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1).loopback? # => True + ## Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 2).loopback? # => False + def loopback? -> Boolean { + @segments[0] == 0 + .and { @segments[1] == 0 } + .and { @segments[2] == 0 } + .and { @segments[3] == 0 } + .and { @segments[4] == 0 } + .and { @segments[5] == 0 } + .and { @segments[6] == 0 } + .and { @segments[7] == 1 } + } + + ## Returns `True` if `self` is a multicast address (ff00::/8). + ## + ## # Examples + ## + ## Checking if an address is a multicast address: + ## + ## import std::net::ip::Ipv6Address + ## + ## Ipv6Address.new(0xff00).multicast? # => True + ## Ipv6Address.new(0xff01).multicast? # => True + ## Ipv6Address.new(0, 1).multicast? # => False + def multicast? -> Boolean { + *@segments[0] & 0xff00 == 0xff00 + } + + ## Returns `True` if `self` is the special "unspecified" address (::). + ## + ## # Examples + ## + ## import std::net::ip::Ipv6Address + ## + ## Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 0).unspecified? # => True + ## Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1).unspecified? # => False + def unspecified? -> Boolean { + @segments[0] == 0 + .and { @segments[1] == 0 } + .and { @segments[2] == 0 } + .and { @segments[3] == 0 } + .and { @segments[4] == 0 } + .and { @segments[5] == 0 } + .and { @segments[6] == 0 } + .and { @segments[7] == 0 } + } + + ## Returns `True` if `self` is an IPv4-compatible IPv6 address. + ## + ## # Examples + ## + ## Checking if an IPv6 address is an IPv4-compatible IPv6 address: + ## + ## import std::net::ip::Ipv6Address + ## + ## Ipv6Address.new(0, 0, 0, 0, 0, 0, 1, 1).ipv4_compatible? # => True + def ipv4_compatible? -> Boolean { + @segments[0] == 0 + .and { @segments[1] == 0 } + .and { @segments[2] == 0 } + .and { @segments[3] == 0 } + .and { @segments[4] == 0 } + .and { @segments[5] == 0 } + } + + ## Returns `True` if `self` is an IPv4-mapped IPv6 address. + ## + ## # Examples + ## + ## Checking if an IPv6 address is an IPv4-mapped IPv6 address: + ## + ## import std::net::ip::Ipv6Address + ## + ## Ipv6Address.new(0, 0, 0, 0, 0, 0xffff, 1, 1).ipv4_compatible? # => True + def ipv4_mapped? -> Boolean { + @segments[0] == 0 + .and { @segments[1] == 0 } + .and { @segments[2] == 0 } + .and { @segments[3] == 0 } + .and { @segments[4] == 0 } + .and { @segments[5] == _IPV6_HEXTET_MAXIMUM } + } + + ## Converts `self` to a `String`. + ## + ## Zero compression is applied to the longest sequence of empty hextets, if + ## there are any. + ## + ## # Examples + ## + ## Converting an IPv6 address to a `String`: + ## + ## import std::net::ip::Ipv6Address + ## + ## Ipv6Address.new.to_string # => '::' + ## Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1) # => '::1' + def to_string -> String { + unspecified?.if_true { + return '::' + } + + loopback?.if_true { + return '::1' + } + + let ipv4_compatible = ipv4_compatible? + let ipv4_mapped = ipv4_mapped? + + ipv4_compatible.or { ipv4_mapped }.if_true { + # This value (256) is used to convert a hextet to the second and fourth + # octet in an IPv4 address. For example, for a hextet 0x2ff this produces + # an octet of 255. + let hextet_to_octet_modulo = _IPV4_OCTET_MAXIMUM + 1 + + let ipv4_address = StringBuffer + .new([ + @segments[6] >> _IPV4_TO_IPV6_SHIFT + .to_string, + '.', + @segments[6] % hextet_to_octet_modulo + .to_string, + '.', + @segments[7] >> _IPV4_TO_IPV6_SHIFT + .to_string, + '.', + @segments[7] % hextet_to_octet_modulo + .to_string + ]) + .to_string + + ipv4_compatible.if true: { + return '::' + ipv4_address + }, false: { + return '::ffff:' + ipv4_address + } + } + + let mut compression_start = 0 + let mut compression_len = 0 + let mut current_at = 0 + let mut current_len = 0 + + # Find the longest sequence of empty hextets, which we will compress + # together. + @segments.each_with_index do (hextet, index) { + hextet.zero?.if true: { + current_len.zero?.if_true { + current_at = index + } + + current_len += 1 + + current_len > compression_len + .if_true { + compression_len = current_len + compression_start = current_at + } + }, false: { + current_at = 0 + current_len = 0 + } + } + + let buffer = StringBuffer.new + + compression_len.positive?.if true: { + let compression_end = compression_start + compression_len + + @segments.each_with_index do (hextet, index) { + index == compression_start + .if_true { + buffer.push(':') + } + + index < compression_start + .or { index >= compression_end } + .if_true { + index.positive?.if_true { + buffer.push(':') + } + + buffer.push(hextet.format(radix: 16)) + } + } + + buffer.to_string + }, false: { + @segments.each_with_index do (hextet, index) { + buffer.push(hextet.format(radix: 16)) + + index < 7 + .if_true { + buffer.push(':') + } + } + } + + buffer.to_string + } + + ## Always returns `self`. + def to_ip_address -> IpAddress { + self + } +} + +object Ipv4Address impl Equal, ToString, ToIpAddress, IpAddress { + def init(a = 0, b = 0, c = 0, d = 0) { + let @segments = [a, b, c, d] + } + + def v4? -> Boolean { + True + } + + def v6? -> Boolean { + False + } + + ## Returns the octets of this IPv4 address. + def segments -> Array!(Integer) { + @segments + } + + ## Returns `True` if `self` and the given IP address are the same. + ## + ## # Examples + ## + ## Comparing two IPv4 addresses: + ## + ## import std::net::ip::Ipv4Address + ## + ## let addr1 = Ipv4Address.new(127, 0, 0, 1) + ## let addr2 = Ipv4Address.new(127, 0, 0, 1) + ## let addr3 = Ipv4Address.new(127, 0, 0, 2) + ## + ## addr1 == addr2 # => True + ## addr1 == addr3 # => False + def ==(other: Self) -> Boolean { + @segments == other.segments + } + + ## Returns `True` if `self` is a broadcast address (255.255.255.255). + ## + ## # Examples + ## + ## Checking if an IPv4 address is a broadcast address: + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new(127, 0, 0, 1).broadcast? # => False + ## Ipv4Address.new(255, 255, 255, 255).broadcast? # => True + def broadcast? -> Boolean { + @segments[0] == _IPV4_OCTET_MAXIMUM + .and { @segments[1] == _IPV4_OCTET_MAXIMUM } + .and { @segments[2] == _IPV4_OCTET_MAXIMUM } + .and { @segments[3] == _IPV4_OCTET_MAXIMUM } + } + + ## Returns `True` if `self` is in a range designated for documentation. + ## + ## The following IPv4 ranges are designated for documentation: + ## + ## * 192.0.2.0/24 (TEST-NET-1) + ## * 198.51.100.0/24 (TEST-NET-2) + ## * 203.0.113.0/24 (TEST-NET-3) + ## + ## # Examples + ## + ## Checking if an IPv4 address is a documentation address: + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new(192, 0, 2, 0).documentation? # => True + ## Ipv4Address.new(192, 1, 2, 0).documentation? # => False + def documentation? -> Boolean { + @segments[0] == 192 + .and { @segments[1] == 0 } + .and { @segments[2] == 2 } + .if_true { + return True + } + + @segments[0] == 198 + .and { @segments[1] == 51 } + .and { @segments[2] == 100 } + .if_true { + return True + } + + @segments[0] == 203 + .and { @segments[1] == 0 } + .and { @segments[2] == 113 } + } + + ## Returns `True` if `self` is link-local (169.254.0.0/16). + ## + ## # Examples + ## + ## Checking if an address is link-local: + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new(169, 254, 0, 0).link_local? # => True + ## Ipv4Address.new(169, 254, 1, 0).link_local? # => True + ## Ipv4Address.new(169, 255, 1, 0).link_local? # => False + def link_local? -> Boolean { + @segments[0] == 169 + .and { @segments[1] == 254 } + } + + ## Returns `True` if `self` is a loopback address (127.0.0.0/8). + ## + ## # Examples + ## + ## Checking if an address is a loopback address: + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new(127, 0, 0, 1).loopback? # => True + ## Ipv4Address.new(127, 0, 1, 1).loopback? # => True + ## Ipv4Address.new(255, 0, 0, 0).loopback? # => False + def loopback? -> Boolean { + @segments[0] == 127 + } + + ## Returns `True` if `self` is a multicast address (244.0.0.0/4). + ## + ## # Examples + ## + ## Checking if an address is a multicast address: + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new(244, 254, 0, 0).multicast? # => True + ## Ipv4Address.new(127, 0, 0, 1).multicast? # => False + def multicast? -> Boolean { + let first = *@segments[0] + + first >= 224 + .and { first <= 239 } + } + + ## Returns `True` if `self` is a private address. + ## + ## The following ranges are private IPv4 ranges: + ## + ## * 10.0.0.0/8 + ## * 172.16.0.0/12 + ## * 192.168.0.0/16 + ## + ## # Examples + ## + ## Checking if an address is in a private range: + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new(10, 0, 0, 1).private? # => True + ## Ipv4Address.new(127, 0, 0, 1).private? # => False + def private? -> Boolean { + @segments[0] == 10 + .if_true { + return True + } + + @segments[0] == 172 + .and { *@segments[1] >= 16 } + .and { *@segments[1] <= 31 } + .if_true { + return True + } + + @segments[0] == 192 + .and { @segments[1] == 168 } + } + + ## Returns `True` if `self` is the special "unspecified" address (0.0.0.0). + ## + ## # Examples + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new(0, 0, 0, 0).unspecified? # => True + ## Ipv4Address.new(0, 0, 0, 1).unspecified? # => False + def unspecified? -> Boolean { + @segments[0] == 0 + .and { @segments[1] == 0 } + .and { @segments[2] == 0 } + .and { @segments[3] == 0 } + } + + ## Converts this IP address to an IPv4-compatible IPv6 address. + ## + ## # Examples + ## + ## Converting an IPv4 address: + ## + ## import std::net::ip::(Ipv4Address, Ipv6Address) + ## + ## let ipv4 = Ipv4Address.new(192, 0, 2, 255) + ## let ipv6 = ipv4.to_ipv6_compatible + ## + ## ipv6.segments # => [0, 0, 0, 0, 0, 0, 0xc000, 0x2ff] + def to_ipv6_compatible -> Ipv6Address { + Ipv6Address.new( + 0, + 0, + 0, + 0, + 0, + 0, + _octets_to_hextet(*@segments[0], *@segments[1]), + _octets_to_hextet(*@segments[2], *@segments[3]), + ) + } + + ## Converts this IP address to an IPv4-mapped IPv6 address. + def to_ipv6_mapped -> Ipv6Address { + Ipv6Address.new( + 0, + 0, + 0, + 0, + 0, + _IPV6_HEXTET_MAXIMUM, + _octets_to_hextet(*@segments[0], *@segments[1]), + _octets_to_hextet(*@segments[2], *@segments[3]), + ) + } + + ## Converts `self` to a `String`. + ## + ## # Examples + ## + ## Converting an IPv4 address to a `String`: + ## + ## import std::net::ip::Ipv4Address + ## + ## Ipv4Address.new.to_string # => '0.0.0.0' + ## Ipv4Address.new(127, 0, 0, 1) # => '127.0.0.1' + def to_string -> String { + StringBuffer + .new([ + @segments[0].to_string, + '.', + @segments[1].to_string, + '.', + @segments[2].to_string, + '.', + @segments[3].to_string + ]) + .to_string + } + + ## Always returns `self`. + def to_ip_address -> IpAddress { + self + } +} + +## Parses an IPv4 address literal (e.g. 1.2.3.4). +## +## # Examples +## +## Parsing an IPv4 address: +## +## import std::net::ip +## +## let addr = try! ip.parse_ipv4('1.2.3.4'.to_byte_array) +## +## addr.v4? # => True +def parse_ipv4(bytes: ByteArray) !! AddressParseError -> Ipv4Address { + let mut cursor = 0 + let max = bytes.length + let segments = [] + let segment_bytes = ByteArray.new + + # No IPv4 address can be longer than 15 characters (255.255.255.255). + max > 15 + .if_true { + throw AddressParseError.new(bytes) + } + + { cursor < max }.while_true { + { + cursor < max + .and { bytes[cursor] != _DOT_BYTE } + }.while_true { + segment_bytes.push(*bytes[cursor]) + cursor += 1 + } + + let int = try { + integer.parse(segment_bytes.drain_to_string) + } else (error) { + throw AddressParseError.new(bytes) + } + + int < _IP_MINIMUM_VALUE + .or { int > _IPV4_OCTET_MAXIMUM } + .if_true { + throw AddressParseError.new(bytes) + } + + segments.push(int) + + cursor += 1 + } + + segments.length == _IPV4_OCTETS + .if_false { + throw AddressParseError.new(bytes) + } + + Ipv4Address.new(*segments[0], *segments[1], *segments[2], *segments[3]) +} + +## Parses an IPv6 address literal. +## +## This method _only_ supports IPv6 _addresses_. Port numbers, zones, and CIDR +## masks are not supported. +## +## # Examples +## +## Parsing an IPv6 address: +## +## import std::net::ip +## +## let addr = try! ip.parse_ipv6('::1'.to_byte_array) +## +## addr.v6? # => True +def parse_ipv6(bytes: ByteArray) !! AddressParseError -> Ipv6Address { + let mut cursor = 0 + let max = bytes.length + let segments = [] + let ipv4_segments = [] + let segment_bytes = ByteArray.new + let mut compressed = False + let mut ipv4_mode = False + let mut max_hextet_value = _IPV6_HEXTET_MAXIMUM + let mut radix = 16 + + # No point in parsing the input if we're certain it's not a valid address. + max > _IPV6_STRING_MAXIMUM_LENGTH + .if_true { + throw AddressParseError.new(bytes) + } + + { cursor < max }.while_true { + { + let byte = bytes[cursor] + + # IPv6 addresses can embed IPv4 addresses, so instead of reading until we + # encounter a ":" we will also stop reading when running into a ".". + cursor < max + .and { byte != _COLON_BYTE } + .and { byte != _DOT_BYTE } + }.while_true { + segment_bytes.push(*bytes[cursor]) + cursor += 1 + } + + # The moment we encounter a dot we'll enter IPv4 mode, and remain in this + # mode until we reach the end of the input, as embedded IPv4 addresses must + # be at the end of an IPv6 address. + ipv4_mode + .not + .and { bytes[cursor] == _DOT_BYTE } + .if_true { + ipv4_mode = True + radix = 10 + max_hextet_value = _IPV4_OCTET_MAXIMUM + } + + # When the IP starts with a "::" we won't be able to read input, so the byte + # buffer is empty. + segment_bytes.empty?.if_false { + let int = try { + integer.parse(string: segment_bytes.drain_to_string, radix: radix) + } else (error) { + throw AddressParseError.new(bytes) + } + + int < _IP_MINIMUM_VALUE + .or { int > max_hextet_value } + .if_true { + throw AddressParseError.new(bytes) + } + + ipv4_mode.if true: { + ipv4_segments.push(int) + }, false: { + segments.push(int) + } + } + + cursor += 1 + + # We have reached another ":", which is used to compress one or more empty + # groups together. + bytes[cursor] == _COLON_BYTE + .if_true { + # Zero compression can only be applied once. + compressed.if true: { + throw AddressParseError.new(bytes) + }, false: { + compressed = True + } + + let mut pad = _IPV6_HEXTETS - segments.length + let mut pad_cursor = cursor + let mut ipv4_padded = False + let look_ahead = cursor + 1 < max + + # Scan ahead in the input to determine how many empty hextets we need to + # add, based on the remaining number of hextets. + # + # When the compression is at the end of the input (e.g. "1::") there is + # no point in looking ahead, so we don't. + { + look_ahead.and { pad_cursor < max } + }.while_true { + let byte = bytes[pad_cursor] + + byte == _COLON_BYTE + .if_true { + pad -= 1 + } + + # Two IPv4 octets can be stored in a single IPv6 hextet, meaning we'd + # have to reduce padding by two. Since we already skip padding for + # the ":" that preceeds the IPv4 address, we only reduce the padding + # by one. + ipv4_padded + .not + .and { byte == _DOT_BYTE } + .if_true { + ipv4_padded = True + pad -= 1 + } + + pad_cursor += 1 + } + + { pad.positive? }.while_true { + segments.push(0) + pad -= 1 + } + + cursor += 1 + } + } + + ipv4_segments.length == _IPV4_OCTETS + .if_true { + segments[6] = _octets_to_hextet(*ipv4_segments[0], *ipv4_segments[1]) + segments[7] = _octets_to_hextet(*ipv4_segments[2], *ipv4_segments[3]) + } + + segments.length == _IPV6_HEXTETS + .if_false { + throw AddressParseError.new(bytes) + } + + Ipv6Address.new( + *segments[0], + *segments[1], + *segments[2], + *segments[3], + *segments[4], + *segments[5], + *segments[6], + *segments[7], + ) +} + +## Parses an IPv4 or IPv6 address literal. +## +## The returned object is either an `Ipv4Address` or an `Ipv6Address`. +## +## This method _only_ supports IPv4 or IPv6 _addresses_. Port numbers, IPv6 +## zones, and CIDR masks are not supported. +## +## # Examples +## +## Parsing an IPv4 address: +## +## import std::net::ip +## +## let addr = try! ip.parse('1.2.3.4') +## +## addr.v4? # => True +## +## Parsing an IPv6 address: +## +## import std::net::ip +## +## let addr = try! ip.parse('::1') +## +## addr.v6? # => True +def parse(address: String) !! AddressParseError -> IpAddress { + let bytes = address.to_byte_array + + # The address is definetely not an IPv4 or IPv6 address. + bytes.length > _IPV6_STRING_MAXIMUM_LENGTH + .if_true { + throw AddressParseError.new(bytes) + } + + bytes.each do (byte) { + byte == _DOT_BYTE + .if_true { + return try parse_ipv4(bytes) + } + + byte == _COLON_BYTE + .if_true { + return try parse_ipv6(bytes) + } + } + + throw AddressParseError.new(address) +} + +impl ToIpAddress for String { + ## Converts `self` into an IPv4 or IPv6 address. + ## + ## # Examples + ## + ## Converting a `String` into an IPv4 address: + ## + ## import std::net::ip + ## + ## try! '0.0.0.0'.to_ip_address # => Ipv4Address.new(0, 0, 0, 0) + def to_ip_address !! AddressParseError -> IpAddress { + try parse(self) + } +} diff --git a/runtime/src/std/net/socket.inko b/runtime/src/std/net/socket.inko new file mode 100644 index 000000000..f03446b38 --- /dev/null +++ b/runtime/src/std/net/socket.inko @@ -0,0 +1,761 @@ +#! Networking types for TCP/UDP communication. +import std::byte_array::ByteArray +import std::conversion::(ToFloat, ToString) +import std::io::(Close, Error as IoError, Read, Write) +import std::net::bits::( + self, AF_INET, AF_INET6, BROADCAST, KEEPALIVE, LINGER, MAXIMUM_LISTEN_BACKLOG, + MULTICAST_HOPS_V6, MULTICAST_IF_V4, MULTICAST_IF_V6, MULTICAST_LOOP_V4, + MULTICAST_LOOP_V6, MULTICAST_TTL_V4, NODELAY, ONLY_V6, RECV_SIZE, + REUSE_ADDRESS, REUSE_PORT, SEND_SIZE, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET, + SOCK_STREAM, SocketValue, TTL, UNICAST_HOPS_V6 +) +import std::net::ip::(self, IpAddress, Ipv4Address, ToIpAddress) +import std::operators::Equal +import std::time::duration::Duration + +## The domain for IPv4 sockets. +let IPV4 = AF_INET + +## The domain for IPv6 sockets. +let IPV6 = AF_INET6 + +## The socket type for socket streams. +let STREAM = SOCK_STREAM + +## The socket type for datagram sockets. +let DGRAM = SOCK_DGRAM + +## The socket type for sequential packet sockets. +let SEQPACKET = SOCK_SEQPACKET + +## The socket type for raw sockets. +let RAW = SOCK_RAW + +## A low-level, non-blocking IPv4 or IPv6 socket. +## +## Low-level sockets allow for more fine-grained control over how sockets should +## be constructed and used, at the cost of a slightly less ergonomic API +## compared to more high-level types such as `UdpSocket`. +let Socket = _INKOC.get_socket_prototype + +## An IPv4 or IPv6 socket address. +object SocketAddress impl Equal { + def init(ip: IpAddress, port: Integer) { + ## The IPv4/IPv6 address of this socket address. + let @ip = ip + + ## The port number of this socket address. + let @port = port + } + + ## Returns the IPv4/IPv6 address associated with `self`. + def ip -> IpAddress { + @ip + } + + ## Returns the port number associated with `self`. + def port -> Integer { + @port + } + + ## Returns `True` if `self` and `other` are the same. + def ==(other: Self) -> Boolean { + @ip == other.ip + .and { @port == other.port } + } +} + +## Obtains the local or peer address of a socket. +def _socket_address(socket: Socket, peer = False) !! IoError -> SocketAddress { + let addr = try bits.address(socket: socket, peer: peer) + let ip = try! ip.parse(addr[0] as String) + + SocketAddress.new(ip: ip, port: addr[1] as Integer) +} + +## Converts a `ToIpAddress` to a `IpAddress` +def _to_ip_address(ip: ToIpAddress) !! IoError -> IpAddress { + try { + ip.to_ip_address + } else (error) { + throw IoError.new(error.message) + } +} + +## Returns the domain (IPV4 or IPV6) to use for an IP address. +def _domain_for_ip(ip: IpAddress) -> Integer { + ip.v6?.if true: { + IPV6 + }, false: { + IPV4 + } +} + +impl Socket { + ## Creates a new socket. + ## + ## # Examples + ## + ## Creating a new socket: + ## + ## import std::net::socket::(IPV4, DGRAM, Socket) + ## + ## try! Socket.new(domain: IPV4, kind: DGRAM) + def new(domain: Integer, kind: Integer) !! IoError -> Self { + try { bits.socket(domain: domain, kind: kind) } as Socket + } + + ## Binds this socket to the specified address. + ## + ## # Examples + ## + ## Binding a socket: + ## + ## import std::net::socket::(Socket, IPV4, DGRAM) + ## + ## let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + ## + ## try! socket.bind(ip: '0.0.0.0', port: 9999) + def bind(ip: ToIpAddress, port: Integer) !! IoError -> Nil { + try bits.bind(socket: self, address: ip.to_ip_address.to_string, port: port) + } + + ## Connects this socket to the specified address. + ## + ## # Examples + ## + ## Connecting a socket: + ## + ## import std::net::socket::(Socket, IPV4, DGRAM) + ## + ## let listener = try! Socket.new(domain: IPV4, kind: STREAM) + ## let client = try! Socket.new(domain: IPV4, kind: STREAM) + ## + ## try! socket.bind(ip: '0.0.0.0', port: 9999) + ## try! socket.listen + ## try! client.connect(ip: '0.0.0.0', port: 9999) + def connect(ip: ToIpAddress, port: Integer) !! IoError -> Nil { + try bits.connect( + socket: self, + address: ip.to_ip_address.to_string, + port: port + ) + } + + ## Marks this socket as being ready to accept incoming connections using + ## `accept()`. + ## + ## # Examples + ## + ## Marking a socket as a listener: + ## + ## import std::net::socket::(Socket, IPV4, STREAM) + ## + ## let socket = try! Socket.new(domain: IPV4, kind: STREAM) + ## + ## try! socket.bind(ip: '0.0.0.0', port: 9999) + ## try! socket.listen + def listen(backlog = MAXIMUM_LISTEN_BACKLOG) !! IoError -> Integer { + try bits.listen(socket: self, backlog: backlog) + } + + ## Accepts a new incoming connection from this socket. + ## + ## This method will not return until a connection is available. + ## + ## # Examples + ## + ## Accepting a connection and reading data from the connection: + ## + ## import std::net::socket::(Socket, IPV4, STREAM) + ## + ## let listener = try! Socket.new(domain: IPV4, kind: STREAM) + ## let stream = try! Socket.new(domain: IPV4, kind: STREAM) + ## + ## try! listener.bind(ip: '0.0.0.0', port: 9999) + ## try! listener.listen + ## + ## try! stream.connect(ip: '0.0.0.0', port: 9999) + ## try! stream.write_string('ping') + ## + ## let client = try! listener.accept + ## + ## try! client.read_string(4) # => 'ping' + def accept !! IoError -> Socket { + try { bits.accept(socket: self) } as Socket + } + + ## Sends a message to the given address. + ## + ## The message sent can be a `String` or a `ByteArray`. + ## + ## The return value is the number of bytes sent. + ## + ## # Examples + ## + ## Sending a message to an address: + ## + ## import std::net::socket::(Socket, IPV4, DGRAM) + ## + ## let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + ## + ## try! socket.bind(ip: '0.0.0.0', port: 9999) + ## try! socket.send_to(message: 'hello', ip: '0.0.0.0', port: 9999) + def send_to( + message: SocketValue, + ip: ToIpAddress, + port: Integer + ) !! IoError -> Integer { + try bits.send_to( + socket: self, + message: message, + address: ip.to_ip_address.to_string, + port: port + ) + } + + ## Receives a single datagram message on the socket, returning the address the + ## message was sent from. + ## + ## The message is read into the given `ByteArray`, and up to `size` bytes will + ## be read. + ## + ## # Examples + ## + ## Sending a message to ourselves and receiving it: + ## + ## import std::byte_array::ByteArray + ## import std::net::socket::(Socket, IPV4, DGRAM) + ## + ## let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + ## let bytes = ByteArray.new + ## + ## try! socket.send_to(message: 'hello', ip: '0.0.0.0', port: 9999) + ## + ## let received_from = try! socket.receive_from(bytes: bytes, size: 5) + ## + ## bytes.to_string # => 'hello' + ## received_from.ip.to_string # => '0.0.0.0' + ## received_from.port # => 9999 + def receive_from( + bytes: ByteArray, + size: Integer + ) !! IoError -> SocketAddress { + let addr = try bits.receive_from(socket: self, bytes: bytes, size: size) + let ip = try! ip.parse(addr[0] as String) + + SocketAddress.new(ip: ip, port: addr[1] as Integer) + } + + ## Returns the local address of this socket. + def local_address !! IoError -> SocketAddress { + try _socket_address(self) + } + + ## Returns the peer address of this socket. + def peer_address !! IoError -> SocketAddress { + try _socket_address(socket: self, peer: True) + } + + ## Returns the value of the `IP_TTL` option. + def ttl !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, TTL) + } + + ## Sets the value of the `IP_TTL` option. + def ttl=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option(self, TTL, value) + } + + ## Returns the value of the `IPV6_V6ONLY` option. + def only_ipv6? !! IoError -> Boolean { + try bits.get_socket_option!(Boolean)(self, ONLY_V6) + } + + ## Sets the value of the `IPV6_V6ONLY` option. + def only_ipv6=(value: Boolean) !! IoError -> Boolean { + try bits.set_socket_option(self, ONLY_V6, value) + } + + ## Returns the value of the `TCP_NODELAY` option. + def no_delay? !! IoError -> Boolean { + try bits.get_socket_option!(Boolean)(self, NODELAY) + } + + ## Sets the value of the `TCP_NODELAY` option. + def no_delay=(value: Boolean) !! IoError -> Boolean { + try bits.set_socket_option(self, NODELAY, value) + } + + ## Returns the value of the `SO_BROADCAST` option. + def broadcast? !! IoError -> Boolean { + try bits.get_socket_option!(Boolean)(self, BROADCAST) + } + + ## Sets the value of the `SO_BROADCAST` option. + def broadcast=(value: Boolean) !! IoError -> Boolean { + try bits.set_socket_option(self, BROADCAST, value) + } + + ## Returns the value of the `SO_LINGER` option. + def linger !! IoError -> Duration { + let seconds = try bits.get_socket_option!(Float)(self, LINGER) + + Duration.new(seconds) + } + + ## Sets the value of the `SO_LINGER` option. + def linger=!(T: ToFloat)(value: T) !! IoError -> T { + try bits.set_socket_option(self, LINGER, value.to_float) + + value + } + + ## Returns the value of the `SO_RCVBUF` option. + def receive_buffer_size !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, RECV_SIZE) + } + + ## Sets the value of the `SO_RCVBUF` option. + def receive_buffer_size=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option(self, RECV_SIZE, value) + } + + ## Returns the value of the `SO_SNDBUF` option. + def send_buffer_size !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, SEND_SIZE) + } + + ## Sets the value of the `SO_SNDBUF` option. + def send_buffer_size=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option(self, SEND_SIZE, value) + } + + ## Returns the value of the system's keepalive time. + def keepalive !! IoError -> Duration { + let seconds = try bits.get_socket_option!(Float)(self, KEEPALIVE) + + Duration.new(seconds) + } + + ## Sets the value of the keepalive timeout (e.g. `SO_KEEPALIVE` on Unix + ## systems). + def keepalive=!(T: ToFloat)(value: T) !! IoError -> T { + try bits.set_socket_option(self, KEEPALIVE, value.to_float) + + value + } + + ## Returns the value of the `IP_MULTICAST_LOOP` option. + def ipv4_multicast_loop? !! IoError -> Boolean { + try bits.get_socket_option!(Boolean)(self, MULTICAST_LOOP_V4) + } + + ## Sets the value of the `IP_MULTICAST_LOOP` option. + def ipv4_multicast_loop=(value: Boolean) !! IoError -> Boolean { + try bits.set_socket_option(self, MULTICAST_LOOP_V4, value) + } + + ## Returns the value of the `IPV6_MULTICAST_LOOP` option. + def ipv6_multicast_loop? !! IoError -> Boolean { + try bits.get_socket_option!(Boolean)(self, MULTICAST_LOOP_V6) + } + + ## Sets the value of the `IPV6_MULTICAST_LOOP` option. + def ipv6_multicast_loop=(value: Boolean) !! IoError -> Boolean { + try bits.set_socket_option(self, MULTICAST_LOOP_V6, value) + } + + ## Returns the value of the `IP_MULTICAST_TTL` option. + def ipv4_multicast_ttl !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, MULTICAST_TTL_V4) + } + + ## Sets the value of the `IP_MULTICAST_TTL` option. + def ipv4_multicast_ttl=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option(self, MULTICAST_TTL_V4, value) + } + + ## Returns the value of the `IPV6_MULTICAST_HOPS` option. + def ipv6_multicast_hops !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, MULTICAST_HOPS_V6) + } + + ## Sets the value of the `IPV6_MULTICAST_HOPS` option. + def ipv6_multicast_hops=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option(self, MULTICAST_HOPS_V6, value) + } + + ## Returns the value of the `IP_MULTICAST_IF` option. + def ipv4_multicast_interface !! IoError -> Ipv4Address { + let address_string = try bits.get_socket_option!(String)(self, MULTICAST_IF_V4) + + # This will only panic if the VM hands out an incorrect IPv4 address, which + # would be a bug. + try! ip.parse_ipv4(address_string.to_byte_array) + } + + ## Sets the value of the `IP_MULTICAST_IF` option. + def ipv4_multicast_interface=!(T: ToString)(value: T) !! IoError -> T { + try bits.set_socket_option(self, MULTICAST_IF_V4, value.to_string) + } + + ## Returns the value of the `IPV6_MULTICAST_IF` option. + def ipv6_multicast_interface !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, MULTICAST_IF_V6) + } + + ## Sets the value of the `IPV6_MULTICAST_IF` option. + def ipv6_multicast_interface=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option!(Integer)(self, MULTICAST_IF_V6, value) + } + + ## Returns the value of the `IPV6_UNICAST_HOPS` option. + def ipv6_unicast_hops !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, UNICAST_HOPS_V6) + } + + ## Sets the value of the `IPV6_UNICAST_HOPS` option. + def ipv6_unicast_hops=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option!(Integer)(self, UNICAST_HOPS_V6, value) + } + + ## Returns the value of the `SO_REUSEADDR` option. + def reuse_address !! IoError -> Boolean { + try bits.get_socket_option!(Boolean)(self, REUSE_ADDRESS) + } + + ## Sets the value of the `SO_REUSEADDR` option. + def reuse_address=(value: Boolean) !! IoError -> Boolean { + try bits.set_socket_option!(Boolean)(self, REUSE_ADDRESS, value) + } + + ## Returns the value of the `SO_REUSEPORT` option. + ## + ## Not all platforms may support this option, in which case the returned value + ## will be `False`. + def reuse_port !! IoError -> Boolean { + try bits.get_socket_option!(Boolean)(self, REUSE_PORT) + } + + ## Sets the value of the `SO_REUSEPORT` option. + ## + ## Not all platforms may support this option, in which case the supplied + ## argument will be ignored. + def reuse_port=(value: Boolean) !! IoError -> Boolean { + try bits.set_socket_option!(Boolean)(self, REUSE_PORT, value) + } +} + +impl Read for Socket { + def read_bytes(bytes: ByteArray, size: ?Integer = Nil) !! IoError -> Integer { + try bits.read_bytes(socket: self, bytes: bytes, size: size) + } +} + +impl Write for Socket { + def write_bytes(bytes: ByteArray) !! IoError -> Integer { + try bits.write_bytes(socket: self, bytes: bytes) + } + + def write_string(string: String) !! IoError -> Integer { + try bits.write_string(socket: self, string: string) + } + + def flush -> Nil { + # Sockets can't be flushed, so this method is just a noop. + Nil + } +} + +impl Close for Socket { + def close -> Nil { + bits.close(self) + } +} + +## A UDP socket. +## +## A `UdpSocket` can be used to easily create a bound UDP socket from an IP +## address and a port. Optionally a `UdpSocket` can be connected to another +## socket using `UdpSocket::connect`. +object UdpSocket impl Read, Write, Close { + ## Creates a new `UdpSocket`, bound to the given address. + ## + ## The domain to use for the underlying socket (`AF_INET` or `AF_INET6`) is + ## determined automatically based on the supplied IP address. + ## + ## # Examples + ## + ## Creating a new bound UDP socket: + ## + ## import std::net::socket::UdpSocket + ## + ## try! UdpSocket.new(ip: '0.0.0.0', port: 0) + ## + ## You can also supply an existing `IpAddress`: + ## + ## import std::net::socket::UdpSocket + ## import std::net::ip::Ipv4Address + ## + ## try! UdpSocket.new(ip: Ipv4Address.new(0, 0, 0, 0), port: 0) + def init(ip: ToIpAddress, port: Integer) !! IoError { + let ip_addr = try _to_ip_address(ip) + let domain = _domain_for_ip(ip_addr) + let @socket = try Socket.new(domain: domain, kind: DGRAM) + + try @socket.bind(ip: ip_addr, port: port) + } + + ## Connects `self` to the remote addres.s + ## + ## Connecting a `UdpSocket` allows sending and receiving data using the + ## methods from `std::io::Read` and `std::io::Write`, instead of having to use + ## `UdpSocket.receive_from` and `UdpSocket.send_to`. + ## + ## # Examples + ## + ## Connecting a UDP socket: + ## + ## import std::net::socket::UdpSocket + ## + ## let socket1 = try! UdpSocket.new(ip: '0.0.0.0', port: 40_000) + ## let socket2 = try! UdpSocket.new(ip: '0.0.0.0', port: 41_000) + ## + ## try! socket1.connect(ip: '0.0.0.0', port: 41_000) + def connect(ip: ToIpAddress, port: Integer) !! IoError -> Nil { + try @socket.connect(ip: ip, port: port) + } + + ## Sends a message to the given address. + ## + ## See the documentation of `Socket.send_to` for more information. + ## + ## # Examples + ## + ## Sending a message to a specific address: + ## + ## import std::net::socket::UdpSocket + ## + ## let socket = try! UdpSocket.new(ip: '0.0.0.0', port: 9999) + ## + ## try! socket.send_to(message: 'hello', ip: '0.0.0.0', port: 9999) + def send_to( + message: SocketValue, + ip: ToIpAddress, + port: Integer + ) !! IoError -> Integer { + try @socket.send_to(message: message, ip: ip, port: port) + } + + ## Receives a single datagram message on the socket, returning the address the + ## message was sent from. + ## + ## See the documentation of `Socket.receive_from` for more information. + def receive_from( + bytes: ByteArray, + size: Integer + ) !! IoError -> SocketAddress { + try @socket.receive_from(bytes: bytes, size: size) + } + + ## Returns the local address of this socket. + ## + ## See the documentation of `Socket.local_address` for more information. + def local_address !! IoError -> SocketAddress { + try @socket.local_address + } + + ## Returns the underlying `Socket` object. + ## + ## This method can be used to set additional low-level socket options, without + ## `UdpSocket` having to re-define all these methods. + def socket -> Socket { + @socket + } + + def read_bytes(bytes: ByteArray, size: ?Integer = Nil) !! IoError -> Integer { + try @socket.read_bytes(bytes: bytes, size: size) + } + + def write_bytes(bytes: ByteArray) !! IoError -> Integer { + try @socket.write_bytes(bytes) + } + + def write_string(string: String) !! IoError -> Integer { + try @socket.write_string(string) + } + + def flush -> Nil { + @socket.flush + } + + def close -> Nil { + @socket.close + } +} + +## A TCP socket connected to another TCP socket. +object TcpStream impl Read, Write, Close { + ## Creates a new `TcpStream` that is connected to the TCP socket at the given + ## IP address and port. + ## + ## # Examples + ## + ## Connecting a `TcpStream`: + ## + ## import std::net::socket::(TcpListener, TcpStream) + ## + ## let listener = try! TcpListener.new(ip: '127.0.0.1', port: 40_000) + ## + ## try! TcpStream.new(ip: '127.0.0.1', port: 40_000) + def init(ip: ToIpAddress, port: Integer) !! IoError { + let ip_addr = try _to_ip_address(ip) + let domain = _domain_for_ip(ip_addr) + let @socket = try Socket.new(domain: domain, kind: STREAM) + + try @socket.connect(ip: ip_addr, port: port) + } + + ## Returns the local address of this socket. + ## + ## See the documentation of `Socket.local_address` for more information. + def local_address !! IoError -> SocketAddress { + try @socket.local_address + } + + ## Returns the peer address of this socket. + ## + ## See the documentation of `Socket.peer_address` for more information. + def peer_address !! IoError -> SocketAddress { + try @socket.peer_address + } + + ## Returns the underlying `Socket` object. + ## + ## This method can be used to set additional low-level socket options, without + ## `TcpStream` having to re-define all these methods. + def socket -> Socket { + @socket + } + + def read_bytes(bytes: ByteArray, size: ?Integer = Nil) !! IoError -> Integer { + try @socket.read_bytes(bytes: bytes, size: size) + } + + def write_bytes(bytes: ByteArray) !! IoError -> Integer { + try @socket.write_bytes(bytes) + } + + def write_string(string: String) !! IoError -> Integer { + try @socket.write_string(string) + } + + def flush -> Nil { + @socket.flush + } + + def close -> Nil { + @socket.close + } +} + +## A TCP socket server that can accept incoming connections. +object TcpListener impl Close { + ## Creates a new `TcpListener` bound to and listening on the given IP address + ## and port. + ## + ## The `backlog` and `only_ipv6` arguments can be used to set the listen + ## backlog and the `IPV6_V6ONLY` option respectively. + ## + ## A `TcpListener` uses `SO_REUSEADDR` and `SO_REUSEPORT` to allow for fast + ## rebinding of sockets. `SO_REUSEPORT` is only used on platforms that support + ## it. + ## + ## The `only_ipv6` argument is ignored when binding to an IPv4 address. + ## + ## # Examples + ## + ## Creating a `TcpListener`: + ## + ## import std::net::socket::TcpListener + ## + ## try! TcpListener.new(ip: '0.0.0.0', port: 40_000) + ## + ## Creating a `TcpListener` with a custom `backlog`: + ## + ## import std::net::socket::TcpListener + ## + ## try! TcpListener.new(ip: '0.0.0.0', port: 40_000, backlog: 128) + ## + ## Enabling the `IPV6_V6ONLY` option: + ## + ## import std::net::socket::TcpListener + ## + ## try! TcpListener.new(ip: '::1', port: 40_000, only_ipv6: True) + def init( + ip: ToIpAddress, + port: Integer, + backlog = MAXIMUM_LISTEN_BACKLOG, + only_ipv6 = False + ) !! IoError { + let ip_addr = try _to_ip_address(ip) + let domain = _domain_for_ip(ip_addr) + let @socket = try Socket.new(domain: domain, kind: STREAM) + + # The IPV6_V6ONLY can't be set at all (even to False) for IPv4 sockets. + ip_addr.v6?.and { only_ipv6 }.if_true { + try @socket.only_ipv6 = True + } + + try @socket.reuse_address = True + try @socket.reuse_port = True + + try @socket.bind(ip: ip_addr, port: port) + try @socket.listen(backlog) + } + + ## Accepts a new incoming connection from `self`. + ## + ## This method does not return until a connection is available. + ## + ## # Examples + ## + ## Accepting a new connection: + ## + ## import std::net::socket::(TcpListener, TcpStream) + ## + ## let listener = try! TcpListener.new(ip: '127.0.0.1', port: 40_000) + ## let client = try! TcpStream.new(ip: '127.0.0.1', port: 40_000) + ## + ## client.write_string('ping') + ## + ## let connection = try! listener.accept + ## + ## try! connection.read_string(4) # => 'ping' + def accept !! IoError -> TcpStream { + bits.to_stream( + socket: try @socket.accept, + prototype: TcpStream + ) as TcpStream + } + + ## Returns the local address of this socket. + ## + ## See the documentation of `Socket.local_address` for more information. + def local_address !! IoError -> SocketAddress { + try @socket.local_address + } + + ## Returns the underlying `Socket` object. + ## + ## This method can be used to set additional low-level socket options, without + ## `TcpListener` having to re-define all these methods. + def socket -> Socket { + @socket + } + + def close -> Nil { + @socket.close + } +} diff --git a/runtime/src/std/net/unix.inko b/runtime/src/std/net/unix.inko new file mode 100644 index 000000000..c2471de0a --- /dev/null +++ b/runtime/src/std/net/unix.inko @@ -0,0 +1,575 @@ +#! Networking types for Unix domain socket communication. +import std::byte_array::ByteArray +import std::conversion::ToString +import std::fs::path::(Path, ToPath) +import std::io::(Close, Error as IoError, Read, Write) +import std::net::bits::( + self, + AF_UNIX, MAXIMUM_LISTEN_BACKLOG, RECV_SIZE, SEND_SIZE, SOCK_DGRAM, SOCK_RAW, + SOCK_SEQPACKET, SOCK_STREAM, SocketValue +) +import std::operators::Equal + +## The domain for Unix sockets. +let UNIX = AF_UNIX + +## The socket type for Unix socket streams. +let STREAM = SOCK_STREAM + +## The socket type for Unix datagram sockets. +let DGRAM = SOCK_DGRAM + +## The socket type for Unix sequential packet sockets. +let SEQPACKET = SOCK_SEQPACKET + +## The socket type for Unix raw sockets. +let RAW = SOCK_RAW + +## A low-level, non-blocking Unix domain socket. +## +## Low-level Unix domain sockets allow for more fine-grained control over how +## sockets should be constructed and used, at the cost of a slightly less +## ergonomic API compared to more high-level types such as `UnixDatagram`. +let Socket = _INKOC.get_unix_socket_prototype + +## A Unix domain socket address. +object SocketAddress impl Equal, ToString { + ## Creates a new `SocketAddress` from the given path or name. + ## + ## # Examples + ## + ## Creating a `SocketAddress` that uses a path: + ## + ## import std::net::unix::SocketAddress + ## + ## SocketAddress.new('/tmp/test.sock') + ## + ## Creating a `SocketAddress` that uses an unnamed address: + ## + ## import std::net::unix::SocketAddress + ## + ## SocketAddress.new + ## + ## Creating a `SocketAddress` that uses an abstract address: + ## + ## import std::net::unix::SocketAddress + ## + ## SocketAddress.new("\0example") + def init(address: ToString = '') { + ## The path or name of the address. + ## + ## This is a `String` since using a `Path` does not make sense for abstract + ## and unnamed addresses. + let @address = address.to_string + } + + ## Returns the path of this address. + ## + ## If the address is unnamed or an abstract address, Nil is returned. + def to_path -> ?Path { + unnamed?.or { abstract? }.if true: { + Nil + }, false: { + @address.to_path + } + } + + ## Returns the address name or path as a `String`. + ## + ## # Examples + ## + ## Converting a `SocketAddress` to a `String`: + ## + ## import std::net::unix::SocketAddress + ## + ## SocketAddress.new('/tmp/test.sock').to_string # => '/tmp/test.sock' + ## SocketAddress.new("\0example").to_string # => "\0example" + def to_string -> String { + @address + } + + ## Returns `True` if `self` is an abstract address. + ## + ## # Examples + ## + ## Checking if an address is abstract: + ## + ## import std::net::unix::SocketAddress + ## + ## SocketAddress.new('/tmp/test.sock').abstract? # => False + ## SocketAddress.new("\0example-address").abstract? # => True + def abstract? -> Boolean { + @address.starts_with?("\0") + } + + ## Returns `True` if `self` is an unnamed address. + ## + ## # Examples + ## + ## Checking if an address is unnamed: + ## + ## import std::net::unix::SocketAddress + ## + ## SocketAddress.new('/tmp/test.sock').unnamed? # => False + ## SocketAddress.new.unnamed? # => True + def unnamed? -> Boolean { + @address.empty? + } + + ## Returns `True` if `self` and `other` are the same socket addresses. + ## + ## # Examples + ## + ## Comparing two `SocketAddress` objects: + ## + ## import std::net::unix::SocketAddress + ## + ## SocketAddress.new('a.sock') == SocketAddress.new('a.sock') # => True + ## SocketAddress.new('a.sock') == SocketAddress.new('b.sock') # => False + def ==(other: Self) -> Boolean { + @address == other.to_string + } +} + +impl Socket { + ## Creates a new Unix domain socket. + ## + ## # Examples + ## + ## Creating a new socket: + ## + ## import std::net::unix::(DGRAM, Socket) + ## + ## try! Socket.new(DGRAM) + def new(kind: Integer) !! IoError -> Socket { + try { bits.socket(domain: UNIX, kind: kind) } as Socket + } + + ## Binds this socket to the specified path or abstract address. + ## + ## # Examples + ## + ## Binding a Unix socket to a path: + ## + ## import std::net::unix::(DGRAM, Socket) + ## + ## let socket = try! Socket.new(DGRAM) + ## + ## try! socket.bind('/tmp/test.sock') + def bind(path: ToString) !! IoError -> Nil { + try bits.bind(socket: self, address: path.to_string, port: 0) + } + + ## Connects this socket to the specified address. + ## + ## # Examples + ## + ## Connecting a Unix socket: + ## + ## import std::net::unix::(STREAM, Socket) + ## + ## let listener = try! Socket.new(STREAM) + ## let stream = try! Socket.new(STREAM) + ## + ## try! listener.bind('/tmp/test.sock') + ## try! listener.listen + ## + ## try! stream.connect('/tmp/test.sock') + def connect(path: ToString) !! IoError -> Nil { + try bits.connect(socket: self, address: path.to_string, port: 0) + } + + ## Marks this socket as being ready to accept incoming connections using + ## `accept()`. + ## + ## # Examples + ## + ## Marking a socket as a listener: + ## + ## import std::net::unix::(Socket, STREAM) + ## + ## let socket = try! Socket.new(STREAM) + ## + ## try! socket.bind('/tmp/test.sock') + ## try! socket.listen + def listen(backlog = MAXIMUM_LISTEN_BACKLOG) !! IoError -> Integer { + try bits.listen(socket: self, backlog: backlog) + } + + ## Accepts a new incoming connection from this socket. + ## + ## This method will not return until a connection is available. + ## + ## # Examples + ## + ## Accepting a connection and reading data from the connection: + ## + ## import std::net::unix::(Socket, STREAM) + ## + ## let listener = try! Socket.new(STREAM) + ## let stream = try! Socket.new(STREAM) + ## + ## try! listener.bind('/tmp/test.sock') + ## try! listener.listen + ## + ## try! stream.connect('/tmp/test.sock') + ## try! stream.write_string('ping') + ## + ## let client = try! listener.accept + ## + ## try! client.read_string(4) # => 'ping' + def accept !! IoError -> Socket { + try { bits.accept(self) } as Socket + } + + ## Sends a message to the given address. + ## + ## The message sent can be a `String` or a `ByteArray`. + ## + ## The return value is the number of bytes sent. + ## + ## # Examples + ## + ## Sending a message to an address: + ## + ## import std::net::unix::(Socket, DGRAM) + ## + ## let socket = try! Socket.new(DGRAM) + ## + ## try! socket.bind('/tmp/test.sock') + ## try! socket.send_to(message: 'hello', address: '/tmp/test.sock') + def send_to(message: SocketValue, address: ToString) !! IoError -> Integer { + try bits.send_to( + socket: self, + message: message, + address: address.to_string, + port: 0 + ) + } + + ## Receives a single datagram message on the socket, returning the address the + ## message was sent from. + ## + ## The message is read into the given `ByteArray`, and up to `size` bytes will + ## be read. + ## + ## # Examples + ## + ## Sending a message to ourselves and receiving it: + ## + ## import std::byte_array::ByteArray + ## import std::net::unix::(Socket, DGRAM) + ## + ## let socket = try! Socket.new(DGRAM) + ## let bytes = ByteArray.new + ## + ## try! socket.send_to(message: 'hello', address: '/tmp/test.sock') + ## + ## let received_from = try! socket.receive_from(bytes: bytes, size: 5) + ## + ## bytes.to_string # => 'hello' + ## received_from.to_string # => '/tmp/test.sock' + def receive_from( + bytes: ByteArray, + size: Integer + ) !! IoError -> SocketAddress { + let addr = try bits.receive_from(socket: self, bytes: bytes, size: size) + + SocketAddress.new(addr[0] as String) + } + + ## Returns the local address of this socket. + def local_address !! IoError -> SocketAddress { + let addr = try bits.address(socket: self, peer: False) + + SocketAddress.new(addr[0] as String) + } + + ## Returns the peer address of this socket. + def peer_address !! IoError -> SocketAddress { + let addr = try bits.address(socket: self, peer: True) + + SocketAddress.new(addr[0] as String) + } + + ## Returns the value of the `SO_RCVBUF` option. + def receive_buffer_size !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, RECV_SIZE) + } + + ## Sets the value of the `SO_RCVBUF` option. + def receive_buffer_size=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option(self, RECV_SIZE, value) + } + + ## Returns the value of the `SO_SNDBUF` option. + def send_buffer_size !! IoError -> Integer { + try bits.get_socket_option!(Integer)(self, SEND_SIZE) + } + + ## Sets the value of the `SO_SNDBUF` option. + def send_buffer_size=(value: Integer) !! IoError -> Integer { + try bits.set_socket_option(self, SEND_SIZE, value) + } +} + +impl Read for Socket { + def read_bytes(bytes: ByteArray, size: ?Integer = Nil) !! IoError -> Integer { + try bits.read_bytes(socket: self, bytes: bytes, size: size) + } +} + +impl Write for Socket { + def write_bytes(bytes: ByteArray) !! IoError -> Integer { + try bits.write_bytes(socket: self, bytes: bytes) + } + + def write_string(string: String) !! IoError -> Integer { + try bits.write_string(socket: self, string: string) + } + + def flush -> Nil { + # Sockets can't be flushed, so this method is just a noop. + Nil + } +} + +impl Close for Socket { + def close -> Nil { + bits.close(self) + } +} + +## A Unix datagram socket. +object UnixDatagram impl Read, Write, Close { + ## Creates a new Unix datagram socket bound to the given address. + ## + ## # Examples + ## + ## Creating a new Unix datagram socket: + ## + ## import std::net::unix::UnixDatagram + ## + ## try! UnixDatagram.new('/tmp/test.sock') + def init(address: ToString) !! IoError { + let @socket = try Socket.new(DGRAM) + + try @socket.bind(address) + } + + ## Connects `self` to the remote addres.s + ## + ## Connecting a `UnixDatagram` allows sending and receiving data using the + ## methods from `std::io::Read` and `std::io::Write`, instead of having to use + ## `UnixDatagram.receive_from` and `UnixDatagram.send_to`. + ## + ## # Examples + ## + ## Connecting a Unix datagram socket: + ## + ## import std::net::unix::UnixDatagram + ## + ## let socket1 = try! UnixDatagram.new('/tmp/test1.sock') + ## let socket2 = try! UnixDatagram.new('/tmp/test2.sock') + ## + ## try! socket1.connect('/tmp/test2.sock') + def connect(address: ToString) !! IoError -> Nil { + try @socket.connect(address) + } + + ## Sends a message to the given address. + ## + ## See the documentation of `Socket.send_to` for more information. + ## + ## # Examples + ## + ## Sending a message to a specific address: + ## + ## import std::net::unix::UnixDatagram + ## + ## let socket = try! UnixDatagram.new('/tmp/test.sock') + ## + ## try! socket.send_to(message: 'hello', address: '/tmp/test.sock') + def send_to(message: SocketValue, address: ToString) !! IoError -> Integer { + try @socket.send_to(message: message, address: address) + } + + ## Receives a single datagram message on the socket, returning the address the + ## message was sent from. + ## + ## See the documentation of `Socket.receive_from` for more information. + def receive_from( + bytes: ByteArray, + size: Integer + ) !! IoError -> SocketAddress { + try @socket.receive_from(bytes: bytes, size: size) + } + + ## Returns the local address of this socket. + ## + ## See the documentation of `Socket.local_address` for more information. + def local_address !! IoError -> SocketAddress { + try @socket.local_address + } + + ## Returns the underlying `Socket` object. + ## + ## This method can be used to set additional low-level socket options, without + ## `UnixDatagram` having to re-define all these methods. + def socket -> Socket { + @socket + } + + def read_bytes(bytes: ByteArray, size: ?Integer = Nil) !! IoError -> Integer { + try @socket.read_bytes(bytes: bytes, size: size) + } + + def write_bytes(bytes: ByteArray) !! IoError -> Integer { + try @socket.write_bytes(bytes) + } + + def write_string(string: String) !! IoError -> Integer { + try @socket.write_string(string) + } + + def flush -> Nil { + @socket.flush + } + + def close -> Nil { + @socket.close + } +} + +## A Unix stream socket connected to another Unix socket. +object UnixStream impl Read, Write, Close { + ## Creates a new `UnixStream` that is connected to the given address. + ## + ## # Examples + ## + ## Connecting a `UnixStream`: + ## + ## import std::net::unix::(UnixListener, UnixStream) + ## + ## let listener = try! UnixListener.new('/tmp/test.sock') + ## + ## try! UnixStream.new('/tmp/test.sock') + def init(address: ToString) !! IoError { + let @socket = try Socket.new(STREAM) + + try @socket.connect(address) + } + + ## Returns the local address of this socket. + ## + ## See the documentation of `Socket.local_address` for more information. + def local_address !! IoError -> SocketAddress { + try @socket.local_address + } + + ## Returns the peer address of this socket. + ## + ## See the documentation of `Socket.peer_address` for more information. + def peer_address !! IoError -> SocketAddress { + try @socket.peer_address + } + + ## Returns the underlying `Socket` object. + ## + ## This method can be used to set additional low-level socket options, without + ## `UnixStream` having to re-define all these methods. + def socket -> Socket { + @socket + } + + def read_bytes(bytes: ByteArray, size: ?Integer = Nil) !! IoError -> Integer { + try @socket.read_bytes(bytes: bytes, size: size) + } + + def write_bytes(bytes: ByteArray) !! IoError -> Integer { + try @socket.write_bytes(bytes) + } + + def write_string(string: String) !! IoError -> Integer { + try @socket.write_string(string) + } + + def flush -> Nil { + @socket.flush + } + + def close -> Nil { + @socket.close + } +} + +## A Unix socket server that can accept incoming connections. +object UnixListener impl Close { + ## Creates a new `UnixListener` bound to and listening on the given address. + ## + ## The `backlog` argument can be used to set the listen backlog. + ## + ## # Examples + ## + ## Creating a `UnixListener`: + ## + ## import std::net::unix::UnixListener + ## + ## try! UnixListener.new('/tmp/test.sock') + ## + ## Creating a `UnixListener` with a custom `backlog`: + ## + ## import std::net::unix::UnixListener + ## + ## try! TcpListener.new('/tmp/test.sock') + def init(address: ToString, backlog = MAXIMUM_LISTEN_BACKLOG) !! IoError { + let @socket = try Socket.new(STREAM) + + try @socket.bind(address) + try @socket.listen(backlog) + } + + ## Accepts a new incoming connection from `self`. + ## + ## This method does not return until a connection is available. + ## + ## # Examples + ## + ## Accepting a new connection: + ## + ## import std::net::unix::(UnixListener, UnixStream) + ## + ## let listener = try! UnixListener.new('/tmp/test.sock') + ## let client = try! UnixStream.new('/tmp/test.sock') + ## + ## client.write_string('ping') + ## + ## let connection = try! listener.accept + ## + ## try! connection.read_string(4) # => 'ping' + def accept !! IoError -> UnixStream { + bits.to_stream( + socket: try @socket.accept, + prototype: UnixStream + ) as UnixStream + } + + ## Returns the local address of this socket. + ## + ## See the documentation of `Socket.local_address` for more information. + def local_address !! IoError -> SocketAddress { + try @socket.local_address + } + + ## Returns the underlying `Socket` object. + ## + ## This method can be used to set additional low-level socket options, without + ## `UnixListener` having to re-define all these methods. + def socket -> Socket { + @socket + } + + def close -> Nil { + @socket.close + } +} diff --git a/runtime/src/std/os.inko b/runtime/src/std/os.inko index 3c2115012..cee05b3ad 100644 --- a/runtime/src/std/os.inko +++ b/runtime/src/std/os.inko @@ -31,11 +31,26 @@ def platform -> String { _INKOC.platform } -## Returns `True` if the program is currently running on Windows. +## Returns `True` if the program is running on Windows. def windows? -> Boolean { platform == 'windows' } +## Returns `True` if the program is running on Linux. +def linux? -> Boolean { + platform == 'linux' +} + +## Returns `True` if the program is running on a Unix system. +def unix? -> Boolean { + windows?.not +} + +## Returns `True` if the program is running on Mac OS. +def mac? -> Boolean { + platform == 'macos' +} + ## The newline separator to use on the current platform. let NEWLINE = windows?.if true: { "\r\n" diff --git a/runtime/tests/main.inko b/runtime/tests/main.inko index 3499d88ab..a766e8556 100644 --- a/runtime/tests/main.inko +++ b/runtime/tests/main.inko @@ -8,6 +8,9 @@ import test::std::fs::test_dir import test::std::fs::test_file import test::std::fs::test_path import test::std::fs::test_raw +import test::std::net::test_ip +import test::std::net::test_socket +import test::std::net::test_unix import test::std::test::test_assert import test::std::test::test_config import test::std::test::test_error::(self as test_test_error) diff --git a/runtime/tests/test/std/net/test_ip.inko b/runtime/tests/test/std/net/test_ip.inko new file mode 100644 index 000000000..2a1d96eba --- /dev/null +++ b/runtime/tests/test/std/net/test_ip.inko @@ -0,0 +1,483 @@ +import std::net::ip::(self, Ipv4Address, Ipv6Address) +import std::test +import std::test::assert + +test.group('std::net::ip.parse_ipv4') do (g) { + g.test('Parsing a valid IPv4 address') { + let parsed = try! ip.parse_ipv4('1.2.3.4'.to_byte_array) + + assert.equal(parsed, Ipv4Address.new(1, 2, 3, 4)) + } + + g.test('Parsing an IPv4 address that is too short') { + assert.panic { + try! ip.parse_ipv4('1.2'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address that is too long') { + assert.panic { + try! ip.parse_ipv4('255.255.255.255.255.255'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address containing invalid characters') { + assert.panic { + try! ip.parse_ipv4('1.f.4.a'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address that does not contain any dots') { + assert.panic { + try! ip.parse_ipv4('1234'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address containing out of bounds octets') { + assert.panic { + try! ip.parse_ipv4('300.0.0.0'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address containing a port') { + assert.panic { + try! ip.parse_ipv4('1.2.3.4:80'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address containing a CIDR mask') { + assert.panic { + try! ip.parse_ipv4('1.2.3.4/24'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address that starts with dot') { + assert.panic { + try! ip.parse_ipv4('.2.3.4'.to_byte_array) + } + } + + g.test('Parsing an IPv4 address that is just a single dot') { + assert.panic { + try! ip.parse_ipv4('.'.to_byte_array) + } + } + + g.test('Parsing an empty String') { + assert.panic { + try! ip.parse_ipv4(''.to_byte_array) + } + } +} + +test.group('std::net::ip.parse_ipv6') do (g) { + g.test('Parsing a valid IPv6 address') { + assert.equal( + try! ip.parse_ipv6('2001:db8:0:0:1:0:0:1'.to_byte_array), + Ipv6Address.new(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1) + ) + + assert.equal( + try! ip.parse_ipv6('2001:0db8:0:0:1:0:0:1'.to_byte_array), + Ipv6Address.new(0x2001, 0x0db8, 0, 0, 1, 0, 0, 1) + ) + } + + g.test('Parsing an IPv6 address with leading zero compression') { + assert.equal( + try! ip.parse_ipv6('::1'.to_byte_array), + Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1) + ) + + assert.equal( + try! ip.parse_ipv6('::1:1:1'.to_byte_array), + Ipv6Address.new(0, 0, 0, 0, 0, 1, 1, 1) + ) + } + + g.test('Parsing an IPv6 address with trailing zero compression') { + assert.equal(try! ip.parse_ipv6('1::'.to_byte_array), Ipv6Address.new(1)) + } + + g.test('Parsing an IPv6 address with zero compression') { + assert.equal( + try! ip.parse_ipv6('2001:DB8:0:0:1::1'.to_byte_array), + Ipv6Address.new(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1) + ) + + assert.equal( + try! ip.parse_ipv6('2001:DB8:0:0::1:1'.to_byte_array), + Ipv6Address.new(0x2001, 0xdb8, 0, 0, 0, 0, 1, 1) + ) + + assert.equal( + try! ip.parse_ipv6('1::1'.to_byte_array), + Ipv6Address.new(1, 0, 0, 0, 0, 0, 0, 1) + ) + } + + g.test('Parsing an IPv6 address with an embedded IPv4 address') { + assert.equal( + try! ip.parse_ipv6( + '0000:0000:0000:0000:0000:ffff:192.168.1.1'.to_byte_array + ), + Ipv6Address.new(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x101) + ) + + assert.equal( + try! ip.parse_ipv6( + '0000:0000:0000:0000:0000:c0a8:192.168.1.1'.to_byte_array + ), + Ipv6Address.new(0, 0, 0, 0, 0, 0xc0a8, 0xc0a8, 0x101) + ) + + assert.equal( + try! ip.parse_ipv6('::1:192.168.1.1'.to_byte_array), + Ipv6Address.new(0, 0, 0, 0, 0, 1, 0xc0a8, 0x101) + ) + } + + g.test('Parsing an embedded IPv4 address with leading compression') { + assert.equal( + try! ip.parse_ipv6('::1.2.3.4'.to_byte_array), + Ipv6Address.new(0, 0, 0, 0, 0, 0, 258, 772) + ) + } + + g.test('Parsing an IPv6 address that is too long') { + assert.panic { + try! ip.parse_ipv6( + '0000:0000:0000:0000:0000:0000:0000:0000:0000'.to_byte_array + ) + } + } + + g.test('Parsing an IPv6 address that is too short') { + assert.panic { + try! ip.parse_ipv6('0000'.to_byte_array) + } + } + + g.test('Parsing an IPv6 address that compresses zeroes more than once') { + assert.panic { + try! ip.parse_ipv6('::1::1'.to_byte_array) + } + } + + g.test('Parsing an IPv6 address that contains too many colons') { + assert.panic { + try! ip.parse_ipv6('1:::1'.to_byte_array) + } + } + + g.test('Parsing an IPv6 address containing invalid hextets') { + assert.panic { + try! ip.parse_ipv6('0000:0000:0000:0000:0000:0000:zzzz'.to_byte_array) + } + } + + g.test('Parsing an IPv6 address embedding an invalid IPv4 address') { + assert.panic { + try! ip.parse_ipv6('::1:300.168.1.1'.to_byte_array) + } + + assert.panic { + try! ip.parse_ipv6('::1:300.168:1.1'.to_byte_array) + } + } + + g.test('Parsing an IPv6 address containing a CIDR mask') { + assert.panic { + try! ip.parse_ipv6('::1/24'.to_byte_array) + } + } + + g.test('Parsing an empty IPv6 address') { + assert.equal(try! ip.parse_ipv6('::'.to_byte_array), Ipv6Address.new) + } +} + +test.group('std::net::ip.parse') do (g) { + g.test('Parsing an IPv4 address') { + assert.equal( + try! ip.parse('1.2.3.4') as Ipv4Address, + Ipv4Address.new(1, 2, 3, 4) + ) + } + + g.test('Parsing an IPv6 address') { + assert.equal( + try! ip.parse('::1') as Ipv6Address, + Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1) + ) + } + + g.test('Parsing an invalid IPv4 address') { + assert.panic { + try! ip.parse('1.2') + } + } + + g.test('Parsing an invalid IP address') { + assert.panic { + try! ip.parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff') + } + + assert.panic { + try! ip.parse('foo') + } + } +} + +test.group('std::net::ip::Ipv4Address.broadcast?') do (g) { + g.test('Checking if an IPv4 address is a broadcast address') { + assert.true(Ipv4Address.new(255, 255, 255, 255).broadcast?) + assert.false(Ipv4Address.new(1, 255, 255, 255).broadcast?) + assert.false(Ipv4Address.new(255, 1, 255, 255).broadcast?) + assert.false(Ipv4Address.new(255, 255, 1, 255).broadcast?) + assert.false(Ipv4Address.new(255, 255, 255, 1).broadcast?) + } +} + +test.group('std::net::ip::Ipv4Address.documentation?') do (g) { + g.test('Checking if an IPv4 address is a documentation address') { + assert.true(Ipv4Address.new(192, 0, 2, 0).documentation?) + assert.true(Ipv4Address.new(192, 0, 2, 1).documentation?) + assert.false(Ipv4Address.new(192, 1, 2, 1).documentation?) + + assert.true(Ipv4Address.new(198, 51, 100, 0).documentation?) + assert.true(Ipv4Address.new(198, 51, 100, 1).documentation?) + assert.false(Ipv4Address.new(198, 52, 100, 1).documentation?) + + assert.true(Ipv4Address.new(203, 0, 113, 0).documentation?) + assert.true(Ipv4Address.new(203, 0, 113, 1).documentation?) + assert.false(Ipv4Address.new(203, 1, 113, 1).documentation?) + } +} + +test.group('std::net::ip::Ipv4Address.link_local?') do (g) { + g.test('Checking if an IPv4 address is link local') { + assert.true(Ipv4Address.new(169, 254).link_local?) + assert.true(Ipv4Address.new(169, 254, 1).link_local?) + assert.true(Ipv4Address.new(169, 254, 1, 1).link_local?) + + assert.false(Ipv4Address.new(169, 1, 1, 1).link_local?) + assert.false(Ipv4Address.new(1, 254, 1, 1).link_local?) + } +} + +test.group('std::net::ip::Ipv4Address.loopback?') do (g) { + g.test('Checking if an IPv4 address is a loopback address') { + assert.true(Ipv4Address.new(127, 0, 0, 1).loopback?) + assert.true(Ipv4Address.new(127, 0, 0, 2).loopback?) + assert.true(Ipv4Address.new(127, 1, 1, 1).loopback?) + assert.false(Ipv4Address.new(128, 0, 0, 0).loopback?) + } +} + +test.group('std::net::ip::Ipv4Address.multicast?') do (g) { + g.test('Checking if an IPv4 address is a multicast address') { + assert.true(Ipv4Address.new(224).multicast?) + assert.true(Ipv4Address.new(225).multicast?) + assert.true(Ipv4Address.new(226).multicast?) + assert.true(Ipv4Address.new(227).multicast?) + assert.true(Ipv4Address.new(239).multicast?) + + assert.false(Ipv4Address.new(200).multicast?) + assert.false(Ipv4Address.new(240).multicast?) + } +} + +test.group('std::net::ip::Ipv4Address.private?') do (g) { + g.test('Checking if an IPv4 address is a private address') { + assert.true(Ipv4Address.new(10, 0, 0, 0).private?) + assert.true(Ipv4Address.new(10, 0, 0, 1).private?) + assert.true(Ipv4Address.new(10, 1, 1, 1).private?) + + assert.true(Ipv4Address.new(172, 16, 0, 0).private?) + assert.true(Ipv4Address.new(172, 16, 0, 1).private?) + assert.true(Ipv4Address.new(172, 16, 1, 0).private?) + + assert.true(Ipv4Address.new(192, 168, 0, 0).private?) + assert.true(Ipv4Address.new(192, 168, 0, 1).private?) + assert.true(Ipv4Address.new(192, 168, 1, 0).private?) + + assert.false(Ipv4Address.new(11, 0, 0, 0).private?) + assert.false(Ipv4Address.new(192, 1, 1, 1).private?) + assert.false(Ipv4Address.new(172, 15, 0, 0).private?) + } +} + +test.group('std::net::ip::Ipv4Address.unspecified?') do (g) { + g.test('Checking if an IPv4 address is unspecified') { + assert.true(Ipv4Address.new(0, 0, 0, 0).unspecified?) + assert.false(Ipv4Address.new(0, 0, 0, 1).unspecified?) + } +} + +test.group('std::net::ip::Ipv4Address.to_ipv6_compatible') do (g) { + g.test('Converting an IPv4 address to an IPv4-compatible IPv6 address') { + let ipv4 = Ipv4Address.new(192, 0, 2, 255) + let ipv6 = ipv4.to_ipv6_compatible + + assert.equal(ipv6, Ipv6Address.new(0, 0, 0, 0, 0, 0, 0xc000, 0x2ff)) + } +} + +test.group('std::net::ip::Ipv4Address.to_ipv6_mapped') do (g) { + g.test('Converting an IPv4 address to an IPv4-mapped IPv6 address') { + let ipv4 = Ipv4Address.new(192, 0, 2, 255) + let ipv6 = ipv4.to_ipv6_mapped + + assert.equal(ipv6, Ipv6Address.new(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x2ff)) + } +} + +test.group('std::net::ip::Ipv4Address.to_string') do (g) { + g.test('Converting an IPv4 address to a String') { + assert.equal(Ipv4Address.new(0, 0, 0, 0).to_string, '0.0.0.0') + assert.equal(Ipv4Address.new(127, 0, 0, 1).to_string, '127.0.0.1') + } +} + +test.group('std::net::ip::Ipv4Address.==') do (g) { + g.test('Comparing two IPv4 addresses') { + assert.equal(Ipv4Address.new(127, 0, 0, 1), Ipv4Address.new(127, 0, 0, 1)) + + assert.not_equal( + Ipv4Address.new(127, 0, 0, 1), + Ipv4Address.new(127, 0, 0, 2) + ) + } +} + +test.group('std::net::ip::Ipv6Address.documentation?') do (g) { + g.test('Checking if an IPv6 address is a documentation address') { + assert.true(Ipv6Address.new(0x2001, 0xdb8).documentation?) + assert.true(Ipv6Address.new(0x2001, 0xdb8, 1).documentation?) + assert.true(Ipv6Address.new(0x2001, 0xdb8, 1, 2).documentation?) + + assert.false(Ipv6Address.new(0x2001, 0xffff).documentation?) + assert.false(Ipv6Address.new(0xffff, 0xdb8, 1, 2).documentation?) + } +} + +test.group('std::net::ip::Ipv6Address.loopback?') do (g) { + g.test('Checking if an IPv6 address is a loopback address') { + assert.true(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1).loopback?) + assert.false(Ipv6Address.new(0, 0, 0, 0, 0, 0, 1, 1).loopback?) + } +} + +test.group('std::net::ip::Ipv6Address.multicast?') do (g) { + g.test('Checking if an IPv6 address is a multicast address') { + assert.true(Ipv6Address.new(0xff00).multicast?) + assert.true(Ipv6Address.new(0xff01).multicast?) + assert.true(Ipv6Address.new(0xff02).multicast?) + assert.true(Ipv6Address.new(0xff03).multicast?) + assert.true(Ipv6Address.new(0xff04).multicast?) + + assert.false(Ipv6Address.new(0x0f00).multicast?) + assert.false(Ipv6Address.new(1).multicast?) + } +} + +test.group('std::net::ip::Ipv6Address.unspecified?') do (g) { + g.test('Checking if an IPv6 address is unspecified') { + assert.true(Ipv6Address.new(0, 0, 0, 0, 0, 0 ,0 ,0).unspecified?) + assert.false(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1).unspecified?) + } +} + +test.group('std::net::ip::Ipv6Address.ipv4_compatible?') do (g) { + g.test('Checking if an IPv6 address is an IPv4-compatible address') { + assert.true(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 0).ipv4_compatible?) + assert.true(Ipv6Address.new(0, 0, 0, 0, 0, 0, 1, 1).ipv4_compatible?) + assert.false(Ipv6Address.new(0, 0, 0, 0, 0, 1, 1, 1).ipv4_compatible?) + } +} + +test.group('std::net::ip::Ipv6Address.ipv4_mapped?') do (g) { + g.test('Checking if an IPv6 address is an IPv4-mapped address') { + assert.true(Ipv6Address.new(0, 0, 0, 0, 0, 0xffff, 0, 0).ipv4_mapped?) + assert.true(Ipv6Address.new(0, 0, 0, 0, 0, 0xffff, 1, 1).ipv4_mapped?) + assert.false(Ipv6Address.new(0, 0, 0, 0, 0, 0xff, 1, 1).ipv4_mapped?) + } +} + +test.group('std::net::ip::Ipv6Address.to_string') do (g) { + g.test('Converting an IPv6 unspecified address to a String') { + assert.equal(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 0).to_string, '::') + } + + g.test('Converting an IPv6 loopback address to a String') { + assert.equal(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1).to_string, '::1') + } + + g.test('Converting an IPv6 address to a String') { + assert.equal(Ipv6Address.new(1, 0, 0, 0, 0, 0, 0, 1).to_string, '1::1') + assert.equal(Ipv6Address.new(1, 0, 1, 0, 0, 0, 0, 1).to_string, '1:0:1::1') + + assert.equal( + Ipv6Address.new(1, 0, 0, 0, 0, 0, 0xc000, 0x2ff).to_string, + '1::c000:2ff' + ) + + assert.equal( + Ipv6Address.new(1, 0, 1, 0, 0, 0, 0xc000, 0x2ff).to_string, + '1:0:1::c000:2ff' + ) + + assert.equal( + Ipv6Address.new(1, 0, 0, 0, 0, 0xffff, 0xc000, 0x2ff).to_string, + '1::ffff:c000:2ff' + ) + + assert.equal( + Ipv6Address.new(1, 0, 1, 0, 0, 0xffff, 0xc000, 0x2ff).to_string, + '1:0:1::ffff:c000:2ff' + ) + } + + g.test('Converting an IPv4-compatible IPv6 address to a String') { + assert.equal( + Ipv6Address.new(0, 0, 0, 0, 0, 0, 0xc000, 0x2ff).to_string, + '::192.0.2.255' + ) + } + + g.test('Converting an IPv4-mapped IPv6 address to a String') { + assert.equal( + Ipv6Address.new(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x2ff).to_string, + '::ffff:192.0.2.255' + ) + } +} + +test.group('std::net::ip::Ipv6Address.==') do (g) { + g.test('Comparing two IPv6 addresses') { + assert.equal( + Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1), + Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1) + ) + + assert.not_equal( + Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1), + Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 2) + ) + } +} + +test.group('std::string::String.to_ip_address') do (g) { + g.test('Converting a valid String to an IP address') { + let addr = try! '0.0.0.0'.to_ip_address + + assert.equal(addr as Ipv4Address, Ipv4Address.new(0, 0, 0, 0)) + } + + g.test('Converting an invalid String to an IP address') { + assert.panic { + try! 'foo'.to_ip_address + } + } +} diff --git a/runtime/tests/test/std/net/test_socket.inko b/runtime/tests/test/std/net/test_socket.inko new file mode 100644 index 000000000..15d6c0c8b --- /dev/null +++ b/runtime/tests/test/std/net/test_socket.inko @@ -0,0 +1,833 @@ +import std::byte_array::ByteArray +import std::net::ip::Ipv4Address +import std::net::bits::MAXIMUM_LISTEN_BACKLOG +import std::net::socket::( + DGRAM, IPV4, IPV6, STREAM, Socket, SocketAddress, TcpListener, TcpStream, + UdpSocket +) +import std::os +import std::process +import std::test +import std::test::assert + +test.group('std::net::socket::SocketAddress.new') do (g) { + g.test('Creating a SocketAddress') { + let ip = Ipv4Address.new(127, 0, 0, 1) + let addr = SocketAddress.new(ip: ip, port: 1234) + + assert.equal(addr.ip as Ipv4Address, ip) + assert.equal(addr.port, 1234) + } +} + +test.group('std::net::socket::SocketAddress.==') do (g) { + g.test('Comparing two identical SocketAddress objects') { + let ip = Ipv4Address.new(127, 0, 0, 1) + let addr1 = SocketAddress.new(ip: ip, port: 1234) + let addr2 = SocketAddress.new(ip: ip, port: 1234) + + assert.equal(addr1, addr2) + } + + g.test('Comparing two different SocketAddress objects') { + let ip = Ipv4Address.new(127, 0, 0, 1) + let addr1 = SocketAddress.new(ip: ip, port: 1234) + let addr2 = SocketAddress.new(ip: ip, port: 12345) + + assert.not_equal(addr1, addr2) + } +} + +test.group('std::net::socket::Socket.new') do (g) { + g.test('Creating an IPv4 stream socket') { + assert.no_panic { + try! Socket.new(domain: IPV4, kind: STREAM) + } + } + + g.test('Creating an IPv4 datagram socket') { + assert.no_panic { + try! Socket.new(domain: IPV4, kind: DGRAM) + } + } + + g.test('Creating an IPv6 stream socket') { + assert.no_panic { + try! Socket.new(domain: IPV6, kind: STREAM) + } + } + + g.test('Creating an IPv6 datagram socket') { + assert.no_panic { + try! Socket.new(domain: IPV6, kind: DGRAM) + } + } + + g.test('Creating an invalid socket') { + assert.panic { + try! Socket.new(domain: 9999, kind: STREAM) + } + } +} + +test.group('std::net::socket::Socket.bind') do (g) { + g.test('Binding a socket') { + assert.no_panic { + let sock = try! Socket.new(domain: IPV4, kind: STREAM) + + try! sock.bind(ip: '0.0.0.0', port: 0) + } + } + + g.test('Binding a socket to an invalid address') { + assert.panic { + let sock = try! Socket.new(domain: IPV4, kind: STREAM) + + try! sock.bind(ip: '0.0.0.0', port: -1) + } + } +} + +test.group('std::net::socket::Socket.connect') do (g) { + g.test('Connecting a socket') { + assert.no_panic { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + let stream = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '0.0.0.0', port: 0) + try! listener.listen + + let addr = try! listener.local_address + + try! stream.connect(ip: addr.ip, port: addr.port) + } + } + + g.test('Connecting a socket to a non-existing address') { + assert.panic { + let stream = try! Socket.new(domain: IPV4, kind: STREAM) + + # connect() may not immediately raise a "connection refused" error, due to + # connect() being non-blocking. In this case the "connection refused" + # error is raised on the next operation. + # + # Since a connect() _might_ still raise the error right away, we have to + # both connect and try to use the socket in some way. + try! stream.connect(ip: '0.0.0.0', port: 40_000) + try! stream.write_string('ping') + } + } +} + +test.group('std::net::socket::Socket.listen') do (g) { + g.test('Marking a Socket as listening with a custom backlog') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.bind(ip: '0.0.0.0', port: 0) + + assert.equal(try! socket.listen(4), 4) + } + + g.test('Marking a Socket as listening using the default backlog') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.bind(ip: '0.0.0.0', port: 0) + + assert.equal(try! socket.listen, MAXIMUM_LISTEN_BACKLOG) + } +} + +test.group('std::net::socket::Socket.accept') do (g) { + g.test('Accepting a connection from an unbound socket') { + assert.panic { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.accept + } + } + + g.test('Accepting a connection from a bound socket') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + let child_proc = process.spawn { + let address = process.receive as SocketAddress + let stream = try! Socket.new(domain: IPV4, kind: STREAM) + + try! stream.connect(ip: address.ip, port: address.port) + } + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + child_proc.send(try! listener.local_address) + + let connection = try! listener.accept + + assert.equal(try! connection.local_address, try! listener.local_address) + } +} + +test.group('std::net::socket::Socket.send_to') do (g) { + g.test('Sending a message to a specific address') { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + # On Windows one can not use sendto() with 0.0.0.0 being the target IP + # address, so instead we bind (and send to) 127.0.0.1. + try! socket.bind(ip: '127.0.0.1', port: 0) + + let send_to = try! socket.local_address + + try! socket.send_to(message: 'ping', ip: send_to.ip, port: send_to.port) + + assert.equal(try! socket.read_string(size: 4), 'ping') + } +} + +test.group('std::net::socket::Socket.receive_from') do (g) { + g.test('Receiving a message and a SocketAddress') { + let listener = try! Socket.new(domain: IPV4, kind: DGRAM) + let client = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! client.bind(ip: '127.0.0.1', port: 0) + + let send_to = try! listener.local_address + + try! client.send_to(message: 'ping', ip: send_to.ip, port: send_to.port) + + let bytes = ByteArray.new + let sender = try! listener.receive_from(bytes: bytes, size: 4) + + assert.equal(sender, try! client.local_address) + assert.equal(bytes.to_string, 'ping') + } +} + +test.group('std::net::socket::Socket.local_address') do (g) { + g.test('Obtaining the local address of an unbound socket') { + os.windows?.if true: { + assert.panic { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! socket.local_address + } + }, false: { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + let address = try! socket.local_address + + assert.equal( + address, + SocketAddress.new(ip: Ipv4Address.new(0, 0, 0, 0), port: 0) + ) + } + } + + g.test('Obtaining the local address of a bound socket') { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! socket.bind(ip: '127.0.0.1', port: 0) + + let local_address = try! socket.local_address + + assert.equal(local_address.ip.to_string, '127.0.0.1') + assert.true(local_address.port.positive?) + } +} + +test.group('std::net::socket::Socket.peer_address') do (g) { + g.test('Obtaining the peer address of a disconnected socket') { + assert.panic { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! socket.peer_address + } + } + + g.test('Obtaining the peer address of a connected socket') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + let client = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let listener_addr = try! listener.local_address + + try! client.connect(ip: listener_addr.ip, port: listener_addr.port) + + assert.equal(try! client.peer_address, listener_addr) + } +} + +test.group('std::net::socket::Socket.ttl') do (g) { + g.test('Setting and obtaining the value of the IP_TTL option') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.ttl = 10 + + assert.equal(try! socket.ttl, 10) + } +} + +test.group('std::net::socket::Socket.only_ipv6?') do (g) { + g.test('Setting and obtainin the IPV6_V6ONLY option') { + let socket = try! Socket.new(domain: IPV6, kind: STREAM) + + try! socket.only_ipv6 = True + + assert.true(try! socket.only_ipv6?) + } +} + +test.group('std::net::socket::Socket.no_delay?') do (g) { + g.test('Setting and obtaining the value of the TCP_NODELAY option') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.no_delay = True + + assert.true(try! socket.no_delay?) + } +} + +test.group('std::net::socket::Socket.broadcast?') do (g) { + g.test('Setting and obtaining the value of the SO_BROADCAST option') { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! socket.broadcast = True + + assert.true(try! socket.broadcast?) + } +} + +test.group('std::net::socket::Socket.linger') do (g) { + g.test('Setting and obtaining the value of the SO_LINGER option') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.linger = 5 + + assert.equal(try! socket.linger.as_seconds.to_integer, 5) + } +} + +test.group('std::net::socket::Socket.receive_buffer_size') do (g) { + g.test('Setting and obtaining the value of the SO_RCVBUF option') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.receive_buffer_size = 256 + + assert.true(try! socket.receive_buffer_size >= 256) + } +} + +test.group('std::net::socket::Socket.send_buffer_size') do (g) { + g.test('Setting and obtaining the value of the SO_SNDBUT option') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.send_buffer_size = 256 + + assert.true(try! socket.send_buffer_size >= 256) + } +} + +# Obtaining the TCP keepalive setting fails on Windows. See +# https://github.com/alexcrichton/socket2-rs/issues/24 for more information. +os.windows?.if_false { + test.group('std::net::socket::Socket.keepalive') do (g) { + g.test('Setting and obtaining the TCP keepalive timeout') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + try! socket.keepalive = 5 + + assert.equal(try! socket.keepalive.as_seconds.to_integer, 5) + } + } +} + +test.group('std::net::socket::Socket.ipv4_multicast_loop?') do (g) { + g.test('Setting and obtaining the value of the IP_MULTICAST_LOOP option') { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! socket.ipv4_multicast_loop = True + + assert.true(try! socket.ipv4_multicast_loop?) + } +} + +test.group('std::net::socket::Socket.ipv6_multicast_loop?') do (g) { + g.test('Setting and obtaining the value of the IPV6_MULTICAST_LOOP option') { + let socket = try! Socket.new(domain: IPV6, kind: DGRAM) + + try! socket.ipv6_multicast_loop = True + + assert.true(try! socket.ipv6_multicast_loop?) + } +} + +test.group('std::net::socket::Socket.ipv4_multicast_ttl?') do (g) { + g.test('Setting and obtaining the value of the IP_MULTICAST_TTL option') { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! socket.ipv4_multicast_ttl = 32 + + assert.equal(try! socket.ipv4_multicast_ttl, 32) + } +} + +test.group('std::net::socket::Socket.ipv6_multicast_hops') do (g) { + g.test('Setting and obtaining the value of the IPV6_MULTICAST_HOPS option') { + let socket = try! Socket.new(domain: IPV6, kind: DGRAM) + + try! socket.ipv6_multicast_hops = 4 + + assert.equal(try! socket.ipv6_multicast_hops, 4) + } +} + +test.group('std::net::socket::Socket.ipv4_multicast_interface') do (g) { + g.test('Setting and obtaining the IP_MULTICAST_IF option') { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + + try! socket.ipv4_multicast_interface = '127.0.0.1' + + assert.equal(try! socket.ipv4_multicast_interface.to_string, '127.0.0.1') + } +} + +test.group('std::net::socket::Socket.ipv6_multicast_interface') do (g) { + g.test('Setting and obtaining the IPV6_MULTICAST_IF option') { + let socket = try! Socket.new(domain: IPV6, kind: DGRAM) + let mut interface = 0 + let mut found = False + + # The actual interface might vary per environment, but there is no + # cross-platform way of figuring out which interface is valid. To work + # around this we just try the first 10 interfaces, and error if none could + # be found. + { + found.not.and { interface < 10 } + }.while_true { + found = True + + try { + socket.ipv6_multicast_interface = interface + } else (error) { + found = False + interface += 1 + } + } + + assert.true(found) + assert.equal(try! socket.ipv6_multicast_interface, interface) + } +} + +test.group('std::net::socket::Socket.ipv6_unicast_hops') do (g) { + g.test('Setting and obtaining the value of the IPV6_UNICAST_HOPS option') { + let socket = try! Socket.new(domain: IPV6, kind: DGRAM) + + try! socket.ipv6_unicast_hops = 4 + + assert.equal(try! socket.ipv6_unicast_hops, 4) + } +} + +test.group('std::net::socket::Socket.reuse_adress') do (g) { + g.test('Setting and obtaining the value of the SO_REUSEADDR option') { + let socket = try! Socket.new(domain: IPV6, kind: DGRAM) + + try! socket.reuse_address = True + + assert.true(try! socket.reuse_address) + } +} + +test.group('std::net::socket::Socket.reuse_port') do (g) { + g.test('Setting and obtaining the value of the SO_REUSEPORT option') { + let socket = try! Socket.new(domain: IPV6, kind: DGRAM) + + try! socket.reuse_port = True + + os.windows?.if true: { + # Windows does not support SO_REUSEPORT, so the return value is always + # `False`. + assert.false(try! socket.reuse_port) + }, false: { + assert.true(try! socket.reuse_port) + } + } +} + +test.group('std::net::socket::Socket.read_bytes') do (g) { + g.test('Reading bytes from a Socket') { + let socket = try! Socket.new(domain: IPV4, kind: DGRAM) + let bytes = ByteArray.new + + try! socket.bind(ip: '127.0.0.1', port: 0) + + let local_addr = try! socket.local_address + + try! socket + .send_to(message: 'ping', ip: local_addr.ip, port: local_addr.port) + + let read = try! socket.read_bytes(bytes: bytes, size: 4) + + assert.equal(bytes.to_string, 'ping') + assert.equal(read, 4) + } +} + +test.group('std::net::socket::Socket.write_bytes') do (g) { + g.test('Writing bytes to a Socket') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + let stream = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let local_addr = try! listener.local_address + + try! stream.connect(ip: local_addr.ip, port: local_addr.port) + + let written = try! stream.write_bytes('ping'.to_byte_array) + let connection = try! listener.accept + let message = try! connection.read_string(size: 4) + + assert.equal(message, 'ping') + assert.equal(written, 4) + } +} + +test.group('std::net::socket::Socket.close') do (g) { + g.test('Closing a Socket') { + assert.no_panic { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + socket.close + } + } +} + +test.group('std::net::socket::Socket.flush') do (g) { + g.test('Flushing a Socket') { + let socket = try! Socket.new(domain: IPV4, kind: STREAM) + + assert.equal(socket.flush, Nil) + } +} + +test.group('std::net::socket::UdpSocket.new') do (g) { + g.test('Creating a UdpSocket') { + assert.no_panic { + try! UdpSocket.new(ip: '0.0.0.0', port: 0) + } + } + + g.test('Creating a UdpSocket using an invalid IP address') { + assert.panic { + try! UdpSocket.new(ip: 'foo', port: 0) + } + } +} + +test.group('std::net::socket::UdpSocket.connect') do (g) { + g.test('Connecting a UdpSocket') { + assert.no_panic { + let socket1 = try! UdpSocket.new(ip: '127.0.0.1', port: 40_000) + let socket2 = try! UdpSocket.new(ip: '127.0.0.1', port: 41_000) + + try! socket1.connect(ip: '127.0.0.1', port: 41_000) + } + } + + g.test('Reading and writing from a connected UdpSocket') { + let socket1 = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let socket2 = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let address = try! socket2.local_address + + try! socket1.connect(ip: '127.0.0.1', port: address.port) + try! socket1.write_string('ping') + + assert.equal(try! socket2.read_string(4), 'ping') + } +} + +test.group('std::net::socket::UdpSocket.send_to') do (g) { + g.test('Sending a message to a specific address') { + let socket = try! UdpSocket.new('127.0.0.1', port: 0) + let send_to = try! socket.local_address + + try! socket.send_to(message: 'ping', ip: send_to.ip, port: send_to.port) + + assert.equal(try! socket.read_string(size: 4), 'ping') + } +} + +test.group('std::net::socket::UdpSocket.receive_from') do (g) { + g.test('Receiving a message and a SocketAddress') { + let listener = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let client = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let send_to = try! listener.local_address + + try! client.send_to(message: 'ping', ip: send_to.ip, port: send_to.port) + + let bytes = ByteArray.new + let sender = try! listener.receive_from(bytes: bytes, size: 4) + + assert.equal(sender, try! client.local_address) + assert.equal(bytes.to_string, 'ping') + } +} + +test.group('std::net::socket::UdpSocket.local_address') do (g) { + g.test('Obtaining the local address of a UdpSocket') { + let socket = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let local_address = try! socket.local_address + + assert.equal(local_address.ip.to_string, '127.0.0.1') + assert.true(local_address.port.positive?) + } +} + +test.group('std::net::socket::UdpSocket.read_bytes') do (g) { + g.test('Reading bytes from a UdpSocket') { + let socket = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let bytes = ByteArray.new + let local_addr = try! socket.local_address + + try! socket + .send_to(message: 'ping', ip: local_addr.ip, port: local_addr.port) + + let read = try! socket.read_bytes(bytes: bytes, size: 4) + + assert.equal(bytes.to_string, 'ping') + assert.equal(read, 4) + } +} + +test.group('std::net::socket::UdpSocket.write_bytes') do (g) { + g.test('Writing bytes to a connected UdpSocket') { + let server_socket = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let client_socket = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + let local_addr = try! server_socket.local_address + + try! client_socket.connect(ip: local_addr.ip, port: local_addr.port) + + let written = try! client_socket.write_bytes('ping'.to_byte_array) + let message = try! server_socket.read_string(size: 4) + + assert.equal(message, 'ping') + assert.equal(written, 4) + } + + g.test('Writing bytes to a disconnected UdpSocket') { + assert.panic { + let socket = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + + try! socket.write_bytes('ping'.to_byte_array) + } + } +} + +test.group('std::net::socket::UdpSocket.close') do (g) { + g.test('Closing a UdpSocket') { + assert.no_panic { + let socket = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + + socket.close + } + } +} + +test.group('std::net::socket::UdpSocket.flush') do (g) { + g.test('Flushing a UdpSocket') { + let socket = try! UdpSocket.new(ip: '127.0.0.1', port: 0) + + assert.equal(socket.flush, Nil) + } +} + +test.group('std::net::socket::TcpStream.new') do (g) { + g.test('Creating a new TcpStream') { + assert.no_panic { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '0.0.0.0', port: 0) + try! listener.listen + + let listener_addr = try! listener.local_address + + try! TcpStream.new(ip: listener_addr.ip, port: listener_addr.port) + } + } +} + +test.group('std::net::socket::TcpStream.local_address') do (g) { + g.test('Obtaining the local address') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let listener_addr = try! listener.local_address + let stream = + try! TcpStream.new(ip: listener_addr.ip, port: listener_addr.port) + + let local_addr = try! stream.local_address + + assert.equal(local_addr.ip.to_string, '127.0.0.1') + assert.true(local_addr.port.positive?) + } +} + +test.group('std::net::socket::TcpStream.peer_address') do (g) { + g.test('Obtaining the peer address') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let listener_addr = try! listener.local_address + let stream = + try! TcpStream.new(ip: listener_addr.ip, port: listener_addr.port) + + let peer_addr = try! stream.peer_address + + assert.equal(peer_addr.ip as Ipv4Address, listener_addr.ip as Ipv4Address) + assert.equal(peer_addr.port, listener_addr.port) + } +} + +test.group('std::net::socket::TcpStream.read_bytes') do (g) { + g.test('Reading bytes from a TcpStream') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let addr = try! listener.local_address + let stream = try! TcpStream.new(ip: addr.ip, port: addr.port) + let bytes = ByteArray.new + + try! listener.accept.write_string('ping') + + try! stream.read_bytes(bytes: bytes, size: 4) + + assert.equal(bytes.to_string, 'ping') + } +} + + +test.group('std::net::socket::TcpStream.write_bytes') do (g) { + g.test('Writing bytes to a TcpStream') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let addr = try! listener.local_address + let stream = try! TcpStream.new(ip: addr.ip, port: addr.port) + let connection = try! listener.accept + + try! stream.write_bytes('ping'.to_byte_array) + + assert.equal(try! connection.read_string(4), 'ping') + } +} + +test.group('std::net::socket::TcpStream.write_string') do (g) { + g.test('Writing a String to a TcpStream') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let addr = try! listener.local_address + let stream = try! TcpStream.new(ip: addr.ip, port: addr.port) + let connection = try! listener.accept + + try! stream.write_string('ping') + + assert.equal(try! connection.read_string(4), 'ping') + } +} + +test.group('std::net::socket::TcpStream.close') do (g) { + g.test('Closing a TcpStream') { + assert.no_panic { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let addr = try! listener.local_address + let stream = try! TcpStream.new(ip: addr.ip, port: addr.port) + + stream.close + } + } +} + +test.group('std::net::socket::TcpStream.flush') do (g) { + g.test('Flushing a TcpStream') { + let listener = try! Socket.new(domain: IPV4, kind: STREAM) + + try! listener.bind(ip: '127.0.0.1', port: 0) + try! listener.listen + + let addr = try! listener.local_address + let stream = try! TcpStream.new(ip: addr.ip, port: addr.port) + + assert.equal(stream.flush, Nil) + } +} + +test.group('std::net::socket::TcpListener.new') do (g) { + g.test('Creating a TcpListener') { + assert.no_panic { + try! TcpListener.new(ip: '0.0.0.0', port: 0) + } + } + + g.test('Creating a TcpListener with an in-use address and port') { + assert.no_panic { + let listener1 = try! TcpListener.new(ip: '0.0.0.0', port: 0) + let address = try! listener1.local_address + + try! TcpListener.new(ip: address.ip, port: address.port) + } + } + + g.test('Creating a TcpListener that only uses IPv6 packets') { + let listener = try! TcpListener.new(ip: '::1', port: 0, only_ipv6: True) + + assert.true(try! listener.socket.only_ipv6?) + } +} + +test.group('std::net::socket::TcpListener.accept') do (g) { + g.test('Accepting an incoming connection') { + let listener = try! TcpListener.new(ip: '127.0.0.1', port: 0) + let addr = try! listener.local_address + let stream = try! TcpStream.new(ip: addr.ip, port: addr.port) + let connection = try! listener.accept + + assert.equal(try! connection.local_address, try! stream.peer_address) + } +} + +test.group('std::net::socket::TcpListener.local_address') do (g) { + g.test('Obtaining the local address') { + let listener = try! TcpListener.new(ip: '127.0.0.1', port: 0) + let addr = try! listener.local_address + + assert.equal(addr.ip.to_string, '127.0.0.1') + assert.true(addr.port.positive?) + } +} + +test.group('std::net::socket::TcpListener.close') do (g) { + g.test('Closing a TcpListener') { + let listener = try! TcpListener.new(ip: '127.0.0.1', port: 0) + + assert.equal(listener.close, Nil) + } +} diff --git a/runtime/tests/test/std/net/test_unix.inko b/runtime/tests/test/std/net/test_unix.inko new file mode 100644 index 000000000..c79316f0f --- /dev/null +++ b/runtime/tests/test/std/net/test_unix.inko @@ -0,0 +1,727 @@ +import std::byte_array::ByteArray +import std::fs::file +import std::fs::path::Path +import std::net::bits::MAXIMUM_LISTEN_BACKLOG +import std::net::unix::( + DGRAM, RAW, SEQPACKET, STREAM, Socket, SocketAddress, UnixDatagram, + UnixListener, UnixStream +) +import std::os +import std::process +import std::test +import std::test::assert +import test::fixtures + +def with_path(block: do (Path)) { + let path = fixtures.temporary_file_path + + process.defer { + try file.remove(path) else Nil + } + + block.call(path) +} + +def with_paths(block: do (Path, Path)) { + let path1 = fixtures.temporary_file_path + let path2 = (path1.to_string + '2').to_path + + process.defer { + try file.remove(path1) else Nil + try file.remove(path2) else Nil + } + + block.call(path1, path2) +} + +def abstract_socket_address -> String { + "\0inko-tests-" + process.current.identifier.to_string +} + +test.group('std::net::unix::SocketAddress.to_path') do (g) { + g.test('Converting a path based SocketAddress to a Path') { + let addr = SocketAddress.new('foo.sock') + + assert.equal(addr.to_path, 'foo.sock'.to_path) + } + + g.test('Converting an abstract SocketAddress to a Path') { + let addr = SocketAddress.new("\0foo") + + assert.equal(addr.to_path, Nil) + } + + g.test('Converting an unnamed SocketAddress to a Path') { + let addr = SocketAddress.new + + assert.equal(addr.to_path, Nil) + } +} + +test.group('std::net::unix::SocketAddress.to_string') do (g) { + g.test('Converting a path based SocketAddress to a String') { + let addr = SocketAddress.new('foo.sock') + + assert.equal(addr.to_string, 'foo.sock') + } + + g.test('Converting an abstract SocketAddress to a String') { + let addr = SocketAddress.new("\0foo") + + assert.equal(addr.to_string, "\0foo") + } + + g.test('Converting an unnamed SocketAddress to a String') { + let addr = SocketAddress.new + + assert.equal(addr.to_string, '') + } +} + +test.group('std::net::unix::SocketAddress.abstract?') do (g) { + g.test('Checking if a SocketAddress is an abstract address') { + assert.false(SocketAddress.new.abstract?) + assert.false(SocketAddress.new('foo.sock').abstract?) + assert.true(SocketAddress.new("\0foo").abstract?) + } +} + +test.group('std::net::unix::SocketAddress.unnamed?') do (g) { + g.test('Checking if a SocketAddress is an unnamed address') { + assert.false(SocketAddress.new('foo.sock').unnamed?) + assert.false(SocketAddress.new("\0foo").unnamed?) + assert.true(SocketAddress.new.unnamed?) + } +} + +test.group('std::net::unix::SocketAddress.==') do (g) { + g.test('Comparing two SocketAddress objects') { + assert.equal(SocketAddress.new('a.sock'), SocketAddress.new('a.sock')) + assert.not_equal(SocketAddress.new('a.sock'), SocketAddress.new('b.sock')) + } +} + +os.unix?.if_true { + test.group('std::net::unix::Socket.new') do (g) { + g.test('Creating datagram Unix socket') { + assert.no_panic { + try! Socket.new(DGRAM) + } + } + + g.test('Creating a stream Unix socket') { + assert.no_panic { + try! Socket.new(STREAM) + } + } + + os.mac?.if_false { + g.test('Creating a sequential packet Unix socket') { + assert.no_panic { + try! Socket.new(SEQPACKET) + } + } + + g.test('Creating a raw Unix socket') { + assert.no_panic { + try! Socket.new(RAW) + } + } + } + + g.test('Creating a Unix socket of an invalid kind') { + assert.panic { + try! Socket.new(9999) + } + } + } + + test.group('std::net::unix::Socket.bind') do (g) { + g.test('Binding a Unix socket to a path') { + assert.no_panic { + with_path do (path) { + let socket = try! Socket.new(STREAM) + + try! socket.bind(path) + } + } + } + + g.test('Binding a Unix socket to a path that already exists') { + assert.panic { + with_path do (path) { + let socket1 = try! Socket.new(STREAM) + let socket2 = try! Socket.new(STREAM) + + try! socket1.bind(path) + try! socket2.bind(path) + } + } + } + + os.linux?.if_true { + g.test('Binding a Unix socket to an abstract address') { + assert.no_panic { + let socket = try! Socket.new(STREAM) + + try! socket.bind(abstract_socket_address) + } + } + } + } + + test.group('std::net::unix::Socket.connect') do (g) { + g.test('Connecting to an invalid address') { + assert.panic { + with_path do (path) { + let socket = try! Socket.new(STREAM) + + try! socket.connect(path) + } + } + } + + g.test('Connecting to a valid address') { + assert.no_panic { + with_path do (path) { + let listener = try! Socket.new(STREAM) + let stream = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + try! stream.connect(path) + } + } + } + + os.linux?.if_true { + g.test('Connecting to an abstract address') { + assert.no_panic { + let listener = try! Socket.new(STREAM) + let stream = try! Socket.new(STREAM) + + try! listener.bind(abstract_socket_address) + try! listener.listen + try! stream.connect(abstract_socket_address) + } + } + } + } + + test.group('std::net::unix::Socket.listen') do (g) { + g.test('Marking a Socket as listening with a custom backlog') { + with_path do (path) { + let socket = try! Socket.new(STREAM) + + try! socket.bind(path) + + assert.equal(try! socket.listen(4), 4) + } + } + + g.test('Marking a Socket as listening with the default backlog') { + with_path do (path) { + let socket = try! Socket.new(STREAM) + + try! socket.bind(path) + + assert.equal(try! socket.listen, MAXIMUM_LISTEN_BACKLOG) + } + } + } + + test.group('std::net::unix::Socket.accept') do (g) { + g.test('Accepting an incoming connection') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + let stream = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + try! stream.connect(path) + + let client = try! listener.accept + + assert.equal(try! client.peer_address, try! stream.local_address) + } + } + } + + test.group('std::net::unix::Socket.send_to') do (g) { + g.test('Sending a message to a specific address') { + with_path do (path) { + let socket = try! Socket.new(DGRAM) + + try! socket.bind(path) + try! socket.send_to(message: 'ping', address: path) + + assert.equal(try! socket.read_string(size: 4), 'ping') + } + } + } + + test.group('std::net::unix::Socket.receive_from') do (g) { + g.test('Receiving a message and a SocketAddress') { + with_paths do (path1, path2) { + let listener = try! Socket.new(DGRAM) + let client = try! Socket.new(DGRAM) + + try! listener.bind(path1) + try! client.bind(path2) + try! client.send_to(message: 'ping', address: path1) + + let bytes = ByteArray.new + let sender = try! listener.receive_from(bytes: bytes, size: 4) + + assert.equal(sender, try! client.local_address) + assert.equal(bytes.to_string, 'ping') + } + } + } + + test.group('std::net::unix::Socket.local_address') do (g) { + g.test('Obtaining the local address of an unbound socket') { + let socket = try! Socket.new(DGRAM) + let address = try! socket.local_address + + assert.equal(address, SocketAddress.new) + } + + g.test('Obtaining the local address of a bound socket') { + with_path do (path) { + let socket = try! Socket.new(DGRAM) + + try! socket.bind(path) + + let local_address = try! socket.local_address + + assert.equal(local_address.to_string, path.to_string) + } + } + } + + test.group('std::net::unix::Socket.peer_address') do (g) { + g.test('Obtaining the peer address of a disconnected socket') { + assert.panic { + let socket = try! Socket.new(DGRAM) + + try! socket.peer_address + } + } + + g.test('Obtaining the peer address of a connected socket') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + let client = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + try! client.connect(path) + + assert.equal(try! client.peer_address, try! listener.local_address) + } + } + } + + test.group('std::net::unix::Socket.read_bytes') do (g) { + g.test('Reading bytes from a Socket') { + with_path do (path) { + let socket = try! Socket.new(DGRAM) + let bytes = ByteArray.new + + try! socket.bind(path) + try! socket.send_to(message: 'ping', address: path) + + let read = try! socket.read_bytes(bytes: bytes, size: 4) + + assert.equal(bytes.to_string, 'ping') + assert.equal(read, 4) + } + } + } + + test.group('std::net::unix::Socket.write_bytes') do (g) { + g.test('Writing bytes to a Socket') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + let stream = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + try! stream.connect(path) + + let written = try! stream.write_bytes('ping'.to_byte_array) + let connection = try! listener.accept + let message = try! connection.read_string(size: 4) + + assert.equal(message, 'ping') + assert.equal(written, 4) + } + } + } + + test.group('std::net::unix::Socket.close') do (g) { + g.test('Closing a Socket') { + assert.no_panic { + let socket = try! Socket.new(STREAM) + + socket.close + } + } + } + + test.group('std::net::unix::Socket.flush') do (g) { + g.test('Flushing a Socket') { + let socket = try! Socket.new(STREAM) + + assert.equal(socket.flush, Nil) + } + } + + test.group('std::net::unix::Socket.receive_buffer_size') do (g) { + g.test('Setting and obtaining the value of the SO_RCVBUF option') { + let socket = try! Socket.new(STREAM) + + try! socket.receive_buffer_size = 256 + + assert.true(try! socket.receive_buffer_size >= 256) + } + } + + test.group('std::net::unix::Socket.send_buffer_size') do (g) { + g.test('Setting and obtaining the value of the SO_SNDBUT option') { + let socket = try! Socket.new(STREAM) + + try! socket.send_buffer_size = 256 + + assert.true(try! socket.send_buffer_size >= 256) + } + } + + test.group('std::net::unix::UnixDatagram') do (g) { + g.test('Creating a UnixDatagram') { + assert.no_panic { + with_path do (path) { + try! UnixDatagram.new(path) + } + } + } + + g.test('Creating a UnixDatagram using an address that is already in use') { + assert.panic { + with_path do (path) { + try! UnixDatagram.new(path) + try! UnixDatagram.new(path) + } + } + } + } + + test.group('std::net::unix::UnixDatagram.connect') do (g) { + g.test('Connecting a UnixDatagram') { + assert.no_panic { + with_paths do (path1, path2) { + let socket1 = try! UnixDatagram.new(path1) + let socket2 = try! UnixDatagram.new(path2) + + try! socket1.connect(path2) + } + } + } + + g.test('Reading and writing from a connected UdpSocket') { + with_paths do (path1, path2) { + let socket1 = try! UnixDatagram.new(path1) + let socket2 = try! UnixDatagram.new(path2) + let address = try! socket2.local_address + + try! socket1.connect(path2) + try! socket1.write_string('ping') + + assert.equal(try! socket2.read_string(4), 'ping') + } + } + } + + test.group('std::net::unix::UnixDatagram.send_to') do (g) { + g.test('Sending a message to a specific address') { + with_path do (path) { + let socket = try! UnixDatagram.new(path) + + try! socket.send_to(message: 'ping', address: path) + + assert.equal(try! socket.read_string(size: 4), 'ping') + } + } + } + + test.group('std::net::unix::UnixDatagram.receive_from') do (g) { + g.test('Receiving a message and a SocketAddress') { + with_paths do (path1, path2) { + let listener = try! UnixDatagram.new(path1) + let client = try! UnixDatagram.new(path2) + + try! client.send_to(message: 'ping', address: path1) + + let bytes = ByteArray.new + let sender = try! listener.receive_from(bytes: bytes, size: 4) + + assert.equal(sender, try! client.local_address) + assert.equal(bytes.to_string, 'ping') + } + } + } + + test.group('std::net::unix::UnixDatagram.local_address') do (g) { + g.test('Obtaining the local address of a UnixDatagram') { + with_path do (path) { + let socket = try! UnixDatagram.new(path) + let local_address = try! socket.local_address + + assert.equal(local_address.to_string, path.to_string) + } + } + } + + test.group('std::net::unix::UnixDatagram.read_bytes') do (g) { + g.test('Reading bytes from a UnixDatagram') { + with_path do (path) { + let socket = try! UnixDatagram.new(path) + let bytes = ByteArray.new + + try! socket.send_to(message: 'ping', address: path) + + let read = try! socket.read_bytes(bytes: bytes, size: 4) + + assert.equal(bytes.to_string, 'ping') + assert.equal(read, 4) + } + } + } + + test.group('std::net::unix::UnixDatagram.write_bytes') do (g) { + g.test('Writing bytes to a connected UnixDatagram') { + with_paths do (path1, path2) { + let server = try! UnixDatagram.new(path1) + let client = try! UnixDatagram.new(path2) + + try! client.connect(path1) + + let written = try! client.write_bytes('ping'.to_byte_array) + let message = try! server.read_string(size: 4) + + assert.equal(message, 'ping') + assert.equal(written, 4) + } + } + + g.test('Writing bytes to a disconnected UnixDatagram') { + assert.panic { + with_path do (path) { + let socket = try! UnixDatagram.new(path) + + try! socket.write_bytes('ping'.to_byte_array) + } + } + } + } + + test.group('std::net::unix::UnixDatagram.close') do (g) { + g.test('Closing a UnixDatagram') { + assert.no_panic { + with_path do (path) { + let socket = try! UnixDatagram.new(path) + + socket.close + } + } + } + } + + test.group('std::net::unix::UnixDatagram.flush') do (g) { + g.test('Flushing a UnixDatagram') { + with_path do (path) { + let socket = try! UnixDatagram.new(path) + + assert.equal(socket.flush, Nil) + } + } + } + + test.group('std::net::unix::UnixStream.new') do (g) { + g.test('Creating a new UnixStream') { + assert.no_panic { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + try! UnixStream.new(path) + } + } + } + } + + test.group('std::net::unix::UnixStream.local_address') do (g) { + g.test('Obtaining the local address') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + + let stream = try! UnixStream.new(path) + let local_addr = try! stream.local_address + + assert.equal(local_addr.to_string, '') + } + } + } + + test.group('std::net::unix::UnixStream.peer_address') do (g) { + g.test('Obtaining the peer address') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + + let stream = try! UnixStream.new(path) + let peer_addr = try! stream.peer_address + + assert.equal(peer_addr.to_string, path.to_string) + } + } + } + + test.group('std::net::unix::UnixStream.read_bytes') do (g) { + g.test('Reading bytes from a UnixStream') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + + let stream = try! UnixStream.new(path) + let bytes = ByteArray.new + + try! listener.accept.write_string('ping') + try! stream.read_bytes(bytes: bytes, size: 4) + + assert.equal(bytes.to_string, 'ping') + } + } + } + + + test.group('std::net::unix::UnixStream.write_bytes') do (g) { + g.test('Writing bytes to a UnixStream') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + + let stream = try! UnixStream.new(path) + let connection = try! listener.accept + + try! stream.write_bytes('ping'.to_byte_array) + + assert.equal(try! connection.read_string(4), 'ping') + } + } + } + + test.group('std::net::unix::UnixStream.write_string') do (g) { + g.test('Writing a String to a UnixStream') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + + let stream = try! UnixStream.new(path) + let connection = try! listener.accept + + try! stream.write_string('ping') + + assert.equal(try! connection.read_string(4), 'ping') + } + } + } + + test.group('std::net::unix::UnixStream.close') do (g) { + g.test('Closing a UnixStream') { + assert.no_panic { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + + let stream = try! UnixStream.new(path) + + stream.close + } + } + } + } + + test.group('std::net::unix::UnixStream.flush') do (g) { + g.test('Flushing a UnixStream') { + with_path do (path) { + let listener = try! Socket.new(STREAM) + + try! listener.bind(path) + try! listener.listen + + let stream = try! UnixStream.new(path) + + assert.equal(stream.flush, Nil) + } + } + } + + test.group('std::net::unix::UnixListener.new') do (g) { + g.test('Creating a UnixListener') { + assert.no_panic { + with_path do (path) { + try! UnixListener.new(path) + } + } + } + } + + test.group('std::net::unix::UnixListener.accept') do (g) { + g.test('Accepting an incoming connection') { + with_path do (path) { + let listener = try! UnixListener.new(path) + let stream = try! UnixStream.new(path) + let connection = try! listener.accept + + assert.equal(try! connection.local_address, try! stream.peer_address) + } + } + } + + test.group('std::net::unix::UnixListener.local_address') do (g) { + g.test('Obtaining the local address') { + with_path do (path) { + let listener = try! UnixListener.new(path) + let addr = try! listener.local_address + + assert.equal(addr.to_string, path.to_string) + } + } + } + + test.group('std::net::unix::UnixListener.close') do (g) { + g.test('Closing a UnixListener') { + with_path do (path) { + let listener = try! UnixListener.new(path) + + assert.equal(listener.close, Nil) + } + } + } +} diff --git a/runtime/tests/test/std/test_integer.inko b/runtime/tests/test/std/test_integer.inko index 314f482ef..76735511b 100644 --- a/runtime/tests/test/std/test_integer.inko +++ b/runtime/tests/test/std/test_integer.inko @@ -277,3 +277,73 @@ test.group('std::integer.parse') do (g) { ) } } + +test.group('std::integer::Integer.format') do (g) { + g.test('Formatting an Integer in base 2') { + assert.equal(4.format(radix: 2), '100') + assert.equal(10.format(radix: 2), '1010') + assert.equal(32.format(radix: 2), '100000') + + assert.equal(-4.format(radix: 2), '-100') + assert.equal(-10.format(radix: 2), '-1010') + assert.equal(-32.format(radix: 2), '-100000') + } + + g.test('Formatting an Integer in base 4') { + assert.equal(4.format(radix: 4), '10') + assert.equal(10.format(radix: 4), '22') + assert.equal(32.format(radix: 4), '200') + + assert.equal(-4.format(radix: 4), '-10') + assert.equal(-10.format(radix: 4), '-22') + assert.equal(-32.format(radix: 4), '-200') + } + + g.test('Formatting an Integer in base 16') { + assert.equal(4.format(radix: 16), '4') + assert.equal(10.format(radix: 16), 'a') + assert.equal(32.format(radix: 16), '20') + assert.equal(0x2ff.format(radix: 16), '2ff') + + assert.equal(-4.format(radix: 16), '-4') + assert.equal(-10.format(radix: 16), '-a') + assert.equal(-32.format(radix: 16), '-20') + assert.equal(-0x2ff.format(radix: 16), '-2ff') + } + + g.test('Formatting an Integer in base 32') { + assert.equal(4.format(radix: 32), '4') + assert.equal(10.format(radix: 32), 'a') + assert.equal(32.format(radix: 32), '10') + + assert.equal(-4.format(radix: 32), '-4') + assert.equal(-10.format(radix: 32), '-a') + assert.equal(-32.format(radix: 32), '-10') + } + + g.test('Formatting Integer 0') { + assert.equal(0.format(radix: 2), '0') + assert.equal(0.format(radix: 4), '0') + assert.equal(0.format(radix: 16), '0') + } + + g.test('Using a radix smaller than 2') { + assert.panic { + 10.format(radix: -1) + } + + assert.panic { + 10.format(radix: 0) + } + + assert.panic { + 10.format(radix: 1) + } + } + + g.test('Using a radix greater than 36') { + assert.panic { + 10.format(radix: 37) + } + } +} diff --git a/runtime/tests/test/std/test_os.inko b/runtime/tests/test/std/test_os.inko index f49219d0a..cc775c383 100644 --- a/runtime/tests/test/std/test_os.inko +++ b/runtime/tests/test/std/test_os.inko @@ -29,6 +29,24 @@ test.group('std::os.windows?') do (g) { } } +test.group('std::os.linux?') do (g) { + g.test('Checking if the underlying platform is Linux') { + assert.equal(os.linux?, os.platform == 'linux') + } +} + +test.group('std::os.unix?') do (g) { + g.test('Checking if the underlying platform is Unix') { + assert.equal(os.unix?, os.windows?.not) + } +} + +test.group('std::os.mac?') do (g) { + g.test('Checking if the underlying platform is Mac OS') { + assert.equal(os.mac?, os.platform == 'macos') + } +} + test.group('std::os::NEWLINE') do (g) { g.test('Obtaining the newline separator for the underlying platform') { let expected = os.windows?.if true: { "\r\n" }, false: { "\n" } diff --git a/vm/Cargo.lock b/vm/Cargo.lock index 8a93f9183..80cc2d125 100644 --- a/vm/Cargo.lock +++ b/vm/Cargo.lock @@ -7,10 +7,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "aho-corasick" -version = "0.6.8" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -18,15 +18,24 @@ name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "argon2rs" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "arrayvec" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -34,9 +43,9 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -45,66 +54,106 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "bindgen" -version = "0.31.3" +name = "backtrace" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cexpr 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "clang-sys 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bindgen" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cexpr 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "clang-sys 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "which 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitflags" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cc" -version = "1.0.25" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cexpr" -version = "0.2.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cfg-if" -version = "0.1.3" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clang-sys" -version = "0.21.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clap" -version = "2.32.0" +version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -114,16 +163,21 @@ name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "constant_time_eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "crossbeam-channel" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -149,12 +203,12 @@ name = "crossbeam-epoch" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -163,10 +217,10 @@ name = "crossbeam-epoch" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -184,7 +238,7 @@ name = "crossbeam-utils" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -192,31 +246,55 @@ name = "crossbeam-utils" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "dirs" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "either" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "env_logger" -version = "0.4.3" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -224,7 +302,7 @@ name = "float-cmp" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -238,28 +316,34 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "getopts" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "glob" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "getopts" -version = "0.2.17" +name = "hashbrown" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "glob" -version = "0.2.11" +name = "humantime" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "inko" @@ -268,44 +352,35 @@ dependencies = [ "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "dirs 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "float-cmp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "libffi 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libffi-sys 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libffi-sys 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "wepoll-binding 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.42" +version = "0.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -314,37 +389,27 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "abort_on_panic 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "libffi-sys 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libffi-sys 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libffi-sys" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bindgen 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)", "make-cmd 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "libloading" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libloading" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -358,18 +423,10 @@ dependencies = [ [[package]] name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -379,45 +436,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" -version = "1.0.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "memchr" -version = "2.0.2" +name = "memoffset" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "memoffset" -version = "0.2.1" +name = "nix" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "nodrop" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nom" -version = "3.2.1" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-bigint" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -425,12 +484,12 @@ name = "num-integer" version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -438,7 +497,7 @@ name = "num_cpus" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -446,7 +505,7 @@ name = "owning_ref" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "stable_deref_trait 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -463,11 +522,11 @@ name = "parking_lot_core" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -481,18 +540,24 @@ version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "quote" -version = "0.3.15" +name = "proc-macro2" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "rand" -version = "0.4.2" +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -501,7 +566,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -510,7 +575,7 @@ dependencies = [ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -556,9 +621,9 @@ name = "rand_jitter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -568,10 +633,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -593,23 +658,23 @@ dependencies = [ [[package]] name = "rayon" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rayon-core" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -622,7 +687,7 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.40" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -630,29 +695,45 @@ name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_users" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "0.2.11" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.5.6" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-demangle" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc_version" version = "0.2.3" @@ -661,6 +742,11 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "scopeguard" version = "0.3.3" @@ -679,37 +765,79 @@ name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "smallvec" -version = "0.6.3" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "socket2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "stable_deref_trait" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" -version = "0.7.0" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.30" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termcolor" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "termion" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "textwrap" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -720,22 +848,22 @@ name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "time" -version = "0.1.40" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ucd-util" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -744,16 +872,13 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "unreachable" -version = "1.0.0" +name = "unicode-xid" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "utf8-ranges" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -761,27 +886,46 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "which" -version = "1.0.5" +name = "wepoll-binding" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wepoll-sys 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "winapi" -version = "0.2.8" +name = "wepoll-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "which" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "winapi" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -789,35 +933,53 @@ dependencies = [ ] [[package]] -name = "winapi-build" -version = "0.1.1" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "winapi-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wincolor" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum abort_on_panic 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fa948f9ec9f095cc955efbe4fd00ac5774ef933cc2442562a8fe5a57c4ef919" -"checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a" +"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" +"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" +"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" -"checksum bindgen 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)" = "57253399c086f4f29e57ffd3b5cdbc23a806a00292619351aa4cfa39cb49d4ea" -"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" -"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" -"checksum cexpr 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42aac45e9567d97474a834efdee3081b3c942b2205be932092f53354ce503d6c" -"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" -"checksum clang-sys 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e414af9726e1d11660801e73ccc7fb81803fb5f49e5903a25b348b2b3b480d2e" -"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f106c02a3604afcdc0df5d36cc47b44b55917dbaf3d808f71c163a0ddba64637" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33e1b67a27bca31fd12a683b2a3618e275311117f48cfcc892e18403ff889026" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +"checksum cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5f3fee5eeb60324c2781f1e41286bdee933850fff9b3c672587fed5ec58c83" +"checksum cexpr 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7fa24eb00d5ffab90eaeaf1092ac85c04c64aaf358ea6f84505b8116d24c6af" +"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum clang-sys 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4227269cec09f5f83ff160be12a1e9b0262dd1aa305302d5ba296c2ebd291055" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b" "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" @@ -826,44 +988,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" -"checksum dirs 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f679c09c1cf5428702cc10f6846c56e4e23420d3a88bcc9335b17c630a7b710b" -"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" -"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" +"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" +"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum float-cmp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "134a8fa843d80a51a5b77d36d42bc2def9edcb0262c914861d08129fd1926600" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05" +"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" -"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" -"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" +"checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" "checksum libffi 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d8a9dac273181f514d742b6b858be5153570c5b80dd4d6020093c0fa584578b1" -"checksum libffi-sys 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4675d5d7fdbba34b66218fae3b0d528c2b29580a64ca2ccc5bbfc5af2324b373" -"checksum libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fd38073de8f7965d0c17d30546d4bb6da311ab428d1c7a3fc71dff7f9d4979b9" +"checksum libffi-sys 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c012abf4e6dc9525f2b3fa3dce2ae8a6422a3208ec9161efcc571afdefe729fc" "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" -"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum make-cmd 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8ca8afbe8af1785e09636acb5a41e08a765f5f0340568716c18a8700ba3c0d3" -"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" -"checksum memchr 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a3b4142ab8738a78c51896f704f83c11df047ff1bda9a92a661aa6361552d93d" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" -"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" -"checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" -"checksum num-bigint 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3eceac7784c5dc97c2d6edf30259b4e153e6e2b42b3c85e9a6e9f45d06caef6e" +"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" -"checksum num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "630de1ef5cc79d0cdd78b7e33b81f083cbfe90de0f4b2b2f07f905867c70e9fe" +"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" @@ -874,33 +1035,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80e811e76f1dbf68abf87a759083d34600017fc4e10b6bd5ad84a700f9dba4b1" -"checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8" +"checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473" +"checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" -"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +"checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" +"checksum regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "559008764a17de49a3146b234641644ed37d118d1ef641a0bb573d146edc6ce0" +"checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" +"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "26df3bb03ca5eac2e64192b723d51f56c1b1e0860e7c766281f4598f181acdc8" -"checksum stable_deref_trait 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbc596e092fe5f598b12ef46cc03754085ac2f4d8c739ad61c4ae266cc3b3fa" -"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" +"checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" +"checksum socket2 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "4e626972d3593207547f14bf5fc9efa4d0e7283deb73fef1dff313dae9ab8878" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2" +"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" -"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" -"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum which 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e84a603e7e0b1ce1aa1ee2b109c7be00155ce52df5081590d1ffb93f4f515cb2" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum wepoll-binding 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "860d7de1f9291b0b7521b45492e6a46fc4eca6d16a1679945560be339a0c3d29" +"checksum wepoll-sys 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1edd385408e1cf7cd3d144c5b61c4e9d27174a2f1dc500b801afc095ee35566f" +"checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index e4e022960..a9333fe61 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -26,7 +26,19 @@ num-traits = "^0.2" dirs = "^1.0" libloading = "^0.5" libffi = "^0.6" -libffi-sys = ">=0.6.3" +libffi-sys = "^0.6" crossbeam-deque = "^0.7" crossbeam-channel = "^0.3" crossbeam-queue = "^0.1" +libc = "^0.2" + +[dependencies.socket2] +version = "^0.3.9" +features = ["unix", "reuseport"] + +[target.'cfg(not(windows))'.dependencies] +nix = "^0.13" + +[target.'cfg(windows)'.dependencies] +wepoll-binding = "^1.0.2" +winapi = "^0.3" diff --git a/vm/src/duration.rs b/vm/src/duration.rs new file mode 100644 index 000000000..0963958a0 --- /dev/null +++ b/vm/src/duration.rs @@ -0,0 +1,29 @@ +use std::time::Duration; + +/// Converts a Duration to the time in seconds as an f64. +pub fn to_f64(value: Option) -> f64 { + value + .map(|duration| { + duration.as_secs() as f64 + + (f64::from(duration.subsec_nanos()) / 1_000_000_000.0) + }) + .unwrap_or(0.0) +} + +/// Converts an f64 (in seconds) to a Duration. +pub fn from_f64(value: f64) -> Result, String> { + if value < 0.0 { + return Err(format!("{} is not a valid time duration", value)); + } + + let result = if value == 0.0 { + None + } else { + let secs = value.trunc() as u64; + let nanos = (value.fract() * 1_000_000_000.0) as u32; + + Some(Duration::new(secs, nanos)) + }; + + Ok(result) +} diff --git a/vm/src/error_messages.rs b/vm/src/error_messages.rs index 5fde4ba3e..fb9e58ec0 100644 --- a/vm/src/error_messages.rs +++ b/vm/src/error_messages.rs @@ -1,4 +1,3 @@ -use std::error::Error; use std::io; pub const IO_PERMISSION_DENIED: &str = @@ -46,26 +45,24 @@ pub const IO_NOT_FOUND: &str = "The resource could not be found."; /// Returns an error message from a Rust IO error. pub fn from_io_error(error: &io::Error) -> String { - let slice = match error.kind() { - io::ErrorKind::NotFound => IO_NOT_FOUND, - io::ErrorKind::PermissionDenied => IO_PERMISSION_DENIED, - io::ErrorKind::ConnectionRefused => IO_CONNECTION_REFUSED, - io::ErrorKind::ConnectionReset => IO_CONNECTION_RESET, - io::ErrorKind::ConnectionAborted => IO_CONNECTION_ABORTED, - io::ErrorKind::NotConnected => IO_NOT_CONNECTED, - io::ErrorKind::AddrInUse => IO_ADDRESS_IN_USE, - io::ErrorKind::AddrNotAvailable => IO_ADDRESS_NOT_AVAILABLE, - io::ErrorKind::BrokenPipe => IO_BROKEN_PIPE, - io::ErrorKind::AlreadyExists => IO_ALREADY_EXISTS, - io::ErrorKind::WouldBlock => IO_WOULD_BLOCK, - io::ErrorKind::InvalidInput => IO_INVALID_INPUT, - io::ErrorKind::InvalidData => IO_INVALID_DATA, - io::ErrorKind::TimedOut => IO_TIMED_OUT, - io::ErrorKind::WriteZero => IO_WRITE_ZERO, - io::ErrorKind::Interrupted => IO_INTERRUPTED, - io::ErrorKind::UnexpectedEof => IO_UNEXPECTED_EOF, - _ => error.description(), - }; - - slice.to_string() + match error.kind() { + io::ErrorKind::NotFound => IO_NOT_FOUND.to_string(), + io::ErrorKind::PermissionDenied => IO_PERMISSION_DENIED.to_string(), + io::ErrorKind::ConnectionRefused => IO_CONNECTION_REFUSED.to_string(), + io::ErrorKind::ConnectionReset => IO_CONNECTION_RESET.to_string(), + io::ErrorKind::ConnectionAborted => IO_CONNECTION_ABORTED.to_string(), + io::ErrorKind::NotConnected => IO_NOT_CONNECTED.to_string(), + io::ErrorKind::AddrInUse => IO_ADDRESS_IN_USE.to_string(), + io::ErrorKind::AddrNotAvailable => IO_ADDRESS_NOT_AVAILABLE.to_string(), + io::ErrorKind::BrokenPipe => IO_BROKEN_PIPE.to_string(), + io::ErrorKind::AlreadyExists => IO_ALREADY_EXISTS.to_string(), + io::ErrorKind::WouldBlock => IO_WOULD_BLOCK.to_string(), + io::ErrorKind::InvalidInput => IO_INVALID_INPUT.to_string(), + io::ErrorKind::InvalidData => IO_INVALID_DATA.to_string(), + io::ErrorKind::TimedOut => IO_TIMED_OUT.to_string(), + io::ErrorKind::WriteZero => IO_WRITE_ZERO.to_string(), + io::ErrorKind::Interrupted => IO_INTERRUPTED.to_string(), + io::ErrorKind::UnexpectedEof => IO_UNEXPECTED_EOF.to_string(), + _ => error.to_string(), + } } diff --git a/vm/src/ffi.rs b/vm/src/ffi.rs index 45f81255c..d5ae2e5ff 100644 --- a/vm/src/ffi.rs +++ b/vm/src/ffi.rs @@ -728,7 +728,7 @@ impl Function { // and the values they point to must outlive the FFI call, otherwise we // may end up passing pointers to invalid memory. let mut argument_pointers: Vec = - arguments.iter_mut().map(|arg| arg.as_c_pointer()).collect(); + arguments.iter_mut().map(Argument::as_c_pointer).collect(); // libffi requires a mutable pointer to the CIF, but "self" is immutable // since we never actually modify the current function. To work around diff --git a/vm/src/filesystem.rs b/vm/src/filesystem.rs index 7d55c0bb5..6424a6a4e 100644 --- a/vm/src/filesystem.rs +++ b/vm/src/filesystem.rs @@ -5,6 +5,7 @@ use crate::error_messages; use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::process::RcProcess; +use crate::runtime_error::RuntimeError; use crate::vm::state::RcState; use std::fs; @@ -18,7 +19,9 @@ const TYPE_DIRECTORY: i64 = 2; macro_rules! map_io { ($op:expr) => {{ - $op.map_err(|err| error_messages::from_io_error(&err)) + $op.map_err(|err| { + RuntimeError::Exception(error_messages::from_io_error(&err)) + }) }}; } @@ -26,14 +29,22 @@ macro_rules! map_io { /// /// The `kind` argument specifies whether the creation, modification or access /// time should be retrieved. -pub fn date_time_for_path(path: &str, kind: i64) -> Result { +pub fn date_time_for_path( + path: &str, + kind: i64, +) -> Result { let meta = map_io!(fs::metadata(path))?; let system_time = match kind { TIME_CREATED => map_io!(meta.created())?, TIME_MODIFIED => map_io!(meta.modified())?, TIME_ACCESSED => map_io!(meta.accessed())?, - _ => return Err(format!("{} is not a valid type of timestamp", kind)), + _ => { + return Err(RuntimeError::Panic(format!( + "{} is not a valid type of timestamp", + kind + ))); + } }; Ok(DateTime::from_system_time(system_time)) @@ -60,7 +71,7 @@ pub fn list_directory_as_pointers( state: &RcState, process: &RcProcess, path: &str, -) -> Result { +) -> Result { let mut paths = Vec::new(); for entry in map_io!(fs::read_dir(path))? { diff --git a/vm/src/immix/bucket.rs b/vm/src/immix/bucket.rs index 1900cb8c1..cf3be5fd7 100644 --- a/vm/src/immix/bucket.rs +++ b/vm/src/immix/bucket.rs @@ -226,7 +226,7 @@ impl Bucket { // the time we start a collection (as they have all been consumed). As // such we don't check for these and instead only check for fragmented // blocks. - self.blocks.iter().any(|block| block.is_fragmented()) + self.blocks.iter().any(Block::is_fragmented) } /// Reclaims the blocks in this bucket diff --git a/vm/src/immix/copy_object.rs b/vm/src/immix/copy_object.rs index ee82b7fa0..a82d6c0eb 100644 --- a/vm/src/immix/copy_object.rs +++ b/vm/src/immix/copy_object.rs @@ -76,6 +76,9 @@ pub trait CopyObject: Sized { ObjectValue::Process(ref proc) => { ObjectValue::Process(proc.clone()) } + ObjectValue::Socket(ref socket) => { + ObjectValue::Socket(socket.clone()) + } }; let mut copy = if let Some(proto_ptr) = to_copy.prototype() { diff --git a/vm/src/immix/local_allocator.rs b/vm/src/immix/local_allocator.rs index d336271aa..f4c226cde 100644 --- a/vm/src/immix/local_allocator.rs +++ b/vm/src/immix/local_allocator.rs @@ -2,8 +2,6 @@ //! //! The LocalAllocator lives in a Process and is used for allocating memory on a //! process heap. -use std::collections::HashSet; - use crate::config::Config; use crate::gc::work_list::WorkList; use crate::immix::bucket::{Bucket, MATURE}; @@ -16,6 +14,7 @@ use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::object_value::ObjectValue; use crate::vm::state::RcState; +use fnv::FnvHashSet; /// The maximum age of a bucket in the young generation. pub const YOUNG_MAX_AGE: i8 = 2; @@ -41,7 +40,7 @@ pub struct LocalAllocator { /// The remembered set of this process. This set is not synchronized via a /// lock of sorts. As such the collector must ensure this process is /// suspended upon examining the remembered set. - pub remembered_set: HashSet, + pub remembered_set: FnvHashSet, /// The bucket to use for the mature generation. pub mature_generation: Bucket, @@ -71,7 +70,7 @@ impl LocalAllocator { mature_generation: Bucket::with_age(MATURE), young_config: GenerationConfig::new(config.young_threshold), mature_config: GenerationConfig::new(config.mature_threshold), - remembered_set: HashSet::new(), + remembered_set: FnvHashSet::default(), } } @@ -248,7 +247,7 @@ impl LocalAllocator { pub fn number_of_young_blocks(&self) -> u32 { self.young_generation .iter() - .map(|bucket| bucket.number_of_blocks()) + .map(Bucket::number_of_blocks) .sum() } @@ -261,7 +260,7 @@ impl LocalAllocator { } pub fn prune_remembered_objects(&mut self) { - self.remembered_set.retain(|p| p.is_marked()); + self.remembered_set.retain(ObjectPointer::is_marked); } pub fn prepare_remembered_objects_for_collection(&mut self) { @@ -531,6 +530,6 @@ mod tests { fn test_type_size() { // This test is put in place to ensure that the type size doesn't change // unexpectedly. - assert_eq!(mem::size_of::(), 264); + assert!(mem::size_of::() <= 264); } } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index ccb94251d..c6f63380f 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -12,6 +12,7 @@ pub mod config; pub mod date_time; pub mod deref_pointer; pub mod directories; +pub mod duration; pub mod error_messages; pub mod execution_context; pub mod ffi; @@ -26,6 +27,7 @@ pub mod macros; pub mod mailbox; pub mod module; pub mod module_registry; +pub mod network_poller; pub mod numeric; pub mod object; pub mod object_pointer; @@ -34,9 +36,11 @@ pub mod platform; pub mod prefetch; pub mod process; pub mod register; +pub mod runtime_error; pub mod runtime_panic; pub mod scheduler; pub mod slicing; +pub mod socket; pub mod stacktrace; pub mod string_pool; pub mod tagged_pointer; diff --git a/vm/src/macros/objects.rs b/vm/src/macros/objects.rs index 1f3584bfd..b74c13597 100644 --- a/vm/src/macros/objects.rs +++ b/vm/src/macros/objects.rs @@ -16,3 +16,9 @@ macro_rules! is_false { $pointer == $state.false_object || $pointer == $state.nil_object }; } + +macro_rules! is_true { + ($state:expr, $pointer:expr) => { + $pointer == $state.true_object + }; +} diff --git a/vm/src/network_poller.rs b/vm/src/network_poller.rs new file mode 100644 index 000000000..cc5012cd4 --- /dev/null +++ b/vm/src/network_poller.rs @@ -0,0 +1,157 @@ +//! Polling of non-blocking sockets using the system's polling mechanism. +pub mod event_id; +pub mod interest; +pub mod worker; + +#[cfg(unix)] +pub mod unix; + +#[cfg(target_os = "linux")] +pub mod epoll; + +#[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos" +))] +pub mod kqueue; + +#[cfg(windows)] +pub mod wepoll; + +#[cfg(target_os = "linux")] +use crate::network_poller::epoll as sys; + +#[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos" +))] +use crate::network_poller::kqueue as sys; + +#[cfg(windows)] +use crate::network_poller::wepoll as sys; + +/// A type for polling non-blocking sockets. +pub type NetworkPoller = sys::NetworkPoller; + +/// A collection of events produced by a `NetworkPoller`. +pub type Events = sys::Events; + +#[cfg(test)] +mod tests { + use super::*; + use crate::network_poller::event_id::EventId; + use crate::network_poller::interest::Interest; + use std::net::UdpSocket; + + #[test] + fn test_register() { + let output = UdpSocket::bind("0.0.0.0:0").unwrap(); + let poller = NetworkPoller::new(); + + assert!(poller.register(&output, EventId(0), Interest::Read).is_ok()); + } + + #[test] + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + fn test_reregister_invalid() { + let output = UdpSocket::bind("0.0.0.0:0").unwrap(); + let poller = NetworkPoller::new(); + + assert!(poller + .reregister(&output, EventId(0), Interest::Read) + .is_ok()); + } + + #[test] + #[cfg(any(target_os = "windows", target_os = "linux"))] + fn test_reregister_invalid() { + let output = UdpSocket::bind("0.0.0.0:0").unwrap(); + let poller = NetworkPoller::new(); + + assert!(poller + .reregister(&output, EventId(0), Interest::Read) + .is_err()); + } + + #[test] + fn test_reregister_valid() { + let output = UdpSocket::bind("0.0.0.0:0").unwrap(); + let poller = NetworkPoller::new(); + + assert!(poller.register(&output, EventId(0), Interest::Read).is_ok()); + + assert!(poller + .reregister(&output, EventId(0), Interest::Write) + .is_ok()); + } + + #[test] + fn test_poll() { + let output = UdpSocket::bind("0.0.0.0:0").unwrap(); + let poller = NetworkPoller::new(); + let mut events = Events::with_capacity(1); + + poller + .register(&output, EventId(1), Interest::Write) + .unwrap(); + + assert!(poller.poll(&mut events).is_ok()); + + assert_eq!(events.capacity(), 1); + assert_eq!(events.len(), 1); + assert_eq!(events.event_ids().next().unwrap(), EventId(1)); + } + + #[test] + fn test_poll_with_lower_capacity() { + let sock1 = UdpSocket::bind("0.0.0.0:0").unwrap(); + let sock2 = UdpSocket::bind("0.0.0.0:0").unwrap(); + let poller = NetworkPoller::new(); + let mut events = Events::with_capacity(1); + + poller + .register(&sock1, EventId(0), Interest::Write) + .unwrap(); + + poller + .register(&sock2, EventId(1), Interest::Write) + .unwrap(); + + poller.poll(&mut events).unwrap(); + + assert_eq!(events.capacity(), 1); + assert_eq!(events.len(), 1); + } + + #[test] + fn test_poll_with_enough_capacity() { + let sock1 = UdpSocket::bind("0.0.0.0:0").unwrap(); + let sock2 = UdpSocket::bind("0.0.0.0:0").unwrap(); + let poller = NetworkPoller::new(); + let mut events = Events::with_capacity(2); + + poller + .register(&sock1, EventId(0), Interest::Write) + .unwrap(); + + poller + .register(&sock2, EventId(1), Interest::Write) + .unwrap(); + + poller.poll(&mut events).unwrap(); + + assert_eq!(events.capacity(), 2); + assert_eq!(events.len(), 2); + + let event_ids = events.event_ids().collect::>(); + + assert!(event_ids.contains(&EventId(0))); + assert!(event_ids.contains(&EventId(1))); + } +} diff --git a/vm/src/network_poller/epoll.rs b/vm/src/network_poller/epoll.rs new file mode 100644 index 000000000..f196d66b2 --- /dev/null +++ b/vm/src/network_poller/epoll.rs @@ -0,0 +1,145 @@ +//! Polling of non-blocking sockets for Linux. +//! +//! This module provides support for polling non-blocking sockets on Linux using +//! epoll. +use super::event_id::EventId; +use super::interest::Interest; +use super::unix::map_error; +use nix::sys::epoll::{ + epoll_create, epoll_ctl, epoll_wait, EpollEvent, EpollFlags, EpollOp, +}; +use nix::unistd::close; +use std::io; +use std::ops::Drop; +use std::os::unix::io::{AsRawFd, RawFd}; + +const POLL_INDEFINITELY: isize = -1; + +/// A collection of epoll events. +pub struct Events { + events: Vec, +} + +#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))] +impl Events { + pub fn with_capacity(amount: usize) -> Self { + Events { + events: Vec::with_capacity(amount), + } + } + + pub fn len(&self) -> usize { + self.events.len() + } + + pub fn capacity(&self) -> usize { + self.events.capacity() + } + + pub fn event_ids<'a>(&'a self) -> impl Iterator + 'a { + self.events.iter().map(|e| EventId(e.data())) + } + + fn set_len(&mut self, amount: usize) { + unsafe { + self.events.set_len(amount); + } + } +} + +/// Polling of non-blocking sockets using epoll. +pub struct NetworkPoller { + fd: RawFd, +} + +unsafe impl Sync for NetworkPoller {} +unsafe impl Send for NetworkPoller {} + +impl NetworkPoller { + pub fn new() -> NetworkPoller { + let fd = + epoll_create().expect("Failed to create an epoll file descriptor"); + + NetworkPoller { fd } + } + + pub fn poll(&self, events: &mut Events) -> io::Result<()> { + // The nix crate uses slice lengths, but our buffer is a Vec. To make + // this work we have to manually set the length of our Vec. + // + // We don't need to clear the input buffer, as old events will either be + // overwritten or deallocated when the Events structure is dropped. + events.set_len(events.events.capacity()); + + let received = map_error(epoll_wait( + self.fd, + &mut events.events, + POLL_INDEFINITELY, + ))?; + + // The number of events might be smaller than the desired length, so we + // need to readjust the length of our buffer. + events.set_len(received); + + Ok(()) + } + + pub fn register( + &self, + fd: &T, + id: EventId, + interest: Interest, + ) -> io::Result<()> { + self.register_or_reregister(fd, id, interest, false) + } + + pub fn reregister( + &self, + fd: &T, + id: EventId, + interest: Interest, + ) -> io::Result<()> { + self.register_or_reregister(fd, id, interest, true) + } + + fn register_or_reregister( + &self, + fd: &T, + id: EventId, + interest: Interest, + update: bool, + ) -> io::Result<()> { + let mut flags = match interest { + Interest::Read => EpollFlags::EPOLLIN, + Interest::Write => EpollFlags::EPOLLOUT, + }; + + // We always want edge triggered events so we don't wake up too many + // threads at once, and oneshot events so we don't keep producing events + // while rescheduling a process. + flags = flags | EpollFlags::EPOLLET | EpollFlags::EPOLLONESHOT; + + let mut event = EpollEvent::new(flags, id.value()); + + let operation = if update { + EpollOp::EpollCtlMod + } else { + EpollOp::EpollCtlAdd + }; + + map_error(epoll_ctl( + self.fd, + operation, + fd.as_raw_fd(), + Some(&mut event), + ))?; + + Ok(()) + } +} + +impl Drop for NetworkPoller { + fn drop(&mut self) { + close(self.fd).expect("Failed to close the epoll file descriptor"); + } +} diff --git a/vm/src/network_poller/event_id.rs b/vm/src/network_poller/event_id.rs new file mode 100644 index 000000000..4af11a66e --- /dev/null +++ b/vm/src/network_poller/event_id.rs @@ -0,0 +1,9 @@ +/// The unique ID of an event obtained by a poller. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct EventId(pub u64); + +impl EventId { + pub fn value(self) -> u64 { + self.0 + } +} diff --git a/vm/src/network_poller/interest.rs b/vm/src/network_poller/interest.rs new file mode 100644 index 000000000..01340775d --- /dev/null +++ b/vm/src/network_poller/interest.rs @@ -0,0 +1,8 @@ +// The type of event a poller should wait for. +pub enum Interest { + /// We're only interested in read operations. + Read, + + /// We're only interested in write operations. + Write, +} diff --git a/vm/src/network_poller/kqueue.rs b/vm/src/network_poller/kqueue.rs new file mode 100644 index 000000000..f4ef5af8d --- /dev/null +++ b/vm/src/network_poller/kqueue.rs @@ -0,0 +1,159 @@ +//! Polling of non-blocking sockets for BSD based systems. +//! +//! This module provides support for polling non-blocking sockets on BSD based +//! systems using kqueue. +use super::event_id::EventId; +use super::interest::Interest; +use super::unix::map_error; +use nix::errno::Errno; +use nix::sys::event::{ + kevent_ts, kqueue, EventFilter, EventFlag, FilterFlag, KEvent, +}; +use nix::unistd::close; +use std::io; +use std::mem; +use std::ops::Drop; +use std::os::unix::io::{AsRawFd, RawFd}; + +macro_rules! kevent { + ($fd:expr, $filter:ident, $flags:expr, $id:expr) => { + KEvent::new( + $fd.as_raw_fd() as usize, + EventFilter::$filter, + $flags, + FilterFlag::empty(), + 0, + $id.value() as isize, + ) + }; +} + +/// A collection of kqueue events. +pub struct Events { + events: Vec, +} + +#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))] +impl Events { + pub fn with_capacity(amount: usize) -> Self { + Events { + events: Vec::with_capacity(amount), + } + } + + pub fn len(&self) -> usize { + self.events.len() + } + + pub fn capacity(&self) -> usize { + self.events.capacity() + } + + pub fn event_ids<'a>(&'a self) -> impl Iterator + 'a { + self.events.iter().map(|e| EventId(e.udata() as u64)) + } + + fn set_len(&mut self, amount: usize) { + unsafe { + self.events.set_len(amount); + } + } +} + +/// Polling of non-blocking sockets using kqueue. +pub struct NetworkPoller { + fd: RawFd, +} + +unsafe impl Sync for NetworkPoller {} +unsafe impl Send for NetworkPoller {} + +impl NetworkPoller { + pub fn new() -> NetworkPoller { + let fd = kqueue().expect("Failed to create a kqueue file descriptor"); + + NetworkPoller { fd } + } + + pub fn poll(&self, events: &mut Events) -> io::Result<()> { + // The nix crate uses slice lengths, but our buffer is a Vec. To make + // this work we have to manually set the length of our Vec. + // + // We don't need to clear the input buffer, as old events will either be + // overwritten or deallocated when the Events structure is dropped. + events.set_len(events.events.capacity()); + + let received = + map_error(kevent_ts(self.fd, &[], &mut events.events, None))?; + + // The number of events might be smaller than the desired length, so we + // need to readjust the length of our buffer. + events.set_len(received); + + Ok(()) + } + + pub fn register( + &self, + fd: &T, + id: EventId, + interest: Interest, + ) -> io::Result<()> { + let flags = + EventFlag::EV_CLEAR | EventFlag::EV_ONESHOT | EventFlag::EV_RECEIPT; + + // Reads and writes are registered as separate events. This means that + // if we want a read, we have to make sure previous writes are disabled. + let (read_flag, write_flag) = match interest { + Interest::Read => (EventFlag::EV_ADD, EventFlag::EV_DELETE), + Interest::Write => (EventFlag::EV_DELETE, EventFlag::EV_ADD), + }; + + let changes = [ + kevent!(fd, EVFILT_READ, flags | read_flag, id), + kevent!(fd, EVFILT_WRITE, flags | write_flag, id), + ]; + + let mut changed: [KEvent; 2] = unsafe { mem::uninitialized() }; + + map_error(kevent_ts(self.fd, &changes, &mut changed, None))?; + + for event in &changed { + if event.data() == 0 { + continue; + } + + let errno = Errno::from_i32(event.data() as i32); + + // When adding an event of one type (e.g. read), we'll attempt to + // remove the other (a write), but that event might not exist. If + // this happens an ENOENT is produced, and we'll ignore it. + if event.flags().contains(EventFlag::EV_DELETE) + && errno == Errno::ENOENT + { + continue; + } + + return Err(io::Error::from(errno)); + } + + Ok(()) + } + + pub fn reregister( + &self, + fd: &T, + id: EventId, + interest: Interest, + ) -> io::Result<()> { + // Re-adding an existing event will modify it, so we can just use + // register(). + self.register(fd, id, interest) + } +} + +impl Drop for NetworkPoller { + fn drop(&mut self) { + close(self.fd).expect("Failed to close the kqueue file descriptor"); + } +} diff --git a/vm/src/network_poller/unix.rs b/vm/src/network_poller/unix.rs new file mode 100644 index 000000000..95179e732 --- /dev/null +++ b/vm/src/network_poller/unix.rs @@ -0,0 +1,7 @@ +//! Helper functionality for Unix based network pollers. +use nix; +use std::io; + +pub fn map_error(error: nix::Result) -> io::Result { + error.map_err(|err| io::Error::from(err.as_errno().unwrap())) +} diff --git a/vm/src/network_poller/wepoll.rs b/vm/src/network_poller/wepoll.rs new file mode 100644 index 000000000..c69f5df07 --- /dev/null +++ b/vm/src/network_poller/wepoll.rs @@ -0,0 +1,84 @@ +//! Polling of non-blocking sockets for Windows. +//! +//! This module provides support for polling non-blocking sockets on Windows +//! using the wepoll ()https://github.com/piscisaureus/wepoll) library. +use super::event_id::EventId; +use super::interest::Interest; +use std::io; +use std::os::windows::io::AsRawSocket; +use wepoll_binding::{Epoll, EventFlag, Events as WepollEvents}; + +/// A collection of wepoll events. +pub struct Events { + events: WepollEvents, +} + +#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))] +impl Events { + pub fn with_capacity(amount: usize) -> Self { + Events { + events: WepollEvents::with_capacity(amount), + } + } + + pub fn len(&self) -> usize { + self.events.len() + } + + pub fn capacity(&self) -> usize { + self.events.capacity() + } + + pub fn event_ids<'a>(&'a self) -> impl Iterator + 'a { + self.events.iter().map(|e| EventId(e.data())) + } +} + +/// Polling of non-blocking sockets using wepoll. +pub struct NetworkPoller { + epoll: Epoll, +} + +impl NetworkPoller { + pub fn new() -> NetworkPoller { + let epoll = + Epoll::new().expect("Failed to create the wepoll file descriptor"); + + NetworkPoller { epoll } + } + + pub fn poll(&self, events: &mut Events) -> io::Result<()> { + self.epoll.poll(&mut events.events, None)?; + + Ok(()) + } + + pub fn register( + &self, + socket: &T, + id: EventId, + interest: Interest, + ) -> io::Result<()> { + self.epoll + .register(socket, self.flags_for(interest), id.value()) + } + + pub fn reregister( + &self, + socket: &T, + id: EventId, + interest: Interest, + ) -> io::Result<()> { + self.epoll + .reregister(socket, self.flags_for(interest), id.value()) + } + + fn flags_for(&self, interest: Interest) -> EventFlag { + let flags = match interest { + Interest::Read => EventFlag::IN, + Interest::Write => EventFlag::OUT, + }; + + flags | EventFlag::ONESHOT + } +} diff --git a/vm/src/network_poller/worker.rs b/vm/src/network_poller/worker.rs new file mode 100644 index 000000000..33e63aa8c --- /dev/null +++ b/vm/src/network_poller/worker.rs @@ -0,0 +1,34 @@ +use crate::arc_without_weak::ArcWithoutWeak; +use crate::network_poller::Events; +use crate::vm::state::RcState; + +/// The maximum number of events to process in a single poll loop iteration. +const EVENTS_PER_ITERATION: usize = 1024; + +pub struct Worker { + state: RcState, +} + +impl Worker { + pub fn new(state: RcState) -> Self { + Worker { state } + } + + pub fn run(&self) { + let mut events = Events::with_capacity(EVENTS_PER_ITERATION); + + loop { + self.state + .network_poller + .poll(&mut events) + .expect("Failed to poll for network events"); + + for id in events.event_ids() { + let process = + unsafe { ArcWithoutWeak::from_raw(id.value() as *mut _) }; + + self.state.scheduler.schedule(process); + } + } + } +} diff --git a/vm/src/object_pointer.rs b/vm/src/object_pointer.rs index ae1270501..b276f08d5 100644 --- a/vm/src/object_pointer.rs +++ b/vm/src/object_pointer.rs @@ -30,6 +30,7 @@ use crate::immutable_string::ImmutableString; use crate::object::{Object, ObjectStatus, FORWARDED_BIT}; use crate::object_value::ObjectValue; use crate::process::RcProcess; +use crate::socket::Socket; use crate::tagged_pointer::TaggedPointer; use crate::vm::state::RcState; @@ -615,6 +616,9 @@ impl ObjectPointer { def_value_getter!(function_value, get, as_function, &RcFunction); def_value_getter!(pointer_value, get, as_pointer, Pointer); def_value_getter!(process_value, get, as_process, &RcProcess); + def_value_getter!(socket_value, get, as_socket, &Socket); + + def_value_getter!(socket_value_mut, get_mut, as_socket_mut, &mut Socket); /// Atomically loads the underlying pointer, returning a new ObjectPointer. pub fn atomic_load(&self) -> Self { diff --git a/vm/src/object_value.rs b/vm/src/object_value.rs index 666989771..7f073f4db 100644 --- a/vm/src/object_value.rs +++ b/vm/src/object_value.rs @@ -3,7 +3,6 @@ //! Objects need to be able to store values of different types such as floats or //! strings. The ObjectValue enum can be used for storing such data and //! operating on it. - use crate::arc_without_weak::ArcWithoutWeak; use crate::binding::RcBinding; use crate::block::Block; @@ -12,6 +11,7 @@ use crate::hasher::Hasher; use crate::immutable_string::ImmutableString; use crate::object_pointer::ObjectPointer; use crate::process::RcProcess; +use crate::socket::Socket; use num_bigint::BigInt; use std::fs; use std::mem; @@ -58,6 +58,9 @@ pub enum ObjectValue { /// A lightweight Inko process. Process(RcProcess), + + /// A nonblocking socket. + Socket(Box), } impl ObjectValue { @@ -292,6 +295,24 @@ impl ObjectValue { } } + pub fn as_socket(&self) -> Result<&Socket, String> { + match *self { + ObjectValue::Socket(ref sock) => Ok(sock), + _ => { + Err("ObjectValue::as_socket() called on a non socket" + .to_string()) + } + } + } + + pub fn as_socket_mut(&mut self) -> Result<&mut Socket, String> { + match *self { + ObjectValue::Socket(ref mut sock) => Ok(sock), + _ => Err("ObjectValue::as_socket_mut() called on a non socket" + .to_string()), + } + } + pub fn take(&mut self) -> ObjectValue { mem::replace(self, ObjectValue::None) } @@ -332,6 +353,7 @@ impl ObjectValue { ObjectValue::Function(_) => "Function", ObjectValue::Pointer(_) => "Pointer", ObjectValue::Process(_) => "Process", + ObjectValue::Socket(_) => "Socket", } } } @@ -404,6 +426,10 @@ pub fn process(value: RcProcess) -> ObjectValue { ObjectValue::Process(value) } +pub fn socket(value: Socket) -> ObjectValue { + ObjectValue::Socket(Box::new(value)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/vm/src/process.rs b/vm/src/process.rs index 67ff582e6..0ece64713 100644 --- a/vm/src/process.rs +++ b/vm/src/process.rs @@ -777,7 +777,7 @@ mod tests { // This test is put in place to ensure the type size doesn't change // unintentionally. - assert_eq!(size, 448); + assert!(size <= 448); } #[test] diff --git a/vm/src/runtime_error.rs b/vm/src/runtime_error.rs new file mode 100644 index 000000000..2151e3699 --- /dev/null +++ b/vm/src/runtime_error.rs @@ -0,0 +1,57 @@ +//! Errors that can be produced at VM runtime. +use crate::error_messages; +use std::convert::From; +use std::io; +use std::net::AddrParseError; + +/// An error that can be raised in the VM at runtime.] +#[derive(Debug)] +pub enum RuntimeError { + /// An error that should be turned into an exception, allowing code to + /// handle it. + Exception(String), + + /// A fatal error that should result in the VM terminating. + Panic(String), + + /// A non-blocking operation would block, and should be retried at a later + /// point in time. + WouldBlock, + + /// A non-blocking operation is still in progress and should not be retried. + /// Instead, the process should be suspended until the operation is done, + /// after which it should start off at the _next_ instruction. + InProgress, +} + +impl RuntimeError { + pub fn should_poll(&self) -> bool { + match self { + RuntimeError::WouldBlock => true, + RuntimeError::InProgress => true, + _ => false, + } + } +} + +impl From for RuntimeError { + fn from(error: io::Error) -> Self { + if error.kind() == io::ErrorKind::WouldBlock { + RuntimeError::WouldBlock + } else { + RuntimeError::Exception(error_messages::from_io_error(&error)) + } + } +} + +impl From for RuntimeError { + fn from(result: String) -> Self { + RuntimeError::Panic(result) + } +} + +impl From for RuntimeError { + fn from(result: AddrParseError) -> Self { + RuntimeError::Exception(result.to_string()) + } +} diff --git a/vm/src/socket.rs b/vm/src/socket.rs new file mode 100644 index 000000000..9dd9cadd4 --- /dev/null +++ b/vm/src/socket.rs @@ -0,0 +1,407 @@ +pub mod socket_address; + +use crate::arc_without_weak::ArcWithoutWeak; +use crate::duration; +use crate::network_poller::event_id::EventId; +use crate::network_poller::interest::Interest; +use crate::network_poller::NetworkPoller; +use crate::process::RcProcess; +use crate::runtime_error::RuntimeError; +use crate::socket::socket_address::SocketAddress; +use socket2::{Domain, SockAddr, Socket as RawSocket, Type}; +use std::io; +use std::net::Ipv4Addr; +use std::net::{IpAddr, SocketAddr}; +use std::slice; + +#[cfg(unix)] +use libc::EINPROGRESS; + +#[cfg(windows)] +use winapi::shared::winerror::WSAEINPROGRESS as EINPROGRESS; + +macro_rules! socket_setter { + ($setter:ident, $type:ty) => { + pub fn $setter(&self, value: $type) -> Result<(), RuntimeError> { + self.socket.$setter(value)?; + + Ok(()) + } + } +} + +macro_rules! socket_getter { + ($getter:ident, $type:ty) => { + pub fn $getter(&self) -> Result<$type, RuntimeError> { + Ok(self.socket.$getter()?) + } + } +} + +macro_rules! socket_u32_getter { + ($getter:ident) => { + pub fn $getter(&self) -> Result { + Ok(self.socket.$getter()? as usize) + } + } +} + +macro_rules! socket_duration_setter { + ($setter:ident) => { + pub fn $setter(&self, value: f64) -> Result<(), RuntimeError> { + let dur = duration::from_f64(value)?; + + self.socket.$setter(dur)?; + + Ok(()) + } + } +} + +macro_rules! socket_duration_getter { + ($getter:ident) => { + pub fn $getter(&self) -> Result { + let dur = self.socket.$getter()?; + + Ok(duration::to_f64(dur)) + } + } +} + +const DOMAIN_IPV4: u8 = 0; +const DOMAIN_IPV6: u8 = 1; +const DOMAIN_UNIX: u8 = 2; + +/// Decodes a SockAddr into an address/path, and a port. +fn decode_sockaddr( + sockaddr: SockAddr, + unix: bool, +) -> Result<(String, i64), RuntimeError> { + let peer_result = if unix { + SocketAddress::Unix(sockaddr).address() + } else { + SocketAddress::Other(sockaddr).address() + }; + + Ok(peer_result?) +} + +#[cfg(unix)] +fn encode_sockaddr( + address: &str, + port: u16, + unix: bool, +) -> Result { + if unix { + return Ok(SockAddr::unix(address)?); + } + + let ip = address.parse::()?; + + Ok(SockAddr::from(SocketAddr::new(ip, port))) +} + +#[cfg(not(unix))] +fn encode_sockaddr( + address: &str, + port: u16, + _unix: bool, +) -> Result { + let ip = address.parse::()?; + + Ok(SockAddr::from(SocketAddr::new(ip, port))) +} + +/// Returns a slice of the input buffer that a socket operation can write to. +/// +/// The slice has enough space to store up to `bytes` of data. +fn socket_output_slice(buffer: &mut Vec, bytes: usize) -> &mut [u8] { + let len = buffer.len(); + let available = buffer.capacity() - len; + let to_reserve = bytes - available; + + if to_reserve > 0 { + // Only increasing capacity when needed is done for two reasons: + // + // 1. It saves us from increasing capacity when there is enough space. + // + // 2. Due to sockets being non-blocking, a socket operation may fail. + // This will result in this code being called multiple times. If we + // were to simply increase capacity every time we'd end up growing + // the buffer much more than necessary. + buffer.reserve_exact(to_reserve); + } + + unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr().add(len), bytes) } +} + +/// A nonblocking socket that can be registered with a `NetworkPoller`. +pub struct Socket { + /// The raw socket. + socket: RawSocket, + + /// A flag indicating that this socket has been registered with a poller. + /// + /// This flag is necessary because the system's polling mechanism may not + /// allow overwriting existing registrations without setting some additional + /// flags. For example, epoll requires the use of EPOLL_CTL_MOD when + /// overwriting a registration, as using EPOLL_CTL_ADD will produce an error + /// if a file descriptor is already registered. + registered: bool, + + /// A flag indicating if we're dealing with a UNIX socket or not. + unix: bool, +} + +impl Socket { + pub fn new(domain_int: u8, kind_int: u8) -> Result { + let domain = match domain_int { + DOMAIN_IPV4 => Domain::ipv4(), + DOMAIN_IPV6 => Domain::ipv6(), + + #[cfg(unix)] + DOMAIN_UNIX => Domain::unix(), + + _ => { + return Err(RuntimeError::Panic(format!( + "{} is not a valid socket domain", + domain_int + ))) + } + }; + + let kind = match kind_int { + 0 => Type::stream(), + 1 => Type::dgram(), + 2 => Type::seqpacket(), + 3 => Type::raw(), + _ => { + return Err(RuntimeError::Panic(format!( + "{} is not a valid socket type", + kind_int + ))) + } + }; + + let socket = RawSocket::new(domain, kind, None)?; + + socket.set_nonblocking(true)?; + + Ok(Socket { + socket, + registered: false, + unix: domain_int == DOMAIN_UNIX, + }) + } + + pub fn bind(&self, address: &str, port: u16) -> Result<(), RuntimeError> { + let sockaddr = encode_sockaddr(address, port, self.unix)?; + + self.socket.bind(&sockaddr)?; + + Ok(()) + } + + pub fn listen(&self, backlog: i32) -> Result<(), RuntimeError> { + self.socket.listen(backlog)?; + + Ok(()) + } + + pub fn connect( + &self, + address: &str, + port: u16, + ) -> Result<(), RuntimeError> { + let sockaddr = encode_sockaddr(address, port, self.unix)?; + + match self.socket.connect(&sockaddr) { + Ok(_) => {} + #[cfg(windows)] + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + // On Windows a connect(2) might throw WSAEWOULDBLOCK, the + // Windows equivalent of EAGAIN/EWOULDBLOCK. When this happens + // we should not retry the connect(), as that may then fail with + // WSAEISCONN. Instead, we signal that the network poller should + // just wait until the socket is ready for writing. + return Err(RuntimeError::InProgress); + } + Err(ref e) if e.raw_os_error() == Some(EINPROGRESS as i32) => { + return Err(RuntimeError::InProgress); + } + Err(e) => { + return Err(e.into()); + } + } + + Ok(()) + } + + pub fn register( + &mut self, + process: &RcProcess, + poller: &NetworkPoller, + interest: Interest, + ) -> Result<(), RuntimeError> { + let event_id = + EventId(ArcWithoutWeak::into_raw(process.clone()) as u64); + + if self.registered { + poller.reregister(&self.socket, event_id, interest)?; + } else { + poller.register(&self.socket, event_id, interest)?; + + self.registered = true; + } + + Ok(()) + } + + pub fn accept(&self) -> Result { + let (socket, _) = self.socket.accept()?; + + Ok(Socket { + socket, + registered: false, + unix: self.unix, + }) + } + + pub fn recv_from( + &self, + buffer: &mut Vec, + bytes: usize, + ) -> Result<(String, i64), RuntimeError> { + let slice = socket_output_slice(buffer, bytes); + let (read, sockaddr) = self.socket.recv_from(slice)?; + + unsafe { + buffer.set_len(buffer.len() + read); + } + + Ok(decode_sockaddr(sockaddr, self.unix)?) + } + + pub fn send_to( + &self, + buffer: &[u8], + address: &str, + port: u16, + ) -> Result { + let sockaddr = encode_sockaddr(address, port, self.unix)?; + + Ok(self.socket.send_to(buffer, &sockaddr)?) + } + + pub fn local_address(&self) -> Result<(String, i64), RuntimeError> { + let sockaddr = self.socket.local_addr()?; + + Ok(decode_sockaddr(sockaddr, self.unix)?) + } + + pub fn peer_address(&self) -> Result<(String, i64), RuntimeError> { + let sockaddr = self.socket.peer_addr()?; + + Ok(decode_sockaddr(sockaddr, self.unix)?) + } + + pub fn is_unix(&self) -> bool { + self.unix + } + + socket_setter!(set_ttl, u32); + socket_setter!(set_only_v6, bool); + socket_setter!(set_nodelay, bool); + socket_setter!(set_broadcast, bool); + socket_setter!(set_multicast_loop_v4, bool); + socket_setter!(set_multicast_loop_v6, bool); + socket_setter!(set_reuse_address, bool); + + socket_setter!(set_recv_buffer_size, usize); + socket_setter!(set_send_buffer_size, usize); + socket_setter!(set_multicast_ttl_v4, u32); + socket_setter!(set_multicast_hops_v6, u32); + socket_setter!(set_multicast_if_v6, u32); + socket_setter!(set_unicast_hops_v6, u32); + + socket_duration_setter!(set_linger); + socket_duration_setter!(set_keepalive); + + socket_getter!(only_v6, bool); + socket_getter!(nodelay, bool); + socket_getter!(broadcast, bool); + socket_getter!(multicast_loop_v4, bool); + socket_getter!(multicast_loop_v6, bool); + socket_getter!(reuse_address, bool); + + socket_getter!(recv_buffer_size, usize); + socket_getter!(send_buffer_size, usize); + + socket_u32_getter!(ttl); + socket_u32_getter!(multicast_ttl_v4); + socket_u32_getter!(multicast_hops_v6); + socket_u32_getter!(multicast_if_v6); + socket_u32_getter!(unicast_hops_v6); + + socket_duration_getter!(linger); + socket_duration_getter!(keepalive); + + pub fn set_multicast_if_v4(&self, addr: &str) -> Result<(), RuntimeError> { + let ip_addr = addr.parse::()?; + + Ok(self.socket.set_multicast_if_v4(&ip_addr)?) + } + + pub fn multicast_if_v4(&self) -> Result { + Ok(self.socket.multicast_if_v4().map(|addr| addr.to_string())?) + } + + #[cfg(unix)] + pub fn set_reuse_port(&self, reuse: bool) -> Result<(), RuntimeError> { + Ok(self.socket.set_reuse_port(reuse)?) + } + + #[cfg(not(unix))] + pub fn set_reuse_port(&self, _reuse: bool) -> Result<(), RuntimeError> { + Ok(()) + } + + #[cfg(unix)] + pub fn reuse_port(&self) -> Result { + Ok(self.socket.reuse_port()?) + } + + #[cfg(not(unix))] + pub fn reuse_port(&self) -> Result { + Ok(false) + } +} + +impl io::Write for Socket { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.socket.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.socket.flush() + } +} + +impl io::Read for Socket { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.socket.recv(buf) + } +} + +impl Clone for Socket { + fn clone(&self) -> Self { + Socket { + socket: self + .socket + .try_clone() + .expect("Failed to clone the socket"), + registered: self.registered, + unix: self.unix, + } + } +} diff --git a/vm/src/socket/socket_address.rs b/vm/src/socket/socket_address.rs new file mode 100644 index 000000000..7257870fa --- /dev/null +++ b/vm/src/socket/socket_address.rs @@ -0,0 +1,97 @@ +use socket2::SockAddr; + +#[cfg(unix)] +use std::ffi::OsStr; + +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; + +#[cfg(unix)] +use libc::{sockaddr_un, AF_INET, AF_INET6}; + +#[cfg(windows)] +use winapi::shared::ws2def::{AF_INET, AF_INET6}; + +#[cfg(unix)] +fn sun_path_offset() -> usize { + let addr: libc::sockaddr_un = unsafe { std::mem::uninitialized() }; + let base = &addr as *const _ as usize; + let path = &addr.sun_path as *const _ as usize; + + path - base +} + +#[cfg(unix)] +fn unix_socket_path(sockaddr: &SockAddr) -> String { + let len = sockaddr.len() as usize - sun_path_offset(); + let raw_addr = unsafe { &*(sockaddr.as_ptr() as *mut sockaddr_un) }; + let path = unsafe { + &*(&raw_addr.sun_path as *const [libc::c_char] as *const [u8]) + }; + + if len == 0 || (cfg!(not(target_os = "linux")) && raw_addr.sun_path[0] == 0) + { + return String::new(); + } + + let (start, stop) = if raw_addr.sun_path[0] == 0 { + (1, len) + } else { + (0, len - 1) + }; + + // Abstract names might contain NULL bytes and invalid UTF8. Since Inko + // doesn't provide any better types at the moment we'll use a string and + // convert the data to UTF8. A byte array would technically be better, but + // these are mutable and make for an unpleasant runtime API. + OsStr::from_bytes(&path[start..stop]) + .to_string_lossy() + .into_owned() +} + +#[cfg(not(unix))] +fn unix_socket_path(_sockaddr: &SockAddr) -> String { + String::new() +} + +/// A wrapper around the system's structure for socket addresses, such as +/// `sockaddr_un` for UNIX sockets. +pub enum SocketAddress { + /// A UNIX socket. + /// + /// We use a separate enum variant because datagram UNIX sockets will have + /// the family field set to AF_UNSPEC when using certain functions such as + /// recvfrom(). + Unix(SockAddr), + + /// A socket of another type, such as an IPv4 or IPv6 socket. + Other(SockAddr), +} + +impl SocketAddress { + pub fn address(&self) -> Result<(String, i64), String> { + match self { + SocketAddress::Unix(sockaddr) => { + Ok((unix_socket_path(sockaddr), -1)) + } + SocketAddress::Other(sockaddr) => { + match i32::from(sockaddr.family()) { + AF_INET => { + let addr = sockaddr.as_inet().unwrap(); + + Ok((addr.ip().to_string(), i64::from(addr.port()))) + } + AF_INET6 => { + let addr = sockaddr.as_inet6().unwrap(); + + Ok((addr.ip().to_string(), i64::from(addr.port()))) + } + _ => Err(format!( + "The address family {} is not supported", + sockaddr.family() + )), + } + } + } + } +} diff --git a/vm/src/vm/env.rs b/vm/src/vm/env.rs index 97c6a7d4d..109483db2 100644 --- a/vm/src/vm/env.rs +++ b/vm/src/vm/env.rs @@ -4,9 +4,9 @@ use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::platform; use crate::process::RcProcess; +use crate::runtime_error::RuntimeError; use crate::vm::state::RcState; use std::env; -use std::io::Result as IOResult; /// Returns the value of an environment variable. pub fn get( @@ -93,19 +93,21 @@ pub fn tmp_directory(state: &RcState, process: &RcProcess) -> ObjectPointer { pub fn working_directory( state: &RcState, process: &RcProcess, -) -> IOResult { - directories::working_directory().map(|path| { - process.allocate(object_value::string(path), state.string_prototype) - }) +) -> Result { + let path = directories::working_directory()?; + + Ok(process.allocate(object_value::string(path), state.string_prototype)) } /// Sets the working directory of the current process. pub fn set_working_directory( dir_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let dir = dir_ptr.string_value()?; - Ok(directories::set_working_directory(dir).map(|_| dir_ptr)) + directories::set_working_directory(dir)?; + + Ok(dir_ptr) } /// Returns all the commandline arguments. diff --git a/vm/src/vm/instruction.rs b/vm/src/vm/instruction.rs index 714436009..9017f3556 100644 --- a/vm/src/vm/instruction.rs +++ b/vm/src/vm/instruction.rs @@ -165,6 +165,18 @@ pub enum InstructionType { StringToFloat, FloatToBits, ProcessIdentifier, + SocketCreate, + SocketWrite, + SocketRead, + SocketAccept, + SocketReceiveFrom, + SocketSendTo, + SocketAddress, + SocketGetOption, + SocketSetOption, + SocketBind, + SocketListen, + SocketConnect, } /// Struct for storing information about a single instruction. diff --git a/vm/src/vm/integer.rs b/vm/src/vm/integer.rs index 09c644c06..b71308f62 100644 --- a/vm/src/vm/integer.rs +++ b/vm/src/vm/integer.rs @@ -35,7 +35,9 @@ pub fn to_string( } else if integer.is_bigint() { integer.bigint_value()?.to_string() } else { - return Err("Only integers are supported for this operation".to_string()); + return Err( + "Only integers are supported for this operation".to_string() + ); }; Ok(process.allocate(object_value::string(result), state.string_prototype)) diff --git a/vm/src/vm/io.rs b/vm/src/vm/io.rs index 8eda29ec7..cd537ccad 100644 --- a/vm/src/vm/io.rs +++ b/vm/src/vm/io.rs @@ -3,6 +3,7 @@ use crate::filesystem; use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::process::RcProcess; +use crate::runtime_error::RuntimeError; use crate::vm::state::RcState; use num_traits::ToPrimitive; use std::fs; @@ -32,23 +33,18 @@ macro_rules! file_mode_error { /// Reads a number of bytes from a stream into a byte array. pub fn io_read( + state: &RcState, + process: &RcProcess, stream: &mut Read, buffer: &mut Vec, amount: ObjectPointer, -) -> Result, String> { +) -> Result { let result = if amount.is_integer() { - let amount_bytes = amount.integer_value().unwrap(); + let amount_bytes = amount.usize_value()?; - if amount_bytes < 0 { - return Err(format!( - "{} is not a valid number of bytes to read", - amount_bytes - )); - } - - stream.take(amount_bytes as u64).read_to_end(buffer) + stream.take(amount_bytes as u64).read_to_end(buffer)? } else { - stream.read_to_end(buffer) + stream.read_to_end(buffer)? }; // When reading into a buffer, the Vec type may decide to grow it beyond the @@ -57,7 +53,18 @@ pub fn io_read( // we manually shrink the buffer once we're done writing. buffer.shrink_to_fit(); - Ok(result) + Ok(process.allocate_usize(result, state.integer_prototype)) +} + +#[cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))] +pub fn buffer_to_write(buffer: &ObjectPointer) -> Result<&[u8], RuntimeError> { + let buff = if buffer.is_string() { + buffer.string_value()?.as_bytes() + } else { + buffer.byte_array_value()? + }; + + Ok(buff) } pub fn io_write( @@ -65,34 +72,30 @@ pub fn io_write( process: &RcProcess, output: &mut W, to_write: ObjectPointer, -) -> Result, String> { - let result = if to_write.is_string() { - output.write(to_write.string_value()?.as_bytes()) - } else { - output.write(to_write.byte_array_value()?) - }; +) -> Result { + let written = output.write(buffer_to_write(&to_write)?)?; - Ok(result.map(|num| process.allocate_usize(num, state.integer_prototype))) + Ok(process.allocate_usize(written, state.integer_prototype)) } pub fn io_flush( state: &RcState, output: &mut W, -) -> io::Result { - output.flush().map(|_| state.nil_object) +) -> Result { + Ok(output.flush().map(|_| state.nil_object)?) } pub fn stdout_write( state: &RcState, process: &RcProcess, to_write: ObjectPointer, -) -> Result, String> { +) -> Result { let mut output = io::stdout(); io_write(state, process, &mut output, to_write) } -pub fn stdout_flush(state: &RcState) -> io::Result { +pub fn stdout_flush(state: &RcState) -> Result { let mut output = io::stdout(); io_flush(state, &mut output) @@ -102,13 +105,13 @@ pub fn stderr_write( state: &RcState, process: &RcProcess, to_write: ObjectPointer, -) -> Result, String> { +) -> Result { let mut output = io::stderr(); io_write(state, process, &mut output, to_write) } -pub fn stderr_flush(state: &RcState) -> io::Result { +pub fn stderr_flush(state: &RcState) -> Result { let mut output = io::stdout(); io_flush(state, &mut output) @@ -119,14 +122,11 @@ pub fn stdin_read( process: &RcProcess, buffer_ptr: ObjectPointer, amount: ObjectPointer, -) -> Result, String> { +) -> Result { let mut input = io::stdin(); let buffer = buffer_ptr.byte_array_value_mut()?; - let result = io_read(&mut input, buffer, amount)? - .map(|num| process.allocate_usize(num, state.integer_prototype)); - - Ok(result) + io_read(state, process, &mut input, buffer, amount) } pub fn write_file( @@ -134,7 +134,7 @@ pub fn write_file( process: &RcProcess, file_ptr: ObjectPointer, to_write: ObjectPointer, -) -> Result, String> { +) -> Result { let file = file_ptr.file_value_mut()?; io_write(state, process, file, to_write) @@ -143,10 +143,10 @@ pub fn write_file( pub fn flush_file( state: &RcState, file_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let file = file_ptr.file_value_mut()?; - Ok(io_flush(state, file)) + io_flush(state, file) } pub fn read_file( @@ -155,14 +155,11 @@ pub fn read_file( file_ptr: ObjectPointer, buffer_ptr: ObjectPointer, amount: ObjectPointer, -) -> Result, String> { +) -> Result { let mut input = file_ptr.file_value_mut()?; let buffer = buffer_ptr.byte_array_value_mut()?; - let result = io_read(&mut input, buffer, amount)? - .map(|num| process.allocate_usize(num, state.integer_prototype)); - - Ok(result) + io_read(state, process, &mut input, buffer, amount) } pub fn open_file( @@ -170,29 +167,25 @@ pub fn open_file( process: &RcProcess, path_ptr: ObjectPointer, mode_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let path = path_ptr.string_value()?; let mode = mode_ptr.integer_value()?; let open_opts = options_for_integer(mode)?; let prototype = prototype_for_open_mode(&state, mode)?; + let file = open_opts.open(path)?; - let result = open_opts - .open(path) - .map(|file| process.allocate(object_value::file(file), prototype)); - - Ok(result) + Ok(process.allocate(object_value::file(file), prototype)) } pub fn file_size( state: &RcState, process: &RcProcess, path_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let path = path_ptr.string_value()?; - let result = fs::metadata(path) - .map(|meta| process.allocate_u64(meta.len(), state.integer_prototype)); + let meta = fs::metadata(path)?; - Ok(result) + Ok(process.allocate_u64(meta.len(), state.integer_prototype)) } pub fn seek_file( @@ -200,7 +193,7 @@ pub fn seek_file( process: &RcProcess, file_ptr: ObjectPointer, offset_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let file = file_ptr.file_value_mut()?; let offset = if offset_ptr.is_bigint() { @@ -209,32 +202,38 @@ pub fn seek_file( if let Some(offset) = big_offset.to_u64() { offset } else { - return Err(format!("{} is too big for a seek offset", big_offset)); + return Err(RuntimeError::Panic(format!( + "{} is too big for a seek offset", + big_offset + ))); } } else { let offset = offset_ptr.integer_value()?; if offset < 0 { - return Err(format!("{} is not a valid seek offset", offset)); + return Err(RuntimeError::Panic(format!( + "{} is not a valid seek offset", + offset + ))); } offset as u64 }; - let result = file - .seek(SeekFrom::Start(offset)) - .map(|cursor| process.allocate_u64(cursor, state.integer_prototype)); + let cursor = file.seek(SeekFrom::Start(offset))?; - Ok(result) + Ok(process.allocate_u64(cursor, state.integer_prototype)) } pub fn remove_file( state: &RcState, path_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let path_str = path_ptr.string_value()?; - Ok(fs::remove_file(path_str).map(|_| state.nil_object)) + fs::remove_file(path_str)?; + + Ok(state.nil_object) } pub fn copy_file( @@ -242,16 +241,17 @@ pub fn copy_file( process: &RcProcess, src_ptr: ObjectPointer, dst_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let src = src_ptr.string_value()?; let dst = dst_ptr.string_value()?; - let result = fs::copy(src, dst) - .map(|bytes| process.allocate_u64(bytes, state.integer_prototype)); + let bytes_copied = fs::copy(src, dst)?; - Ok(result) + Ok(process.allocate_u64(bytes_copied, state.integer_prototype)) } -pub fn file_type(path_ptr: ObjectPointer) -> Result { +pub fn file_type( + path_ptr: ObjectPointer, +) -> Result { let path = path_ptr.string_value()?; let file_type = filesystem::type_of_path(path); @@ -263,16 +263,13 @@ pub fn file_time( process: &RcProcess, path_ptr: ObjectPointer, kind_ptr: ObjectPointer, -) -> Result { +) -> Result { let path = path_ptr.string_value()?; let kind = kind_ptr.integer_value()?; + let dt = filesystem::date_time_for_path(path, kind)?; - filesystem::date_time_for_path(path, kind).map(|dt| { - process.allocate( - object_value::float(dt.timestamp()), - state.float_prototype, - ) - }) + Ok(process + .allocate(object_value::float(dt.timestamp()), state.float_prototype)) } pub fn options_for_integer(mode: i64) -> Result { @@ -308,40 +305,41 @@ pub fn create_directory( state: &RcState, path_ptr: ObjectPointer, recursive_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let path = path_ptr.string_value()?; - let result = if is_false!(state, recursive_ptr) { - fs::create_dir(path) + if is_false!(state, recursive_ptr) { + fs::create_dir(path)?; } else { - fs::create_dir_all(path) - }; + fs::create_dir_all(path)?; + } - Ok(result.map(|_| state.nil_object)) + Ok(state.nil_object) } pub fn remove_directory( state: &RcState, path_ptr: ObjectPointer, recursive_ptr: ObjectPointer, -) -> Result, String> { +) -> Result { let path = path_ptr.string_value()?; - let result = if is_false!(state, recursive_ptr) { - fs::remove_dir(path) + if is_false!(state, recursive_ptr) { + fs::remove_dir(path)?; } else { - fs::remove_dir_all(path) - }; + fs::remove_dir_all(path)?; + } - Ok(result.map(|_| state.nil_object)) + Ok(state.nil_object) } pub fn list_directory( state: &RcState, process: &RcProcess, path_ptr: ObjectPointer, -) -> Result { +) -> Result { let path = path_ptr.string_value()?; + let files = filesystem::list_directory_as_pointers(&state, process, path)?; - filesystem::list_directory_as_pointers(&state, process, path) + Ok(files) } diff --git a/vm/src/vm/machine.rs b/vm/src/vm/machine.rs index 31f9f0ca1..0203a4f93 100644 --- a/vm/src/vm/machine.rs +++ b/vm/src/vm/machine.rs @@ -1,20 +1,16 @@ //! Virtual Machine for running instructions -use num_bigint::BigInt; -use rayon::ThreadPoolBuilder; -use std::i32; -use std::ops::{Add, Mul, Sub}; -use std::panic; - use crate::compiled_code::CompiledCodePointer; use crate::execution_context::ExecutionContext; use crate::gc::request::Request as GcRequest; use crate::integer_operations; use crate::module_registry::{ModuleRegistry, RcModuleRegistry}; +use crate::network_poller::worker::Worker as NetworkPollerWorker; use crate::numeric::division::{FlooredDiv, OverflowingFlooredDiv}; use crate::numeric::modulo::{Modulo, OverflowingModulo}; use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::process::RcProcess; +use crate::runtime_error::RuntimeError; use crate::runtime_panic; use crate::scheduler::join_list::JoinList; use crate::scheduler::pool::Pool; @@ -32,9 +28,15 @@ use crate::vm::io; use crate::vm::module; use crate::vm::object; use crate::vm::process; +use crate::vm::socket; use crate::vm::state::RcState; use crate::vm::string; use crate::vm::time; +use num_bigint::BigInt; +use rayon::ThreadPoolBuilder; +use std::i32; +use std::ops::{Add, Mul, Sub}; +use std::panic; use std::thread; macro_rules! reset_context { @@ -86,20 +88,6 @@ macro_rules! throw_error_message { }}; } -macro_rules! throw_io_error { - ( - $machine:expr, - $process:expr, - $error:expr, - $context:ident, - $index:ident - ) => {{ - let msg = $crate::error_messages::from_io_error(&$error); - - throw_error_message!($machine, $process, msg, $context, $index); - }}; -} - macro_rules! enter_context { ($process:expr, $context:ident, $index:ident) => {{ $context.instruction_index = $index; @@ -125,6 +113,36 @@ macro_rules! safepoint_and_reduce { }}; } +macro_rules! try_runtime_error { + ($expr:expr, $vm:expr, $proc:expr, $context:ident, $index:ident) => { + match $expr { + Ok(thing) => thing, + Err(err) => { + match err { + RuntimeError::Panic(msg) => return Err(msg), + RuntimeError::Exception(msg) => { + throw_error_message!($vm, $proc, msg, $context, $index); + continue; + } + RuntimeError::WouldBlock => { + $context.instruction_index = $index - 1; + + // It's up to the called function to register the + // resource with a poller, so all we need/can do here is + // bail out. + return Ok(()); + } + RuntimeError::InProgress => { + $context.instruction_index = $index; + + return Ok(()); + } + }; + } + } + }; +} + #[derive(Clone)] pub struct Machine { pub state: RcState, @@ -161,6 +179,11 @@ impl Machine { let secondary_guard = self.start_blocking_threads(); let timeout_guard = self.start_timeout_worker_thread(); + // The network poller doesn't produce a guard, because there's no + // cross-platform way of waking up the system poller, so we just don't + // wait for it to finish when terminating. + self.start_network_poller_thread(); + // Starting the primary threads will block this thread, as the main // worker will run directly onto the current thread. As such, we must // start these threads last. @@ -234,6 +257,17 @@ impl Machine { .unwrap() } + fn start_network_poller_thread(&self) { + let state = self.state.clone(); + + thread::Builder::new() + .name("network poller".to_string()) + .spawn(move || { + NetworkPollerWorker::new(state).run(); + }) + .unwrap(); + } + fn terminate(&self) { self.state.scheduler.terminate(); self.state.gc_pool.terminate(); @@ -450,7 +484,9 @@ impl Machine { let divide_with = context.get_register(instruction.arg(2)); if divide_with.is_zero_integer() { - return Err("Can not divide an Integer by 0".to_string()); + return Err( + "Can not divide an Integer by 0".to_string() + ); } integer_overflow_op!( @@ -708,124 +744,147 @@ impl Machine { InstructionType::StdoutWrite => { let reg = instruction.arg(0); let input = context.get_register(instruction.arg(1)); + let size = try_runtime_error!( + io::stdout_write(&self.state, process, input), + self, + process, + context, + index + ); - match io::stdout_write(&self.state, process, input)? { - Ok(size) => context.set_register(reg, size), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, size); } InstructionType::StderrWrite => { let reg = instruction.arg(0); let input = context.get_register(instruction.arg(1)); + let size = try_runtime_error!( + io::stderr_write(&self.state, process, input), + self, + process, + context, + index + ); - match io::stderr_write(&self.state, process, input)? { - Ok(size) => context.set_register(reg, size), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, size); } InstructionType::StdoutFlush => { let reg = instruction.arg(0); + let obj = try_runtime_error!( + io::stdout_flush(&self.state), + self, + process, + context, + index + ); - match io::stdout_flush(&self.state) { - Ok(obj) => context.set_register(reg, obj), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, obj); } InstructionType::StderrFlush => { let reg = instruction.arg(0); + let obj = try_runtime_error!( + io::stderr_flush(&self.state), + self, + process, + context, + index + ); - match io::stderr_flush(&self.state) { - Ok(obj) => context.set_register(reg, obj), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, obj); } InstructionType::StdinRead => { let reg = instruction.arg(0); let buff = context.get_register(instruction.arg(1)); let max = context.get_register(instruction.arg(2)); + let obj = try_runtime_error!( + io::stdin_read(&self.state, process, buff, max), + self, + process, + context, + index + ); - match io::stdin_read(&self.state, process, buff, max)? { - Ok(obj) => context.set_register(reg, obj), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, obj); } InstructionType::FileOpen => { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); let mode = context.get_register(instruction.arg(2)); + let file = try_runtime_error!( + io::open_file(&self.state, process, path, mode), + self, + process, + context, + index + ); - match io::open_file(&self.state, process, path, mode)? { - Ok(file) => context.set_register(reg, file), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, file); } InstructionType::FileWrite => { let reg = instruction.arg(0); let file = context.get_register(instruction.arg(1)); let input = context.get_register(instruction.arg(2)); + let size = try_runtime_error!( + io::write_file(&self.state, process, file, input), + self, + process, + context, + index + ); - match io::write_file(&self.state, process, file, input)? { - Ok(size) => context.set_register(reg, size), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - } + context.set_register(reg, size); } InstructionType::FileRead => { let reg = instruction.arg(0); let file = context.get_register(instruction.arg(1)); let buff = context.get_register(instruction.arg(2)); let max = context.get_register(instruction.arg(3)); + let obj = try_runtime_error!( + io::read_file(&self.state, process, file, buff, max), + self, + process, + context, + index + ); - match io::read_file(&self.state, process, file, buff, max)? - { - Ok(obj) => context.set_register(reg, obj), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, obj); } InstructionType::FileFlush => { let file = context.get_register(instruction.arg(0)); - if let Err(err) = io::flush_file(&self.state, file)? { - throw_io_error!(self, process, err, context, index); - } + try_runtime_error!( + io::flush_file(&self.state, file), + self, + process, + context, + index + ); } InstructionType::FileSize => { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); + let size = try_runtime_error!( + io::file_size(&self.state, process, path), + self, + process, + context, + index + ); - match io::file_size(&self.state, process, path)? { - Ok(size) => context.set_register(reg, size), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, size); } InstructionType::FileSeek => { let reg = instruction.arg(0); let file = context.get_register(instruction.arg(1)); let offset = context.get_register(instruction.arg(2)); + let cursor = try_runtime_error!( + io::seek_file(&self.state, process, file, offset), + self, + process, + context, + index + ); - match io::seek_file(&self.state, process, file, offset)? { - Ok(cursor) => context.set_register(reg, cursor), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - } + context.set_register(reg, cursor); } InstructionType::LoadModule => { let reg = instruction.arg(0); @@ -1187,13 +1246,15 @@ impl Machine { InstructionType::FileRemove => { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); + let obj = try_runtime_error!( + io::remove_file(&self.state, path), + self, + process, + context, + index + ); - match io::remove_file(&self.state, path)? { - Ok(obj) => context.set_register(reg, obj), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, obj); } InstructionType::Panic => { let msg = context.get_register(instruction.arg(0)); @@ -1228,18 +1289,26 @@ impl Machine { let reg = instruction.arg(0); let src = context.get_register(instruction.arg(1)); let dst = context.get_register(instruction.arg(2)); + let obj = try_runtime_error!( + io::copy_file(&self.state, process, src, dst), + self, + process, + context, + index + ); - match io::copy_file(&self.state, process, src, dst)? { - Ok(obj) => context.set_register(reg, obj), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - } + context.set_register(reg, obj); } InstructionType::FileType => { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); - let res = io::file_type(path)?; + let res = try_runtime_error!( + io::file_type(path), + self, + process, + context, + index + ); context.set_register(reg, res); } @@ -1247,13 +1316,15 @@ impl Machine { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); let kind = context.get_register(instruction.arg(2)); + let time = try_runtime_error!( + io::file_time(&self.state, process, path, kind), + self, + process, + context, + index + ); - match io::file_time(&self.state, process, path, kind) { - Ok(time) => context.set_register(reg, time), - Err(err) => throw_error_message!( - self, process, err, context, index - ), - }; + context.set_register(reg, time); } InstructionType::TimeSystem => { let reg = instruction.arg(0); @@ -1277,36 +1348,42 @@ impl Machine { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); let recursive = context.get_register(instruction.arg(2)); + let res = try_runtime_error!( + io::create_directory(&self.state, path, recursive), + self, + process, + context, + index + ); - match io::create_directory(&self.state, path, recursive)? { - Ok(res) => context.set_register(reg, res), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, res); } InstructionType::DirectoryRemove => { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); let recursive = context.get_register(instruction.arg(2)); + let res = try_runtime_error!( + io::remove_directory(&self.state, path, recursive), + self, + process, + context, + index + ); - match io::remove_directory(&self.state, path, recursive)? { - Ok(res) => context.set_register(reg, res), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, res); } InstructionType::DirectoryList => { let reg = instruction.arg(0); let path = context.get_register(instruction.arg(1)); + let array = try_runtime_error!( + io::list_directory(&self.state, process, path), + self, + process, + context, + index + ); - match io::list_directory(&self.state, process, path) { - Ok(array) => context.set_register(reg, array), - Err(err) => throw_error_message!( - self, process, err, context, index - ), - }; + context.set_register(reg, array); } InstructionType::StringConcat => { let reg = instruction.arg(0); @@ -1490,24 +1567,28 @@ impl Machine { } InstructionType::EnvGetWorkingDirectory => { let reg = instruction.arg(0); + let path = try_runtime_error!( + env::working_directory(&self.state, process), + self, + process, + context, + index + ); - match env::working_directory(&self.state, process) { - Ok(path) => context.set_register(reg, path), - Err(err) => { - throw_io_error!(self, process, err, context, index); - } - }; + context.set_register(reg, path); } InstructionType::EnvSetWorkingDirectory => { let reg = instruction.arg(0); let dir = context.get_register(instruction.arg(1)); + let res = try_runtime_error!( + env::set_working_directory(dir), + self, + process, + context, + index + ); - match env::set_working_directory(dir)? { - Ok(dir) => context.set_register(reg, dir), - Err(err) => { - throw_io_error!(self, process, err, context, index) - } - }; + context.set_register(reg, res); } InstructionType::EnvArguments => { let reg = instruction.arg(0); @@ -1705,30 +1786,221 @@ impl Machine { let reg = instruction.arg(0); let val = context.get_register(instruction.arg(1)); let rdx = context.get_register(instruction.arg(2)); + let value = try_runtime_error!( + string::to_integer(&self.state, process, val, rdx), + self, + process, + context, + index + ); - match string::to_integer(&self.state, process, val, rdx)? { - Ok(value) => context.set_register(reg, value), - Err(err) => throw_error_message!( - self, process, err, context, index - ), - }; + context.set_register(reg, value); } InstructionType::StringToFloat => { let reg = instruction.arg(0); let val = context.get_register(instruction.arg(1)); + let value = try_runtime_error!( + string::to_float(&self.state, process, val), + self, + process, + context, + index + ); - match string::to_float(&self.state, process, val)? { - Ok(value) => context.set_register(reg, value), - Err(err) => throw_error_message!( - self, process, err, context, index - ), - }; + context.set_register(reg, value); } InstructionType::FloatToBits => { let reg = instruction.arg(0); let val = context.get_register(instruction.arg(1)); let res = float::to_bits(&self.state, process, val)?; + context.set_register(reg, res); + } + InstructionType::SocketCreate => { + let reg = instruction.arg(0); + let domain = context.get_register(instruction.arg(1)); + let kind = context.get_register(instruction.arg(2)); + let res = try_runtime_error!( + socket::create(&self.state, process, domain, kind), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketWrite => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let input = context.get_register(instruction.arg(2)); + let size = try_runtime_error!( + socket::write(&self.state, process, sock, input), + self, + process, + context, + index + ); + + context.set_register(reg, size); + } + InstructionType::SocketRead => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let buff = context.get_register(instruction.arg(2)); + let amount = context.get_register(instruction.arg(3)); + let size = try_runtime_error!( + socket::read(&self.state, process, sock, buff, amount), + self, + process, + context, + index + ); + + context.set_register(reg, size); + } + InstructionType::SocketAccept => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let res = try_runtime_error!( + socket::accept(&self.state, process, sock), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketReceiveFrom => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let buff = context.get_register(instruction.arg(2)); + let amount = context.get_register(instruction.arg(3)); + let res = try_runtime_error!( + socket::receive_from( + &self.state, + process, + sock, + buff, + amount + ), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketSendTo => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let buff = context.get_register(instruction.arg(2)); + let addr = context.get_register(instruction.arg(3)); + let port = context.get_register(instruction.arg(4)); + let res = try_runtime_error!( + socket::send_to( + &self.state, + process, + sock, + buff, + addr, + port + ), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketAddress => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let kind = context.get_register(instruction.arg(2)); + let res = try_runtime_error!( + socket::address(&self.state, process, sock, kind), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketGetOption => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let opt = context.get_register(instruction.arg(2)); + let res = try_runtime_error!( + socket::get_option(&self.state, process, sock, opt), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketSetOption => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let opt = context.get_register(instruction.arg(2)); + let val = context.get_register(instruction.arg(3)); + let res = try_runtime_error!( + socket::set_option(&self.state, sock, opt, val), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketBind => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let addr = context.get_register(instruction.arg(2)); + let port = context.get_register(instruction.arg(3)); + let res = try_runtime_error!( + socket::bind(&self.state, process, sock, addr, port), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketListen => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let backlog = context.get_register(instruction.arg(2)); + let res = try_runtime_error!( + socket::listen(sock, backlog), + self, + process, + context, + index + ); + + context.set_register(reg, res); + } + InstructionType::SocketConnect => { + let reg = instruction.arg(0); + let sock = context.get_register(instruction.arg(1)); + let addr = context.get_register(instruction.arg(2)); + let port = context.get_register(instruction.arg(3)); + let res = try_runtime_error!( + socket::connect(&self.state, process, sock, addr, port), + self, + process, + context, + index + ); + context.set_register(reg, res); } }; diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index ced423100..701369640 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -12,6 +12,7 @@ pub mod machine; pub mod module; pub mod object; pub mod process; +pub mod socket; pub mod state; pub mod string; pub mod test; diff --git a/vm/src/vm/object.rs b/vm/src/vm/object.rs index 321e7dda9..fd0de6ba8 100644 --- a/vm/src/vm/object.rs +++ b/vm/src/vm/object.rs @@ -60,6 +60,8 @@ pub fn prototype_for_identifier( 13 => state.function_prototype, 14 => state.pointer_prototype, 15 => state.process_prototype, + 16 => state.socket_prototype, + 17 => state.unix_socket_prototype, _ => return Err(format!("Invalid prototype identifier: {}", id_int)), }; diff --git a/vm/src/vm/process.rs b/vm/src/vm/process.rs index b0db2b657..df4153d33 100644 --- a/vm/src/vm/process.rs +++ b/vm/src/vm/process.rs @@ -1,5 +1,6 @@ //! VM functions for working with Inko processes. use crate::block::Block; +use crate::duration; use crate::immix::copy_object::CopyObject; use crate::object_pointer::ObjectPointer; use crate::object_value; @@ -258,22 +259,7 @@ pub fn unwind_until_defining_scope(process: &RcProcess) { pub fn optional_timeout( pointer: ObjectPointer, ) -> Result, String> { - let time = pointer.float_value()?; - - if time < 0.0 { - return Err(format!("{} is an invalid timeout value", time)); - } - - let result = if time == 0.0 { - None - } else { - let secs = time.trunc() as u64; - let nanos = (time.fract() * 1_000_000_000.0) as u32; - - Some(Duration::new(secs, nanos)) - }; - - Ok(result) + duration::from_f64(pointer.float_value()?) } /// Attempts to reschedule the given process after it was sent a message. diff --git a/vm/src/vm/socket.rs b/vm/src/vm/socket.rs new file mode 100644 index 000000000..a2f5bf2f2 --- /dev/null +++ b/vm/src/vm/socket.rs @@ -0,0 +1,359 @@ +use crate::network_poller::interest::Interest; +use crate::object_pointer::ObjectPointer; +use crate::object_value; +use crate::process::RcProcess; +use crate::runtime_error::RuntimeError; +use crate::socket::Socket; +use crate::vm::io; +use crate::vm::state::RcState; + +macro_rules! allocate_bool { + ($state: expr, $expr:expr) => { + if $expr { + $state.true_object + } else { + $state.false_object + } + }; +} + +macro_rules! allocate_usize { + ($state:expr, $process:expr, $expr:expr) => { + $process.allocate_usize($expr, $state.integer_prototype) + }; +} + +macro_rules! allocate_f64 { + ($state:expr, $process:expr, $expr:expr) => { + $process.allocate(object_value::float($expr), $state.float_prototype) + }; +} + +macro_rules! to_u32 { + ($expr:expr) => { + $expr.u32_value()? + }; +} + +const TTL: i64 = 0; +const ONLY_V6: i64 = 1; +const NODELAY: i64 = 2; +const BROADCAST: i64 = 3; +const LINGER: i64 = 4; +const RECV_SIZE: i64 = 5; +const SEND_SIZE: i64 = 6; +const KEEPALIVE: i64 = 7; +const MULTICAST_LOOP_V4: i64 = 8; +const MULTICAST_LOOP_V6: i64 = 9; +const MULTICAST_TTL_V4: i64 = 10; +const MULTICAST_HOPS_V6: i64 = 11; +const MULTICAST_IF_V4: i64 = 12; +const MULTICAST_IF_V6: i64 = 13; +const UNICAST_HOPS_V6: i64 = 14; +const REUSE_ADDRESS: i64 = 15; +const REUSE_PORT: i64 = 16; + +pub fn create( + state: &RcState, + process: &RcProcess, + domain_ptr: ObjectPointer, + kind_ptr: ObjectPointer, +) -> Result { + let domain = domain_ptr.u8_value()?; + let kind = kind_ptr.u8_value()?; + let socket = Socket::new(domain, kind)?; + let proto = if socket.is_unix() { + state.unix_socket_prototype + } else { + state.socket_prototype + }; + + let socket_ptr = process.allocate(object_value::socket(socket), proto); + + Ok(socket_ptr) +} + +pub fn write( + state: &RcState, + process: &RcProcess, + socket_ptr: ObjectPointer, + input_ptr: ObjectPointer, +) -> Result { + let sock = socket_ptr.socket_value_mut()?; + + socket_result( + io::io_write(state, process, sock, input_ptr), + state, + process, + sock, + Interest::Write, + ) +} + +pub fn read( + state: &RcState, + process: &RcProcess, + socket_ptr: ObjectPointer, + buff_ptr: ObjectPointer, + amount_ptr: ObjectPointer, +) -> Result { + let sock = socket_ptr.socket_value_mut()?; + let buffer = buff_ptr.byte_array_value_mut()?; + + socket_result( + io::io_read(state, process, sock, buffer, amount_ptr), + state, + process, + sock, + Interest::Read, + ) +} + +pub fn listen( + socket_ptr: ObjectPointer, + backlog_ptr: ObjectPointer, +) -> Result { + let sock = socket_ptr.socket_value()?; + let backlog = backlog_ptr.i32_value()?; + + sock.listen(backlog)?; + + Ok(backlog_ptr) +} + +pub fn bind( + state: &RcState, + process: &RcProcess, + socket_ptr: ObjectPointer, + addr_ptr: ObjectPointer, + port_ptr: ObjectPointer, +) -> Result { + let sock = socket_ptr.socket_value_mut()?; + let addr = addr_ptr.string_value()?; + let port = port_ptr.u16_value()?; + let result = sock.bind(addr, port).map(|_| state.nil_object); + + socket_result(result, state, process, sock, Interest::Read) +} + +pub fn connect( + state: &RcState, + process: &RcProcess, + socket_ptr: ObjectPointer, + addr_ptr: ObjectPointer, + port_ptr: ObjectPointer, +) -> Result { + let sock = socket_ptr.socket_value_mut()?; + let addr = addr_ptr.string_value()?; + let port = port_ptr.u16_value()?; + let result = sock.connect(addr, port).map(|_| state.nil_object); + + socket_result(result, state, process, sock, Interest::Write) +} + +pub fn accept( + state: &RcState, + process: &RcProcess, + socket_ptr: ObjectPointer, +) -> Result { + let sock = socket_ptr.socket_value_mut()?; + let proto = if sock.is_unix() { + state.unix_socket_prototype + } else { + state.socket_prototype + }; + + let result = sock + .accept() + .map(|sock| process.allocate(object_value::socket(sock), proto)); + + socket_result(result, state, process, sock, Interest::Read) +} + +pub fn receive_from( + state: &RcState, + process: &RcProcess, + socket_ptr: ObjectPointer, + buffer_ptr: ObjectPointer, + amount_ptr: ObjectPointer, +) -> Result { + let sock = socket_ptr.socket_value_mut()?; + let mut buffer = buffer_ptr.byte_array_value_mut()?; + let amount = amount_ptr.usize_value()?; + let result = sock + .recv_from(&mut buffer, amount) + .map(|(addr, port)| allocate_address_pair(state, process, addr, port)); + + socket_result(result, state, process, sock, Interest::Read) +} + +pub fn send_to( + state: &RcState, + process: &RcProcess, + socket_pointer: ObjectPointer, + buffer_pointer: ObjectPointer, + address_pointer: ObjectPointer, + port_pointer: ObjectPointer, +) -> Result { + let buffer = io::buffer_to_write(&buffer_pointer)?; + let sock = socket_pointer.socket_value_mut()?; + let address = address_pointer.string_value()?; + let port = port_pointer.u16_value()?; + let result = sock + .send_to(buffer, address, port) + .map(|bytes| process.allocate_usize(bytes, state.integer_prototype)); + + socket_result(result, state, process, sock, Interest::Write) +} + +pub fn address( + state: &RcState, + process: &RcProcess, + socket_pointer: ObjectPointer, + kind_pointer: ObjectPointer, +) -> Result { + let sock = socket_pointer.socket_value()?; + let kind = kind_pointer.integer_value()?; + + match kind { + 0 => sock.local_address(), + 1 => sock.peer_address(), + _ => Err(RuntimeError::Panic(format!( + "{} is not a valid type of socket address", + kind + ))), + } + .map(|(addr, port)| allocate_address_pair(state, process, addr, port)) +} + +pub fn set_option( + state: &RcState, + socket_pointer: ObjectPointer, + option_pointer: ObjectPointer, + val_pointer: ObjectPointer, +) -> Result { + let sock = socket_pointer.socket_value()?; + let option = option_pointer.integer_value()?; + + match option { + TTL => sock.set_ttl(to_u32!(val_pointer))?, + ONLY_V6 => sock.set_only_v6(is_true!(state, val_pointer))?, + NODELAY => sock.set_nodelay(is_true!(state, val_pointer))?, + BROADCAST => sock.set_broadcast(is_true!(state, val_pointer))?, + LINGER => sock.set_linger(val_pointer.float_value()?)?, + RECV_SIZE => sock.set_recv_buffer_size(val_pointer.usize_value()?)?, + SEND_SIZE => sock.set_send_buffer_size(val_pointer.usize_value()?)?, + KEEPALIVE => sock.set_keepalive(val_pointer.float_value()?)?, + MULTICAST_LOOP_V4 => { + sock.set_multicast_loop_v4(is_true!(state, val_pointer))? + } + MULTICAST_LOOP_V6 => { + sock.set_multicast_loop_v6(is_true!(state, val_pointer))? + } + MULTICAST_TTL_V4 => sock.set_multicast_ttl_v4(to_u32!(val_pointer))?, + MULTICAST_HOPS_V6 => { + sock.set_multicast_hops_v6(to_u32!(val_pointer))? + } + MULTICAST_IF_V4 => { + sock.set_multicast_if_v4(val_pointer.string_value()?)? + } + MULTICAST_IF_V6 => sock.set_multicast_if_v6(to_u32!(val_pointer))?, + UNICAST_HOPS_V6 => sock.set_unicast_hops_v6(to_u32!(val_pointer))?, + REUSE_ADDRESS => { + sock.set_reuse_address(is_true!(state, val_pointer))? + } + REUSE_PORT => sock.set_reuse_port(is_true!(state, val_pointer))?, + _ => { + return Err(RuntimeError::Panic(format!( + "The sock option {} is not valid", + option + ))); + } + }; + + Ok(val_pointer) +} + +pub fn get_option( + state: &RcState, + process: &RcProcess, + socket_pointer: ObjectPointer, + option_pointer: ObjectPointer, +) -> Result { + let sock = socket_pointer.socket_value()?; + let option = option_pointer.integer_value()?; + let result = match option { + TTL => allocate_usize!(state, process, sock.ttl()?), + ONLY_V6 => allocate_bool!(state, sock.only_v6()?), + NODELAY => allocate_bool!(state, sock.nodelay()?), + BROADCAST => allocate_bool!(state, sock.broadcast()?), + LINGER => allocate_f64!(state, process, sock.linger()?), + RECV_SIZE => allocate_usize!(state, process, sock.recv_buffer_size()?), + SEND_SIZE => allocate_usize!(state, process, sock.send_buffer_size()?), + KEEPALIVE => allocate_f64!(state, process, sock.keepalive()?), + MULTICAST_LOOP_V4 => allocate_bool!(state, sock.multicast_loop_v4()?), + MULTICAST_LOOP_V6 => allocate_bool!(state, sock.multicast_loop_v6()?), + MULTICAST_TTL_V4 => { + allocate_usize!(state, process, sock.multicast_ttl_v4()?) + } + MULTICAST_HOPS_V6 => { + allocate_usize!(state, process, sock.multicast_hops_v6()?) + } + MULTICAST_IF_V4 => process.allocate( + object_value::string(sock.multicast_if_v4()?), + state.string_prototype, + ), + MULTICAST_IF_V6 => { + allocate_usize!(state, process, sock.multicast_if_v6()?) + } + UNICAST_HOPS_V6 => { + allocate_usize!(state, process, sock.unicast_hops_v6()?) + } + REUSE_ADDRESS => allocate_bool!(state, sock.reuse_address()?), + REUSE_PORT => allocate_bool!(state, sock.reuse_port()?), + _ => { + return Err(RuntimeError::Panic(format!( + "The sock option {} is not valid", + option + ))); + } + }; + + Ok(result) +} + +fn allocate_address_pair( + state: &RcState, + process: &RcProcess, + addr: String, + port: i64, +) -> ObjectPointer { + let addr_ptr = + process.allocate(object_value::string(addr), state.string_prototype); + + let port_ptr = ObjectPointer::integer(port); + + process.allocate( + object_value::array(vec![addr_ptr, port_ptr]), + state.array_prototype, + ) +} + +fn socket_result( + result: Result, + state: &RcState, + process: &RcProcess, + socket: &mut Socket, + interest: Interest, +) -> Result { + match result { + Ok(res) => Ok(res), + Err(error) => { + if error.should_poll() { + socket.register(process, &state.network_poller, interest)?; + } + + Err(error) + } + } +} diff --git a/vm/src/vm/state.rs b/vm/src/vm/state.rs index 6cbba9caa..857abc627 100644 --- a/vm/src/vm/state.rs +++ b/vm/src/vm/state.rs @@ -12,6 +12,7 @@ use crate::immix::copy_object::CopyObject; use crate::immix::global_allocator::{GlobalAllocator, RcGlobalAllocator}; use crate::immix::permanent_allocator::PermanentAllocator; use crate::immutable_string::ImmutableString; +use crate::network_poller::NetworkPoller; use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::scheduler::generic_pool::GenericPool; @@ -143,6 +144,12 @@ pub struct State { /// The prototype to use for pointers to C variables. pub pointer_prototype: ObjectPointer, + /// The prototype to use for regular sockets. + pub socket_prototype: ObjectPointer, + + /// The prototype to use for Unix domain sockets. + pub unix_socket_prototype: ObjectPointer, + /// The commandline arguments passed to an Inko program. pub arguments: Vec, @@ -151,6 +158,9 @@ pub struct State { /// This field defaults to a null pointer. Reading and writing this field /// should be done using atomic operations. pub default_panic_handler: ObjectPointer, + + /// The system polling mechanism to use for polling non-blocking sockets. + pub network_poller: NetworkPoller, } impl RefUnwindSafe for State {} @@ -185,6 +195,8 @@ impl State { let function_prototype = perm_alloc.allocate_empty(); let pointer_prototype = perm_alloc.allocate_empty(); let process_prototype = perm_alloc.allocate_empty(); + let socket_prototype = perm_alloc.allocate_empty(); + let unix_socket_prototype = perm_alloc.allocate_empty(); { top_level.set_prototype(object_proto); @@ -208,6 +220,8 @@ impl State { function_prototype.set_prototype(object_proto); pointer_prototype.set_prototype(object_proto); process_prototype.set_prototype(object_proto); + socket_prototype.set_prototype(object_proto); + unix_socket_prototype.set_prototype(object_proto); } let gc_pool = GenericPool::new("GC".to_string(), config.gc_threads); @@ -251,6 +265,9 @@ impl State { function_prototype, pointer_prototype, process_prototype, + socket_prototype, + unix_socket_prototype, + network_poller: NetworkPoller::new(), }; for argument in arguments { diff --git a/vm/src/vm/string.rs b/vm/src/vm/string.rs index 1aefdc82c..4800d6702 100644 --- a/vm/src/vm/string.rs +++ b/vm/src/vm/string.rs @@ -2,14 +2,11 @@ use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::process::RcProcess; +use crate::runtime_error::RuntimeError; use crate::slicing; use crate::vm::state::RcState; use num_bigint::BigInt; -/// The result of a string conversion. The OK value is the value converted to, -/// the Err value is an error message to thrown in the VM. -type ConversionResult = Result; - pub fn to_lower( state: &RcState, process: &RcProcess, @@ -163,12 +160,14 @@ pub fn to_integer( process: &RcProcess, str_ptr: ObjectPointer, radix_ptr: ObjectPointer, -) -> Result { +) -> Result { let string = str_ptr.string_value()?; let radix = radix_ptr.integer_value()?; if radix < 2 || radix > 36 { - return Err("radix must be between 2 and 32, not {}".to_string()); + return Err(RuntimeError::Panic( + "radix must be between 2 and 32, not {}".to_string(), + )); } let int_ptr = if let Ok(value) = i64::from_str_radix(string, radix as u32) { @@ -176,13 +175,13 @@ pub fn to_integer( } else if let Ok(val) = string.parse::() { process.allocate(object_value::bigint(val), state.integer_prototype) } else { - return Ok(Err(format!( + return Err(RuntimeError::Exception(format!( "{:?} can not be converted to an Integer", string ))); }; - Ok(Ok(int_ptr)) + Ok(int_ptr) } /// Converts a string to a float. @@ -190,15 +189,18 @@ pub fn to_float( state: &RcState, process: &RcProcess, str_ptr: ObjectPointer, -) -> Result { +) -> Result { let string = str_ptr.string_value()?; if let Ok(value) = string.parse::() { let pointer = process.allocate(object_value::float(value), state.float_prototype); - Ok(Ok(pointer)) + Ok(pointer) } else { - Ok(Err(format!("{:?} can not be converted to a Float", string))) + Err(RuntimeError::Exception(format!( + "{:?} can not be converted to a Float", + string + ))) } } diff --git a/vm/src/vm/time.rs b/vm/src/vm/time.rs index dc9425ba2..778c5a11a 100644 --- a/vm/src/vm/time.rs +++ b/vm/src/vm/time.rs @@ -1,5 +1,6 @@ //! VM functions for working with time objects. use crate::date_time::DateTime; +use crate::duration; use crate::object_pointer::ObjectPointer; use crate::object_value; use crate::process::RcProcess; @@ -7,8 +8,7 @@ use crate::vm::state::RcState; pub fn monotonic(state: &RcState, process: &RcProcess) -> ObjectPointer { let duration = state.start_time.elapsed(); - let seconds = duration.as_secs() as f64 - + (f64::from(duration.subsec_nanos()) / 1_000_000_000.0); + let seconds = duration::to_f64(Some(duration)); process.allocate(object_value::float(seconds), state.float_prototype) }