From 638384a91c31215aed7eb28b2887d145dc3e6bb7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Sat, 13 Jul 2024 16:13:04 +0200 Subject: [PATCH] WIP: TLS sockets This commit also changes std.net.socket.SocketAddress such that it no longer stores IP address as a String, instead storing them as a proper IpAddress instance. In addition, SocketAddress.new is removed in favour of creating an instance of the class directly, as all fields are (and will remain) public such that a dedicated "new" method is redundant. This fixes https://github.com/inko-lang/inko/issues/329. Changelog: added --- Cargo.lock | 422 ++++++++++++++++++------ compiler/src/linker.rs | 8 +- rt/Cargo.toml | 13 + rt/src/network_poller.rs | 11 + rt/src/network_poller/epoll.rs | 1 + rt/src/network_poller/kqueue.rs | 24 +- rt/src/result.rs | 7 +- rt/src/runtime.rs | 6 + rt/src/runtime/env.rs | 20 -- rt/src/runtime/socket.rs | 199 ++++++++++- rt/src/socket.rs | 55 ++- std/src/std/env.inko | 15 +- std/src/std/fs/path.inko | 46 ++- std/src/std/io.inko | 14 + std/src/std/net/socket.inko | 100 ++++-- std/src/std/string.inko | 26 +- std/test/compiler/test_diagnostics.inko | 4 +- std/test/std/fs/test_path.inko | 33 +- std/test/std/net/test_socket.inko | 104 +++--- std/test/std/test_env.inko | 14 + std/test/std/test_io.inko | 6 +- std/test/std/test_optparse.inko | 6 +- std/test/std/test_string.inko | 36 +- 23 files changed, 887 insertions(+), 283 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3b6f89fa..e27643301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -44,11 +44,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -61,9 +67,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -73,9 +79,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake3" @@ -101,11 +107,23 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + [[package]] name = "cc" -version = "1.0.95" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" @@ -113,6 +131,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compiler" version = "0.15.0" @@ -146,11 +174,27 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -166,15 +210,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" @@ -184,9 +228,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", @@ -206,9 +250,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -240,9 +284,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -251,9 +295,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "idna" @@ -302,23 +346,43 @@ dependencies = [ "syn", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "llvm-sys" @@ -336,30 +400,58 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" -version = "0.32.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -370,6 +462,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -384,9 +482,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -476,15 +574,18 @@ dependencies = [ "libc", "rand", "rustix", + "rustls", + "rustls-pemfile", + "rustls-platform-verifier", "socket2", "unicode-segmentation", ] [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" @@ -492,7 +593,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -501,11 +602,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -513,28 +615,120 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-platform-verifier" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3beb939bcd33c269f4bf946cc829fcd336370267c4a927ac0399c84a3151a1" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "num-bigint", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "similar" @@ -558,9 +752,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys", @@ -574,15 +768,15 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", @@ -591,9 +785,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" dependencies = [ "filetime", "libc", @@ -601,18 +795,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -621,9 +815,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -667,9 +861,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "untrusted" @@ -679,31 +873,40 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.7" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64", "log", "once_cell", "rustls", "rustls-pki-types", - "rustls-webpki", "url", "webpki-roots", ] [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -712,13 +915,44 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" @@ -730,9 +964,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -746,54 +980,54 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/compiler/src/linker.rs b/compiler/src/linker.rs index 39d20ceca..6ee66e134 100644 --- a/compiler/src/linker.rs +++ b/compiler/src/linker.rs @@ -256,7 +256,13 @@ pub(crate) fn link( cmd.arg("-lm"); cmd.arg("-lpthread"); } - _ => {} + OperatingSystem::Mac => { + // This is needed for TLS support. + for name in ["Security", "CoreFoundation"] { + cmd.arg("-framework"); + cmd.arg(name); + } + } } let mut static_linking = state.config.static_linking; diff --git a/rt/Cargo.toml b/rt/Cargo.toml index 9853cb5aa..d4dd2d134 100644 --- a/rt/Cargo.toml +++ b/rt/Cargo.toml @@ -23,6 +23,19 @@ unicode-segmentation = "^1.10" backtrace = "^0.3" rustix = { version = "^0.38", features = ["fs", "mm", "param", "process", "net", "std", "time", "event"], default-features = false } +# The dependencies needed for TLS support. +# +# We use ring instead of the default aws-lc-sys because: +# +# 1. aws-lc-sys requires cmake to be installed when building on FreeBSD (and +# potentially other platforms), as aws-lc-sys only provides generated +# bindings for a limited set of platforms +# 2. aws-lc-sys increases compile times quite a bit +# 3. We don't care about FIPS compliance at the time of writing +rustls = { version = "^0.23", features = ["ring", "tls12", "std"], default-features = false } +rustls-platform-verifier = "^0.3" +rustls-pemfile = "^2.1" + [dependencies.socket2] version = "^0.5" features = ["all"] diff --git a/rt/src/network_poller.rs b/rt/src/network_poller.rs index e28c278d1..c5221edb2 100644 --- a/rt/src/network_poller.rs +++ b/rt/src/network_poller.rs @@ -28,6 +28,17 @@ pub(crate) type NetworkPoller = sys::Poller; pub(crate) enum Interest { Read, Write, + ReadWrite, +} + +impl Interest { + pub(crate) fn new(read: bool, write: bool) -> Interest { + match (read, write) { + (true, true) => Interest::ReadWrite, + (false, true) => Interest::Write, + _ => Interest::Read, + } + } } /// A thread that polls a poller and reschedules processes. diff --git a/rt/src/network_poller/epoll.rs b/rt/src/network_poller/epoll.rs index 6fecea8d8..c501a06b7 100644 --- a/rt/src/network_poller/epoll.rs +++ b/rt/src/network_poller/epoll.rs @@ -11,6 +11,7 @@ fn flags_for(interest: Interest) -> EventFlags { let flags = match interest { Interest::Read => EventFlags::IN, Interest::Write => EventFlags::OUT, + Interest::ReadWrite => EventFlags::IN | EventFlags::OUT, }; flags | EventFlags::ET | EventFlags::ONESHOT diff --git a/rt/src/network_poller/kqueue.rs b/rt/src/network_poller/kqueue.rs index 4d3790277..17e006dbf 100644 --- a/rt/src/network_poller/kqueue.rs +++ b/rt/src/network_poller/kqueue.rs @@ -40,18 +40,24 @@ impl Poller { source: impl AsFd, interest: Interest, ) { - let fd = source.as_fd().as_raw_fd(); - let (add, del) = match interest { - Interest::Read => (EventFilter::Read(fd), EventFilter::Write(fd)), - Interest::Write => (EventFilter::Write(fd), EventFilter::Read(fd)), - }; let id = process.identifier() as isize; + let fd = source.as_fd().as_raw_fd(); let flags = EventFlags::CLEAR | EventFlags::ONESHOT | EventFlags::RECEIPT; - let events = [ - Event::new(add, EventFlags::ADD | flags, id), - Event::new(del, EventFlags::DELETE, 0), - ]; + let events = match interest { + Interest::Read => [ + Event::new(EventFilter::Read(fd), EventFlags::ADD | flags, id), + Event::new(EventFilter::Write(fd), EventFlags::DELETE, 0), + ], + Interest::Write => [ + Event::new(EventFilter::Write(fd), EventFlags::ADD | flags, id), + Event::new(EventFilter::Read(fd), EventFlags::DELETE, 0), + ], + Interest::ReadWrite => [ + Event::new(EventFilter::Write(fd), EventFlags::ADD | flags, id), + Event::new(EventFilter::Read(fd), EventFlags::ADD | flags, id), + ], + }; self.apply(&events); } diff --git a/rt/src/result.rs b/rt/src/result.rs index d72a8b71d..c368d8370 100644 --- a/rt/src/result.rs +++ b/rt/src/result.rs @@ -13,7 +13,10 @@ pub(crate) fn error_to_int(error: io::Error) -> i64 { // raw_os_error() above returns a None. Errno::TIMEDOUT.raw_os_error() } else { - -1 + match error.kind() { + io::ErrorKind::InvalidData => -2, + _ => -1, + } }; code as i64 @@ -60,7 +63,7 @@ impl Result { } pub(crate) fn io_error(error: io::Error) -> Result { - Self::error({ error_to_int(error) } as _) + Self::error(error_to_int(error) as _) } } diff --git a/rt/src/runtime.rs b/rt/src/runtime.rs index f027d53eb..fcaf9e44f 100644 --- a/rt/src/runtime.rs +++ b/rt/src/runtime.rs @@ -67,6 +67,12 @@ pub unsafe extern "system" fn inko_runtime_new( // does for us when compiling an executable. signal_sched::block_all(); + // Configure the TLS provider. This must be done once before we start the + // program. + rustls::crypto::ring::default_provider() + .install_default() + .expect("failed to set up the default TLS cryptography provider"); + Box::into_raw(Box::new(Runtime::new(&*counts, args))) } diff --git a/rt/src/runtime/env.rs b/rt/src/runtime/env.rs index dfb9dbd51..1f8bbb43f 100644 --- a/rt/src/runtime/env.rs +++ b/rt/src/runtime/env.rs @@ -40,26 +40,6 @@ pub unsafe extern "system" fn inko_env_size(state: *const State) -> i64 { (*state).environment.len() as _ } -#[no_mangle] -pub unsafe extern "system" fn inko_env_home_directory( - state: *const State, -) -> InkoResult { - let state = &*state; - - // Rather than performing all sorts of magical incantations to get the home - // directory, we're just going to require that HOME is set. - // - // If the home is explicitly set to an empty string we still ignore it, - // because there's no scenario in which Some("") is useful. - state - .environment - .get("HOME") - .filter(|&path| !path.is_empty()) - .cloned() - .map(|v| InkoResult::ok(InkoString::alloc(state.string_class, v) as _)) - .unwrap_or_else(InkoResult::none) -} - #[no_mangle] pub unsafe extern "system" fn inko_env_temp_directory( state: *const State, diff --git a/rt/src/runtime/socket.rs b/rt/src/runtime/socket.rs index 496b51200..fd84cfa11 100644 --- a/rt/src/runtime/socket.rs +++ b/rt/src/runtime/socket.rs @@ -4,10 +4,16 @@ use crate::network_poller::Interest; use crate::process::ProcessPointer; use crate::result::{error_to_int, Result}; use crate::scheduler::timeouts::Timeout; -use crate::socket::Socket; +use crate::socket::{read_from, Socket}; use crate::state::State; -use std::io::{self, Write}; +use rustls::pki_types::ServerName; +use rustls::{ClientConfig, ClientConnection, RootCertStore, Stream}; +use rustls_pemfile::certs; +use rustls_platform_verifier::tls_config; +use std::fs::File; +use std::io::{self, BufReader, Write}; use std::ptr::{drop_in_place, write}; +use std::sync::Arc; #[repr(C)] pub struct RawAddress { @@ -24,19 +30,13 @@ impl RawAddress { } } -fn blocking( +fn poll( state: &State, mut process: ProcessPointer, socket: &mut Socket, interest: Interest, deadline: i64, - mut func: impl FnMut(&mut Socket) -> io::Result, -) -> io::Result { - match func(socket) { - Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} - val => return val, - } - +) -> io::Result<()> { let poll_id = unsafe { process.thread() }.network_poller; // We must keep the process' state lock open until everything is registered, @@ -72,7 +72,24 @@ fn blocking( return Err(io::Error::from(io::ErrorKind::TimedOut)); } - func(socket) + Ok(()) +} + +fn blocking( + state: &State, + process: ProcessPointer, + socket: &mut Socket, + interest: Interest, + deadline: i64, + mut func: impl FnMut(&mut Socket) -> io::Result, +) -> io::Result { + match func(socket) { + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + poll(state, process, socket, interest, deadline) + .and_then(|_| func(socket)) + } + val => val, + } } #[no_mangle] @@ -127,7 +144,7 @@ pub unsafe extern "system" fn inko_socket_read( let state = &*state; blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| { - sock.read(&mut (*buffer).value, amount as usize) + read_from(sock, &mut (*buffer).value, amount as usize) }) .map(|size| Result::ok(size as _)) .unwrap_or_else(Result::io_error) @@ -349,3 +366,161 @@ pub unsafe extern "system" fn inko_socket_try_clone( pub unsafe extern "system" fn inko_socket_drop(socket: *mut Socket) { drop_in_place(socket); } + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_client_config_new() -> Result { + Result::ok(Arc::into_raw(Arc::new(tls_config())) as *mut _) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_client_config_with_certificate( + path: *const InkoString, +) -> Result { + let mut store = RootCertStore::empty(); + let mut reader = match File::open(InkoString::read(path)) { + Ok(f) => BufReader::new(f), + Err(e) => return Result::io_error(e), + }; + + for res in certs(&mut reader) { + match res { + // We don't want to expose a bunch of error messages/cases for the + // different reasons for a certificate being invalid, as it's not + // clear users actually care about that. As such, at least for the + // time being we just use a single opaque error for invalid + // certificates. + Ok(cert) => { + if store.add(cert).is_err() { + return Result::none(); + } + } + Err(e) => return Result::io_error(e), + } + } + + let conf = Arc::new( + ClientConfig::builder() + .with_root_certificates(store) + .with_no_client_auth(), + ); + + Result::ok(Arc::into_raw(conf) as *mut _) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_client_config_clone( + config: *const ClientConfig, +) -> *const ClientConfig { + Arc::increment_strong_count(config); + config +} + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_client_config_drop( + config: *const ClientConfig, +) { + drop(Arc::from_raw(config)); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_client_connection_new( + config: *const ClientConfig, + server: *const InkoString, +) -> Result { + let name = match ServerName::try_from(InkoString::read(server)) { + Ok(v) => v, + Err(_) => return Result::error(0 as _), + }; + + Arc::increment_strong_count(config); + + // ClientConnection::new() is fallible, but from the documentation and + // source code it's not at all clear under what circumstances it produces an + // error, and if such an error can be handled in a meaningful way. As such, + // we panic in the event of an error. + let con = ClientConnection::new(Arc::from_raw(config), name) + .expect("failed to set up the client connection"); + + Result::ok_boxed(con) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_client_connection_drop( + connection: *mut ClientConnection, +) { + drop(Box::from_raw(connection)); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_socket_write( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + connection: *mut ClientConnection, + data: *mut u8, + size: i64, + deadline: i64, +) -> Result { + let state = &*state; + let slice = std::slice::from_raw_parts(data, size as _); + let mut stream = Stream::new(&mut *connection, &mut *socket); + + loop { + match stream.write(slice) { + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + let interest = Interest::new( + stream.conn.wants_read(), + stream.conn.wants_write(), + ); + + if let Err(e) = + poll(state, process, stream.sock, interest, deadline) + { + return Result::io_error(e); + } + } + val => { + return val + .map(|v| Result::ok(v as _)) + .unwrap_or_else(Result::io_error); + } + } + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_tls_socket_read( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + connection: *mut ClientConnection, + buffer: *mut ByteArray, + amount: i64, + deadline: i64, +) -> Result { + let state = &*state; + let buf = &mut (*buffer).value; + let mut stream = Stream::new(&mut *connection, &mut *socket); + + loop { + match read_from(&mut stream, buf, amount as usize) { + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + let interest = Interest::new( + stream.conn.wants_read(), + stream.conn.wants_write(), + ); + + if let Err(e) = + poll(state, process, stream.sock, interest, deadline) + { + return Result::io_error(e); + } + } + val => { + return val + .map(|v| Result::ok(v as _)) + .unwrap_or_else(Result::io_error); + } + }; + } +} diff --git a/rt/src/socket.rs b/rt/src/socket.rs index 5f881cf23..c69400c61 100644 --- a/rt/src/socket.rs +++ b/rt/src/socket.rs @@ -84,12 +84,10 @@ fn socket_output_slice(buffer: &mut Vec, bytes: usize) -> &mut [u8] { unsafe { slice::from_raw_parts_mut(buffer.as_mut_ptr().add(len), bytes) } } -fn update_buffer_length_and_capacity(buffer: &mut Vec, read: usize) { +fn update_buffer_length(buffer: &mut Vec, read: usize) { unsafe { buffer.set_len(buffer.len() + read); } - - buffer.shrink_to_fit(); } fn socket_type(kind: i64) -> io::Result { @@ -105,6 +103,29 @@ fn socket_type(kind: i64) -> io::Result { } } +pub(crate) fn read_from( + reader: &mut R, + into: &mut Vec, + amount: usize, +) -> io::Result { + if amount > 0 { + // We don't use take(), because that only terminates if: + // + // 1. We hit EOF, or + // 2. We have read the desired number of bytes + // + // For files this is fine, but for sockets EOF is not triggered + // until the socket is closed; which is almost always too late. + let slice = socket_output_slice(into, amount); + let read = reader.read(slice)?; + + update_buffer_length(into, read); + Ok(read) + } else { + Ok(reader.read_to_end(into)?) + } +} + /// A nonblocking socket that can be registered with a `NetworkPoller`. /// /// When changing the layout of this type, don't forget to also update its @@ -260,29 +281,6 @@ impl Socket { }) } - pub(crate) fn read( - &self, - buffer: &mut Vec, - amount: usize, - ) -> io::Result { - if amount > 0 { - // We don't use take(), because that only terminates if: - // - // 1. We hit EOF, or - // 2. We have read the desired number of bytes - // - // For files this is fine, but for sockets EOF is not triggered - // until the socket is closed; which is almost always too late. - let slice = socket_output_slice(buffer, amount); - let read = self.inner.recv(unsafe { transmute(slice) })?; - - update_buffer_length_and_capacity(buffer, read); - Ok(read) - } else { - Ok((&self.inner).read_to_end(buffer)?) - } - } - pub(crate) fn recv_from( &self, buffer: &mut Vec, @@ -292,8 +290,7 @@ impl Socket { let (read, sockaddr) = self.inner.recv_from(unsafe { transmute(slice) })?; - update_buffer_length_and_capacity(buffer, read); - + update_buffer_length(buffer, read); decode_sockaddr(sockaddr, self.unix) .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } @@ -358,7 +355,7 @@ impl io::Write for Socket { impl io::Read for Socket { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.inner.recv(unsafe { transmute(buf) }) + self.inner.read(buf) } } diff --git a/std/src/std/env.inko b/std/src/std/env.inko index a6b85c756..e8d486783 100644 --- a/std/src/std/env.inko +++ b/std/src/std/env.inko @@ -37,8 +37,6 @@ fn extern inko_env_get(state: Pointer[UInt8], name: String) -> AnyResult fn extern inko_env_get_working_directory(state: Pointer[UInt8]) -> AnyResult -fn extern inko_env_home_directory(state: Pointer[UInt8]) -> AnyResult - fn extern inko_env_set_working_directory(path: String) -> AnyResult fn extern inko_env_temp_directory(state: Pointer[UInt8]) -> String @@ -119,10 +117,17 @@ fn pub variables -> Map[String, String] { # env.home_directory # => Option.Some('/home/alice') # ``` fn pub home_directory -> Option[Path] { - match inko_env_home_directory(_INKO.state) { - case { @tag = 0, @value = val } -> Option.Some(Path.new(val as String)) - case _ -> Option.None + # Rather than performing all sorts of magical incantations to get the home + # directory, we're just going to require that HOME is set. + # + # If the home is explicitly set to an empty string we still ignore it, because + # there's no scenario in which Some("") is useful. + let val = match inko_env_get(_INKO.state, 'HOME') { + case { @tag = 0, @value = val } -> val as String + case _ -> return Option.None } + + if val.size > 0 { Option.Some(Path.new(val)) } else { Option.None } } # Returns the path to the temporary directory. diff --git a/std/src/std/fs/path.inko b/std/src/std/fs/path.inko index 2ba9f9916..da38eca26 100644 --- a/std/src/std/fs/path.inko +++ b/std/src/std/fs/path.inko @@ -1,6 +1,7 @@ # Cross-platform path manipulation. import std.clone (Clone) import std.cmp (Equal) +import std.env (home_directory) import std.fmt (Format, Formatter) import std.fs (DirectoryEntry) import std.hash (Hash, Hasher) @@ -85,6 +86,13 @@ let pub SEPARATOR = '/' # The byte used to represent the path separator. let SEPARATOR_BYTE = 47 +# The character used to signal the user's home directory. +let HOME = '~' + +# The prefix of a path that indicates a path relative to the user's home +# directory. +let HOME_WITH_SEPARATOR = HOME + SEPARATOR + # Returns the number of bytes leading up to the last path separator. # # If no separator could be found, `-1` is returned. @@ -623,11 +631,20 @@ class pub Path { # Returns the canonical, absolute version of `self`. # + # # Resolving home directories + # + # If `self` is equal to `~`, this method returns the path to the user's home + # directory. If `self` starts with `~/`, this prefix is replaced with the path + # to the user's home directory (e.g. `~/foo` becomes `/var/home/alice/foo`). + # # # Errors # - # This method may return an `Error` for cases such as when `self` doesn't - # exist, or when a component that isn't the last component is _not_ a - # directory. + # This method may return an `Error` for cases such as: + # + # - `self` doesn't exist + # - a component that isn't the last component is _not_ a directory + # - `self` is equal to `~` or starts with `~/`, but the home directory can't + # be found (e.g. it doesn't exist) # # # Examples # @@ -635,9 +652,30 @@ class pub Path { # import std.fs.path (Path) # # Path.new('/foo/../bar').expand.get # => Path.new('/bar') + # Path.new('~').expand.get # => '/var/home/...' + # Path.new('~/').expand.get # => '/var/home/...' # ``` fn pub expand -> Result[Path, Error] { - match inko_path_expand(_INKO.state, @path) { + if @path == HOME { + return match home_directory { + case Some(v) -> Result.Ok(v) + case _ -> Result.Error(Error.NotFound) + } + } + + let mut target = @path + + match @path.strip_prefix(HOME_WITH_SEPARATOR) { + case Some(tail) -> { + target = match home_directory { + case Some(v) -> join_strings(v.path, tail) + case _ -> throw Error.NotFound + } + } + case _ -> {} + } + + match inko_path_expand(_INKO.state, target) { case { @tag = 0, @value = v } -> Result.Ok(Path.new(v as String)) case { @tag = _, @value = e } -> { Result.Error(Error.from_os_error(e as Int)) diff --git a/std/src/std/io.inko b/std/src/std/io.inko index bc8891661..7b6bbefbe 100644 --- a/std/src/std/io.inko +++ b/std/src/std/io.inko @@ -19,6 +19,12 @@ let MAX_READ_ALL_SIZE = 1024 * 1024 # The default size of the buffer maintained by `BufferedReader`. let DEFAULT_BUFFER_SIZE = 8 * 1024 +# The error code used to signal invalid data. +# +# This error code isn't produced by libc, instead it's specific to the runtime +# library. +let INVALID_DATA = -2 + fn extern inko_last_error -> Int32 # An error type for I/O operations. @@ -121,6 +127,10 @@ class pub enum Error { # A memory address used (e.g. as an argument) is in an invalid range. case BadAddress + # The data provided for the operation is invalid, such as when using an + # invalid TLS certificate. + case InvalidData + # An error not covered by the other variants. # # The wrapped `Int` is the raw error code. @@ -170,6 +180,7 @@ class pub enum Error { case errors.EHOSTUNREACH -> Error.HostUnreachable case errors.EINPROGRESS -> Error.InProgress case errors.EFAULT -> Error.BadAddress + case INVALID_DATA -> Error.InvalidData case val -> Error.Other(val) } } @@ -214,6 +225,7 @@ impl ToString for Error { case TimedOut -> 'the operation timed out' case WouldBlock -> 'the operation would block' case BadAddress -> 'a memory address is in an invalid range' + case InvalidData -> "the data provided isn't valid for the operation" case Other(code) -> 'an other error with code ${code} occurred' } } @@ -253,6 +265,7 @@ impl Format for Error { case TimedOut -> 'TimedOut' case WouldBlock -> 'WouldBlock' case BadAddress -> 'BadAddress' + case InvalidData -> 'InvalidData' case Other(code) -> { formatter.tuple('Other').field(code).finish return @@ -297,6 +310,7 @@ impl Equal[ref Error] for Error { case (TimedOut, TimedOut) -> true case (WouldBlock, WouldBlock) -> true case (Other(a), Other(b)) -> a == b + case (InvalidData, InvalidData) -> true case _ -> false } } diff --git a/std/src/std/net/socket.inko b/std/src/std/net/socket.inko index 42171d2c5..1619e5228 100644 --- a/std/src/std/net/socket.inko +++ b/std/src/std/net/socket.inko @@ -87,11 +87,6 @@ class extern RawAddress { let @port: Int } -class extern AnyResult { - let @tag: Int - let @value: UInt64 -} - class extern IntResult { let @tag: Int let @value: Int @@ -222,6 +217,26 @@ fn extern inko_socket_shutdown_read_write( socket: Pointer[RawSocket], ) -> IntResult +fn extern inko_tls_socket_write( + state: Pointer[UInt8], + process: Pointer[UInt8], + socket: Pointer[RawSocket], + connection: Pointer[UInt8], + data: Pointer[UInt8], + size: Int, + deadline: Int, +) -> IntResult + +fn extern inko_tls_socket_read( + state: Pointer[UInt8], + process: Pointer[UInt8], + socket: Pointer[RawSocket], + connection: Pointer[UInt8], + buffer: mut ByteArray, + amount: Int, + deadline: Int, +) -> IntResult + # The maximum value valid for a listen() call. # # Linux and FreeBSD do not allow for values greater than this as they internally @@ -264,34 +279,35 @@ class pub enum Type { # An IPv4 or IPv6 socket address. class pub SocketAddress { # The IPv4/IPv6 address of this socket address. - # - # This is stored as a `String` so we don't need to parse the address every - # time a `SocketAddress` is created. - let pub @address: String + let pub @ip: IpAddress # The port number of this socket address. let pub @port: Int - fn pub static new(address: String, port: Int) -> SocketAddress { - SocketAddress(address: address, port: port) - } + fn static from_raw(raw: ref RawAddress) -> SocketAddress { + # The address passed to this method is one filled in by the system. Assuming + # our IP address parsing logic is complete (which it should be), + # encountering an address we can't parse is a bug and should terminate the + # program. + let ip = match IpAddress.parse(raw.address) { + case Some(v) -> v + case _ -> panic("IpAddress.parse doesn't support '${raw.address}'") + } - # Returns the IPv4/IPv6 address associated with `self`. - fn pub ip -> Option[IpAddress] { - IpAddress.parse(@address) + SocketAddress(ip, raw.port) } } impl Equal[ref SocketAddress] for SocketAddress { # Returns `true` if `self` and `other` are the same. fn pub ==(other: ref SocketAddress) -> Bool { - @address == other.address and @port == other.port + @ip == other.ip and @port == other.port } } impl Format for SocketAddress { fn pub fmt(formatter: mut Formatter) { - formatter.write('${@address}:${@port}') + formatter.write('${@ip}:${@port}') } } @@ -639,7 +655,7 @@ class pub Socket { ) as Int { - case 0 -> Result.Ok(SocketAddress.new(raw.address, raw.port)) + case 0 -> Result.Ok(SocketAddress.from_raw(raw)) case e -> Result.Error(Error.from_os_error(e)) } } @@ -649,7 +665,7 @@ class pub Socket { let raw = RawAddress(address: '', port: 0) match inko_socket_local_address(_INKO.state, @raw, mut raw) as Int { - case 0 -> Result.Ok(SocketAddress.new(raw.address, raw.port)) + case 0 -> Result.Ok(SocketAddress.from_raw(raw)) case e -> Result.Error(Error.from_os_error(e)) } } @@ -659,7 +675,7 @@ class pub Socket { let raw = RawAddress(address: '', port: 0) match inko_socket_peer_address(_INKO.state, @raw, mut raw) as Int { - case 0 -> Result.Ok(SocketAddress.new(raw.address, raw.port)) + case 0 -> Result.Ok(SocketAddress.from_raw(raw)) case e -> Result.Error(Error.from_os_error(e)) } } @@ -850,6 +866,50 @@ class pub Socket { panic('getsockopt(2) failed: ${Error.last_os_error}') } } + + fn pub mut tls_write_string( + con: Pointer[UInt8], + string: String, + ) -> Result[Nil, Error] { + let state = _INKO.state + let proc = _INKO.process + + match + inko_tls_socket_write( + state, + proc, + @raw, + con, + string.to_pointer, + string.size, + @deadline, + ) + { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @value = e } -> Result.Error(Error.from_os_error(e)) + } + } + + fn pub mut tls_read( + con: Pointer[UInt8], + into: mut ByteArray, + size: Int, + ) -> Result[Int, Error] { + match + inko_tls_socket_read( + _INKO.state, + _INKO.process, + @raw, + con, + into, + size, + @deadline, + ) + { + case { @tag = 0, @value = v } -> Result.Ok(v) + case { @tag = _, @value = e } -> Result.Error(Error.from_os_error(e)) + } + } } impl Drop for Socket { diff --git a/std/src/std/string.inko b/std/src/std/string.inko index 682dcfd0d..a55cf1383 100644 --- a/std/src/std/string.inko +++ b/std/src/std/string.inko @@ -482,28 +482,38 @@ class builtin String { # Returns a new `String` without the given prefix. # + # If `self` starts with the prefix, a `Option.Some` is returned containing the + # substring after the prefix. If `self` doesn't start with the prefix, an + # `Option.None` is returned. + # # # Examples # # ```inko - # 'xhellox'.strip_prefix('x') # => 'hellox' + # 'xhellox'.strip_prefix('x') # => Option.Some('hellox') + # 'xhellox'.strip_prefix('y') # => Option.None # ``` - fn pub strip_prefix(prefix: String) -> String { - if starts_with?(prefix).false? { return clone } + fn pub strip_prefix(prefix: String) -> Option[String] { + if starts_with?(prefix).false? { return Option.None } - slice(start: prefix.size, size: size - prefix.size).into_string + Option.Some(slice(start: prefix.size, size: size - prefix.size).into_string) } # Returns a new `String` without the given suffix. # + # If `self` ends with the suffix, a `Option.Some` is returned containing the + # substring before the prefix. If `self` doesn't end with the suffix, an + # `Option.None` is returned. + # # # Examples # # ```inko - # 'xhellox'.strip_suffix('x') # => 'xhello' + # 'xhellox'.strip_suffix('x') # => Option.Some('xhello') + # 'xhellox'.strip_suffix('y') # => Option.None # ``` - fn pub strip_suffix(suffix: String) -> String { - if ends_with?(suffix).false? { return clone } + fn pub strip_suffix(suffix: String) -> Option[String] { + if ends_with?(suffix).false? { return Option.None } - slice(start: 0, size: size - suffix.size).into_string + Option.Some(slice(start: 0, size: size - suffix.size).into_string) } # Returns a new `String` without any leading whitespace. diff --git a/std/test/compiler/test_diagnostics.inko b/std/test/compiler/test_diagnostics.inko index 6df68cb01..1a0336817 100644 --- a/std/test/compiler/test_diagnostics.inko +++ b/std/test/compiler/test_diagnostics.inko @@ -215,7 +215,7 @@ class Diagnostic { # We remove the directory leading up to the file, that way the diagnostic # lines in the test file don't need to specify the full file paths, and # debugging failing tests is a little less annoying due to noisy output. - let file = (try string(map, 'file')).strip_prefix('${directory}/') + let file = (try string(map, 'file')).strip_prefix('${directory}/').get let line = try location(map, 'lines') let column = try location(map, 'columns') let message = try string(map, 'message') @@ -277,7 +277,7 @@ fn pub tests(t: mut Tests) { case Error(e) -> panic('failed to read the diagnostics directory: ${e}') } - let name = test_file.tail.strip_suffix('.inko') + let name = test_file.tail.strip_suffix('.inko').get t.test('inko check ${name}', fn move (t) { let file = ReadOnlyFile.new(test_file.clone).or_panic( diff --git a/std/test/std/fs/test_path.inko b/std/test/std/fs/test_path.inko index bb0bcbdf0..6e31649e9 100644 --- a/std/test/std/fs/test_path.inko +++ b/std/test/std/fs/test_path.inko @@ -5,6 +5,7 @@ import std.fs (DirectoryEntry, FileType) import std.fs.file (self, ReadOnlyFile, WriteOnlyFile) import std.fs.path (self, Path) import std.io (Error) +import std.stdio (STDOUT) import std.sys import std.test (Tests) import std.time (DateTime) @@ -153,17 +154,37 @@ fn pub tests(t: mut Tests) { t.test('Path.fmt', fn (t) { t.equal(fmt(Path.new('foo')), '"foo"') }) t.test('Path.expand', fn (t) { - let temp = env.temporary_directory - let bar = temp.join('foo').join('bar') + with_directory(t.id, fn (temp) { + let bar = temp.join('foo').join('bar') - bar.create_directory_all.get + bar.create_directory_all.get - let expanded = bar.join('..').join('..').expand + let expanded = bar.join('..').join('..').expand - t.equal(expanded, Result.Ok(temp)) - bar.remove_directory_all + t.equal(expanded, Result.Ok(temp.clone)) + }) + + t.equal(Path.new('~').expand.ok, env.home_directory) + t.equal(Path.new('~/').expand.ok, env.home_directory) + t.true(Path.new('~foo').expand.error?) + t.true(Path.new('/~').expand.error?) + t.true(Path.new('~/this-directory-should-not-exist').expand.error?) }) + t.fork( + 'Path.expand with a missing home directory', + child: fn { + let out = STDOUT.new + let res = Path.new('~').expand.map(fn (v) { v.to_string }).or('ERROR') + + out.write_string(res) + }, + test: fn (test, proc) { + proc.variable('HOME', '') + test.equal(proc.spawn.stdout, 'ERROR') + }, + ) + t.test('Path.tail', fn (t) { t.equal(Path.new('foo').tail, 'foo') t.equal(Path.new('foo/').tail, 'foo') diff --git a/std/test/std/net/test_socket.inko b/std/test/std/net/test_socket.inko index d1c212940..1a86db7ed 100644 --- a/std/test/std/net/test_socket.inko +++ b/std/test/std/net/test_socket.inko @@ -47,15 +47,15 @@ impl Drop for SocketPath { fn pub tests(t: mut Tests) { t.test('SocketAddress.new', fn (t) { - let addr = SocketAddress.new(address: '127.0.0.1', port: 1234) + let addr = SocketAddress(ip: IpAddress.v4(127, 0, 0, 1), port: 1234) - t.equal(addr.ip, Option.Some(IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)))) + t.equal(addr.ip, IpAddress.V4(Ipv4Address.new(127, 0, 0, 1))) t.equal(addr.port, 1234) }) t.test('SocketAddress.==', fn (t) { - let addr1 = SocketAddress.new(address: '127.0.0.1', port: 1234) - let addr2 = SocketAddress.new(address: '127.0.0.1', port: 4567) + let addr1 = SocketAddress(ip: IpAddress.v4(127, 0, 0, 1), port: 1234) + let addr2 = SocketAddress(ip: IpAddress.v4(127, 0, 0, 1), port: 4567) t.equal(addr1, addr1) t.not_equal(addr1, addr2) @@ -63,7 +63,7 @@ fn pub tests(t: mut Tests) { t.test('SocketAddress.fmt', fn (t) { t.equal( - fmt(SocketAddress.new(address: '127.0.0.1', port: 1234)), + fmt(SocketAddress(ip: IpAddress.v4(127, 0, 0, 1), port: 1234)), '127.0.0.1:1234', ) }) @@ -101,7 +101,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.true(stream1.connect(addr.ip.get, addr.port).ok?) + t.true(stream1.connect(addr.ip, addr.port).ok?) let stream2 = Socket.ipv4(Type.STREAM).get @@ -133,7 +133,7 @@ fn pub tests(t: mut Tests) { let addr = server.local_address.get - t.equal(client.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(client.connect(addr.ip, addr.port), Result.Ok(nil)) let connection = server.accept.get @@ -149,7 +149,7 @@ fn pub tests(t: mut Tests) { let buffer = ByteArray.new t.equal( - socket.send_string_to('ping', send_to.ip.get, send_to.port), + socket.send_string_to('ping', send_to.ip, send_to.port), Result.Ok(4), ) t.equal(socket.read(into: buffer, size: 4), Result.Ok(4)) @@ -165,11 +165,7 @@ fn pub tests(t: mut Tests) { let buffer = ByteArray.new t.equal( - socket.send_bytes_to( - 'ping'.to_byte_array, - send_to.ip.get, - send_to.port.clone, - ), + socket.send_bytes_to('ping'.to_byte_array, send_to.ip, send_to.port.clone), Result.Ok(4), ) t.equal(socket.read(into: buffer, size: 4), Result.Ok(4)) @@ -186,7 +182,7 @@ fn pub tests(t: mut Tests) { let send_to = listener.local_address.get t.equal( - client.send_string_to('ping', send_to.ip.get, send_to.port.clone), + client.send_string_to('ping', send_to.ip, send_to.port.clone), Result.Ok(4), ) @@ -200,7 +196,7 @@ fn pub tests(t: mut Tests) { let socket = Socket.ipv4(Type.DGRAM).get let address = socket.local_address.get - t.equal(address.address, '0.0.0.0') + t.equal(address.ip, IpAddress.v4(0, 0, 0, 0)) t.equal(address.port, 0) }) @@ -211,7 +207,7 @@ fn pub tests(t: mut Tests) { let local_address = socket.local_address.get - t.equal(local_address.address, '127.0.0.1') + t.equal(local_address.ip, IpAddress.v4(127, 0, 0, 1)) t.true(local_address.port > 0) }) @@ -230,7 +226,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.equal(client.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(client.connect(addr.ip, addr.port), Result.Ok(nil)) t.equal(client.peer_address, Result.Ok(addr)) }) @@ -309,7 +305,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.equal(stream.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(stream.connect(addr.ip, addr.port), Result.Ok(nil)) stream.shutdown_read.get let bytes = ByteArray.new @@ -327,7 +323,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.equal(stream.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(stream.connect(addr.ip, addr.port), Result.Ok(nil)) stream.shutdown_write.get t.true(stream.write_string('ping').error?) @@ -342,7 +338,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.equal(stream.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(stream.connect(addr.ip, addr.port), Result.Ok(nil)) stream.shutdown.get t.true(stream.write_string('ping').error?) @@ -357,7 +353,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.equal(stream.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(stream.connect(addr.ip, addr.port), Result.Ok(nil)) stream.shutdown.get let bytes = ByteArray.new @@ -380,7 +376,7 @@ fn pub tests(t: mut Tests) { let addr = socket.local_address.get let bytes = ByteArray.new - t.equal(socket.send_string_to('ping', addr.ip.get, addr.port), Result.Ok(4)) + t.equal(socket.send_string_to('ping', addr.ip, addr.port), Result.Ok(4)) t.equal(socket.read(into: bytes, size: 4), Result.Ok(4)) t.equal(bytes.into_string, 'ping') }) @@ -394,7 +390,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.equal(stream.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(stream.connect(addr.ip, addr.port), Result.Ok(nil)) stream.write_bytes('ping'.to_byte_array).get let connection = listener.accept.get @@ -413,7 +409,7 @@ fn pub tests(t: mut Tests) { let addr = listener.local_address.get - t.equal(stream.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(stream.connect(addr.ip, addr.port), Result.Ok(nil)) stream.write_string('ping').get let connection = listener.accept.get @@ -477,7 +473,7 @@ fn pub tests(t: mut Tests) { let socket2 = UdpSocket.new(ip: ip, port: 0).get let addr = socket2.local_address.get - t.true(socket1.connect(addr.ip.get, addr.port).ok?) + t.true(socket1.connect(addr.ip, addr.port).ok?) }) t.test('UdpSocket.send_string_to', fn (t) { @@ -487,7 +483,7 @@ fn pub tests(t: mut Tests) { let addr = socket.local_address.get - t.equal(socket.send_string_to('ping', addr.ip.get, addr.port), Result.Ok(4)) + t.equal(socket.send_string_to('ping', addr.ip, addr.port), Result.Ok(4)) let bytes = ByteArray.new @@ -500,7 +496,7 @@ fn pub tests(t: mut Tests) { let socket = UdpSocket.new(ip: ip, port: 0).get let addr = socket.local_address.get - socket.send_bytes_to('ping'.to_byte_array, addr.ip.get, addr.port).get + socket.send_bytes_to('ping'.to_byte_array, addr.ip, addr.port).get let bytes = ByteArray.new @@ -514,7 +510,7 @@ fn pub tests(t: mut Tests) { let client = UdpSocket.new(ip: ip, port: 0).get let addr = listener.local_address.get - client.send_string_to('ping', addr.ip.get, addr.port).get + client.send_string_to('ping', addr.ip, addr.port).get let bytes = ByteArray.new @@ -527,7 +523,7 @@ fn pub tests(t: mut Tests) { let socket = UdpSocket.new(ip: ip, port: 0).get let local_address = socket.local_address.get - t.equal(local_address.address, '127.0.0.1') + t.equal(local_address.ip, IpAddress.v4(127, 0, 0, 1)) t.true(local_address.port > 0) }) @@ -543,7 +539,7 @@ fn pub tests(t: mut Tests) { let socket = UdpSocket.new(ip: ip, port: 0).get let addr = socket.local_address.get - t.equal(socket.send_string_to('ping', addr.ip.get, addr.port), Result.Ok(4)) + t.equal(socket.send_string_to('ping', addr.ip, addr.port), Result.Ok(4)) let bytes = ByteArray.new @@ -557,7 +553,7 @@ fn pub tests(t: mut Tests) { let client_socket = UdpSocket.new(ip: ip, port: 0).get let addr = server_socket.local_address.get - t.equal(client_socket.connect(addr.ip.get, addr.port), Result.Ok(nil)) + t.equal(client_socket.connect(addr.ip, addr.port), Result.Ok(nil)) client_socket.write_bytes('ping'.to_byte_array).get let bytes = ByteArray.new @@ -580,7 +576,7 @@ fn pub tests(t: mut Tests) { listener.listen.get let addr = listener.local_address.get - let client = try TcpClient.new(addr.ip.get, addr.port) + let client = try TcpClient.new(addr.ip, addr.port) t.true(client.socket.no_delay?) Result.Ok(nil) @@ -596,7 +592,7 @@ fn pub tests(t: mut Tests) { { let client = try TcpClient.with_timeout( - addr.ip.get, + addr.ip, addr.port, timeout_after: Duration.from_secs(2), ) @@ -611,7 +607,15 @@ fn pub tests(t: mut Tests) { timeout_after: Duration.from_micros(500), ) - t.equal(timed_out.error, Option.Some(Error.TimedOut)) + # If no internet connection is available the error is NetworkUnreachable + # instead, so we have to account for that. + t.true( + match timed_out { + case Error(TimedOut or NetworkUnreachable) -> true + case _ -> false + }, + ) + Result.Ok(nil) }) @@ -622,10 +626,10 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) let local_addr = try stream.local_address - t.equal(local_addr.address, '127.0.0.1') + t.equal(local_addr.ip, IpAddress.v4(127, 0, 0, 1)) t.true(local_addr.port > 0) Result.Ok(nil) }) @@ -637,10 +641,10 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) let peer_addr = try stream.peer_address - t.equal(peer_addr.address, addr.address) + t.equal(peer_addr.ip, addr.ip) t.equal(peer_addr.port, addr.port) Result.Ok(nil) }) @@ -652,7 +656,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) let bytes = ByteArray.new let client = try listener.accept @@ -670,7 +674,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) let connection = try listener.accept let bytes = ByteArray.new @@ -687,7 +691,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) let connection = try listener.accept let bytes = ByteArray.new @@ -704,7 +708,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) stream.flush }) @@ -716,7 +720,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) t.equal(stream.shutdown_read, Result.Ok(nil)) @@ -734,7 +738,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) t.equal(stream.shutdown_write, Result.Ok(nil)) t.true(stream.write_string('ping').error?) @@ -748,7 +752,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) t.equal(stream.shutdown, Result.Ok(nil)) t.true(stream.write_string('ping').error?) @@ -762,7 +766,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) t.equal(stream.shutdown, Result.Ok(nil)) @@ -780,7 +784,7 @@ fn pub tests(t: mut Tests) { try listener.listen let addr = try listener.local_address - let client = try TcpClient.new(addr.ip.get, addr.port) + let client = try TcpClient.new(addr.ip, addr.port) let clone = try client.try_clone t.true(clone.socket.no_delay?) @@ -795,14 +799,14 @@ fn pub tests(t: mut Tests) { let listener = TcpServer.new(ip: ip, port: 0).get let addr = listener.local_address.get - t.true(TcpServer.new(addr.ip.get, addr.port).ok?) + t.true(TcpServer.new(addr.ip, addr.port).ok?) }) t.ok('TcpServer.accept', fn (t) { let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) let listener = try TcpServer.new(ip: ip, port: 0) let addr = try listener.local_address - let stream = try TcpClient.new(addr.ip.get, addr.port) + let stream = try TcpClient.new(addr.ip, addr.port) let connection = try listener.accept t.equal(connection.local_address, stream.peer_address) @@ -815,7 +819,7 @@ fn pub tests(t: mut Tests) { let listener = TcpServer.new(ip: ip, port: 0).get let addr = listener.local_address.get - t.equal(addr.address, '127.0.0.1') + t.equal(addr.ip, IpAddress.v4(127, 0, 0, 1)) t.true(addr.port > 0) }) diff --git a/std/test/std/test_env.inko b/std/test/std/test_env.inko index f704802b0..0afadfb3e 100644 --- a/std/test/std/test_env.inko +++ b/std/test/std/test_env.inko @@ -44,6 +44,20 @@ fn pub tests(t: mut Tests) { } }) + t.fork( + 'env.home_directory with a missing home directory', + child: fn { + let out = STDOUT.new + let res = env.home_directory.map(fn (v) { v.to_string }).or('ERROR') + + out.write_string(res) + }, + test: fn (test, proc) { + proc.variable('HOME', '') + test.equal(proc.spawn.stdout, 'ERROR') + }, + ) + t.test('env.working_directory', fn (t) { let path = env.working_directory.get diff --git a/std/test/std/test_io.inko b/std/test/std/test_io.inko index 40d97a4ec..4e6f96113 100644 --- a/std/test/std/test_io.inko +++ b/std/test/std/test_io.inko @@ -1,5 +1,7 @@ import std.fmt (fmt) -import std.io (Buffer, BufferedReader, DEFAULT_BUFFER_SIZE, Error, Read, Write) +import std.io ( + Buffer, BufferedReader, DEFAULT_BUFFER_SIZE, Error, INVALID_DATA, Read, Write, +) import std.libc.bsd.errors if bsd import std.libc.linux.errors if linux import std.libc.mac.errors if mac @@ -101,6 +103,7 @@ fn pub tests(t: mut Tests) { t.equal(Error.from_os_error(errors.ECONNREFUSED), Error.ConnectionRefused) t.equal(Error.from_os_error(errors.EHOSTUNREACH), Error.HostUnreachable) t.equal(Error.from_os_error(errors.EINPROGRESS), Error.InProgress) + t.equal(Error.from_os_error(INVALID_DATA), Error.InvalidData) t.equal(Error.from_os_error(999), Error.Other(999)) }) @@ -135,6 +138,7 @@ fn pub tests(t: mut Tests) { t.equal(fmt(Error.StorageFull), 'StorageFull') t.equal(fmt(Error.TimedOut), 'TimedOut') t.equal(fmt(Error.WouldBlock), 'WouldBlock') + t.equal(fmt(Error.InvalidData), 'InvalidData') t.equal(fmt(Error.Other(999)), 'Other(999)') }) diff --git a/std/test/std/test_optparse.inko b/std/test/std/test_optparse.inko index d918a35f5..2bdafd9f1 100644 --- a/std/test/std/test_optparse.inko +++ b/std/test/std/test_optparse.inko @@ -535,7 +535,8 @@ fn pub tests(t: mut Tests) { lorem ipsum --example -x Foo' - .strip_prefix('\n'), + .strip_prefix('\n') + .get, ) opts.single('o', 'option-with-a-much-longer-name', '', 'Example') @@ -552,7 +553,8 @@ fn pub tests(t: mut Tests) { --example -x Foo -o, --option-with-a-much-longer-name Example' - .strip_prefix('\n'), + .strip_prefix('\n') + .get, ) }) diff --git a/std/test/std/test_string.inko b/std/test/std/test_string.inko index a83446645..9483547ef 100644 --- a/std/test/std/test_string.inko +++ b/std/test/std/test_string.inko @@ -258,27 +258,27 @@ fn pub tests(t: mut Tests) { }) t.test('String.strip_prefix', fn (t) { - t.equal('hello'.strip_prefix('xxxxxxxxx'), 'hello') - t.equal('hello'.strip_prefix('x'), 'hello') - t.equal('hello'.strip_prefix(''), 'hello') - t.equal('XhelloX'.strip_prefix('x'), 'XhelloX') - t.equal('xhellox'.strip_prefix('xy'), 'xhellox') - t.equal('xhellox'.strip_prefix('y'), 'xhellox') - t.equal('xhellox'.strip_prefix('x'), 'hellox') - t.equal('xxhelloxx'.strip_prefix('xx'), 'helloxx') - t.equal('๐Ÿ˜ƒhello๐Ÿ˜ƒ'.strip_prefix('๐Ÿ˜ƒ'), 'hello๐Ÿ˜ƒ') + t.equal('hello'.strip_prefix('xxxxxxxxx'), Option.None) + t.equal('hello'.strip_prefix('x'), Option.None) + t.equal('hello'.strip_prefix(''), Option.None) + t.equal('XhelloX'.strip_prefix('x'), Option.None) + t.equal('xhellox'.strip_prefix('xy'), Option.None) + t.equal('xhellox'.strip_prefix('y'), Option.None) + t.equal('xhellox'.strip_prefix('x'), Option.Some('hellox')) + t.equal('xxhelloxx'.strip_prefix('xx'), Option.Some('helloxx')) + t.equal('๐Ÿ˜ƒhello๐Ÿ˜ƒ'.strip_prefix('๐Ÿ˜ƒ'), Option.Some('hello๐Ÿ˜ƒ')) }) t.test('String.strip_suffix', fn (t) { - t.equal('hello'.strip_suffix('xxxxxxxxx'), 'hello') - t.equal('hello'.strip_suffix('x'), 'hello') - t.equal('hello'.strip_suffix(''), 'hello') - t.equal('XhelloX'.strip_suffix('x'), 'XhelloX') - t.equal('xhellox'.strip_suffix('xy'), 'xhellox') - t.equal('xhellox'.strip_suffix('y'), 'xhellox') - t.equal('xhellox'.strip_suffix('x'), 'xhello') - t.equal('xxhelloxx'.strip_suffix('xx'), 'xxhello') - t.equal('๐Ÿ˜ƒhello๐Ÿ˜ƒ'.strip_suffix('๐Ÿ˜ƒ'), '๐Ÿ˜ƒhello') + t.equal('hello'.strip_suffix('xxxxxxxxx'), Option.None) + t.equal('hello'.strip_suffix('x'), Option.None) + t.equal('hello'.strip_suffix(''), Option.None) + t.equal('XhelloX'.strip_suffix('x'), Option.None) + t.equal('xhellox'.strip_suffix('xy'), Option.None) + t.equal('xhellox'.strip_suffix('y'), Option.None) + t.equal('xhellox'.strip_suffix('x'), Option.Some('xhello')) + t.equal('xxhelloxx'.strip_suffix('xx'), Option.Some('xxhello')) + t.equal('๐Ÿ˜ƒhello๐Ÿ˜ƒ'.strip_suffix('๐Ÿ˜ƒ'), Option.Some('๐Ÿ˜ƒhello')) }) t.test('String.trim_start', fn (t) {