From 0b545382d14dfd0ed509a05c5366e5aef8f383c5 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 23 Jul 2022 03:16:03 +0000 Subject: [PATCH 01/83] core.native --- build.sbt | 17 +++++++----- .../fs2/compression/CompressionPlatform.scala | 0 .../scala/fs2/compression/GunzipResult.scala | 0 .../main/scala/fs2/ChunkRuntimePlatform.scala | 26 +++++++++++++++++++ .../src/test/scala/fs2/TestPlatform.scala | 26 +++++++++++++++++++ project/plugins.sbt | 2 ++ 6 files changed, 64 insertions(+), 7 deletions(-) rename core/{jvm => jvm-native}/src/main/scala/fs2/compression/CompressionPlatform.scala (100%) rename core/{jvm => jvm-native}/src/main/scala/fs2/compression/GunzipResult.scala (100%) create mode 100644 core/native/src/main/scala/fs2/ChunkRuntimePlatform.scala create mode 100644 core/native/src/test/scala/fs2/TestPlatform.scala diff --git a/build.sbt b/build.sbt index 47022ff38f..095a220037 100644 --- a/build.sbt +++ b/build.sbt @@ -174,7 +174,9 @@ lazy val root = tlCrossRootProject lazy val IntegrationTest = config("it").extend(Test) -lazy val core = crossProject(JVMPlatform, JSPlatform) +ThisBuild / resolvers ++= Resolver.sonatypeOssRepos("snapshots") + +lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("core")) .configs(IntegrationTest) .settings(Defaults.itSettings: _*) @@ -188,18 +190,19 @@ lazy val core = crossProject(JVMPlatform, JSPlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % "2.8.0", "org.typelevel" %%% "cats-laws" % "2.8.0" % Test, - "org.typelevel" %%% "cats-effect" % "3.3.14", - "org.typelevel" %%% "cats-effect-laws" % "3.3.14" % Test, - "org.typelevel" %%% "cats-effect-testkit" % "3.3.14" % Test, + "com.armanbilge" %%% "cats-effect" % "3.4-f28b163-SNAPSHOT", + "com.armanbilge" %%% "cats-effect-laws" % "3.4-f28b163-SNAPSHOT" % Test, + "com.armanbilge" %%% "cats-effect-testkit" % "3.4-f28b163-SNAPSHOT" % Test, "org.scodec" %%% "scodec-bits" % "1.1.34", - "org.typelevel" %%% "scalacheck-effect-munit" % "1.0.4" % Test, - "org.typelevel" %%% "munit-cats-effect-3" % "1.0.7" % Test, - "org.typelevel" %%% "discipline-munit" % "1.0.9" % Test + "org.typelevel" %%% "scalacheck-effect-munit" % "2.0-1a82a3d-SNAPSHOT" % Test, + "com.armanbilge" %%% "munit-cats-effect" % "2.0-4e051ab-SNAPSHOT" % Test, + "org.typelevel" %%% "discipline-munit" % "2.0.0-M3" % Test ), tlJdkRelease := Some(8), Compile / doc / scalacOptions ++= (if (scalaVersion.value.startsWith("2.")) Seq("-nowarn") else Nil) ) + .nativeConfigure(_.disablePlugins(DoctestPlugin)) lazy val coreJVM = core.jvm .settings( diff --git a/core/jvm/src/main/scala/fs2/compression/CompressionPlatform.scala b/core/jvm-native/src/main/scala/fs2/compression/CompressionPlatform.scala similarity index 100% rename from core/jvm/src/main/scala/fs2/compression/CompressionPlatform.scala rename to core/jvm-native/src/main/scala/fs2/compression/CompressionPlatform.scala diff --git a/core/jvm/src/main/scala/fs2/compression/GunzipResult.scala b/core/jvm-native/src/main/scala/fs2/compression/GunzipResult.scala similarity index 100% rename from core/jvm/src/main/scala/fs2/compression/GunzipResult.scala rename to core/jvm-native/src/main/scala/fs2/compression/GunzipResult.scala diff --git a/core/native/src/main/scala/fs2/ChunkRuntimePlatform.scala b/core/native/src/main/scala/fs2/ChunkRuntimePlatform.scala new file mode 100644 index 0000000000..ef64bbb190 --- /dev/null +++ b/core/native/src/main/scala/fs2/ChunkRuntimePlatform.scala @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 + +private[fs2] trait ChunkRuntimePlatform[+O] + +private[fs2] trait ChunkCompanionRuntimePlatform diff --git a/core/native/src/test/scala/fs2/TestPlatform.scala b/core/native/src/test/scala/fs2/TestPlatform.scala new file mode 100644 index 0000000000..0c9de713fe --- /dev/null +++ b/core/native/src/test/scala/fs2/TestPlatform.scala @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 + +trait TestPlatform { + def isJVM: Boolean = false +} diff --git a/project/plugins.sbt b/project/plugins.sbt index db75b1a946..ae1d5b638c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,6 +2,8 @@ val sbtTypelevelVersion = "0.4.13" addSbtPlugin("org.typelevel" % "sbt-typelevel" % sbtTypelevelVersion) addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % sbtTypelevelVersion) addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.5") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.2") From 20a1dbb0159105c9e37106aa1e1b52bd74102863 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 23 Jul 2022 04:01:43 +0000 Subject: [PATCH 02/83] io.native. sort of --- build.sbt | 9 +- .../src/main/scala/fs2/io/net/Datagram.scala | 0 .../scala/fs2/io/net/DatagramSocket.scala | 0 .../fs2/io/net/DatagramSocketGroup.scala | 0 .../src/main/scala/fs2/io/net/Network.scala | 0 .../src/main/scala/fs2/io/net/Socket.scala | 0 .../main/scala/fs2/io/net/SocketGroup.scala | 0 .../main/scala/fs2/io/net/SocketOption.scala | 0 .../scala/fs2/io/net/tls/TLSContext.scala | 0 .../main/scala/fs2/io/net/tls/TLSLogger.scala | 0 .../main/scala/fs2/io/net/tls/TLSSocket.scala | 0 .../io/net/unixsocket/UnixSocketAddress.scala | 0 .../fs2/io/net/unixsocket/UnixSockets.scala | 0 .../scala/fs2/io/net/tcp/SocketSuite.scala | 0 .../test/scala/fs2/io/net/udp/UdpSuite.scala | 0 .../io/net/unixsocket/UnixSocketsSuite.scala | 0 .../main/scala/fs2/io/DeprecatedWatcher.scala | 0 .../scala/fs2/io/compressionplatform.scala | 0 .../src/main/scala/fs2/io/file/CopyFlag.scala | 0 .../fs2/io/file/DeprecatedFilesApi.scala | 0 .../fs2/io/file/FileHandlePlatform.scala | 0 .../scala/fs2/io/file/FilesPlatform.scala | 0 .../src/main/scala/fs2/io/file/Flag.scala | 0 .../src/main/scala/fs2/io/file/Path.scala | 0 .../fs2/io/file/PermissionsPlatform.scala | 0 .../fs2/io/file/ReadCursorPlatform.scala | 0 .../src/main/scala/fs2/io/file/Watcher.scala | 12 +-- .../fs2/io/file/WriteCursorPlatform.scala | 0 .../src/main/scala/fs2/io/file/file.scala | 0 .../scala/fs2/io/file/BaseFileSuite.scala | 0 .../src/main/scala/fs2/io/ioplatform.scala | 92 +++++++++++++++++++ 31 files changed, 103 insertions(+), 10 deletions(-) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/Datagram.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/DatagramSocket.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/DatagramSocketGroup.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/Network.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/Socket.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/SocketGroup.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/SocketOption.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/tls/TLSContext.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/tls/TLSLogger.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/tls/TLSSocket.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala (100%) rename io/{shared => js-jvm}/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala (100%) rename io/{shared => js-jvm}/src/test/scala/fs2/io/net/tcp/SocketSuite.scala (100%) rename io/{shared => js-jvm}/src/test/scala/fs2/io/net/udp/UdpSuite.scala (100%) rename io/{shared => js-jvm}/src/test/scala/fs2/io/net/unixsocket/UnixSocketsSuite.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/DeprecatedWatcher.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/compressionplatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/CopyFlag.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/DeprecatedFilesApi.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/FileHandlePlatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/FilesPlatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/Flag.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/Path.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/PermissionsPlatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/ReadCursorPlatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/Watcher.scala (97%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/WriteCursorPlatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/file/file.scala (100%) rename io/{jvm => jvm-native}/src/test/scala/fs2/io/file/BaseFileSuite.scala (100%) create mode 100644 io/native/src/main/scala/fs2/io/ioplatform.scala diff --git a/build.sbt b/build.sbt index 095a220037..9484f74c8a 100644 --- a/build.sbt +++ b/build.sbt @@ -218,13 +218,15 @@ lazy val coreJS = core.js scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) -lazy val io = crossProject(JVMPlatform, JSPlatform) +lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("io")) .settings( name := "fs2-io", - libraryDependencies += "com.comcast" %%% "ip4s-core" % "3.1.3", tlVersionIntroduced ~= { _.updated("3", "3.1.0") } ) + .platformsSettings(JVMPlatform, JSPlatform)( + libraryDependencies += "com.comcast" %%% "ip4s-core" % "3.1.3" + ) .jvmSettings( Test / fork := true, libraryDependencies ++= Seq( @@ -283,6 +285,9 @@ lazy val io = crossProject(JVMPlatform, JSPlatform) ) ) ) + .nativeSettings( + nativeConfig ~= { _.withEmbedResources(true) } + ) lazy val scodec = crossProject(JVMPlatform, JSPlatform) .in(file("scodec")) diff --git a/io/shared/src/main/scala/fs2/io/net/Datagram.scala b/io/js-jvm/src/main/scala/fs2/io/net/Datagram.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/Datagram.scala rename to io/js-jvm/src/main/scala/fs2/io/net/Datagram.scala diff --git a/io/shared/src/main/scala/fs2/io/net/DatagramSocket.scala b/io/js-jvm/src/main/scala/fs2/io/net/DatagramSocket.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/DatagramSocket.scala rename to io/js-jvm/src/main/scala/fs2/io/net/DatagramSocket.scala diff --git a/io/shared/src/main/scala/fs2/io/net/DatagramSocketGroup.scala b/io/js-jvm/src/main/scala/fs2/io/net/DatagramSocketGroup.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/DatagramSocketGroup.scala rename to io/js-jvm/src/main/scala/fs2/io/net/DatagramSocketGroup.scala diff --git a/io/shared/src/main/scala/fs2/io/net/Network.scala b/io/js-jvm/src/main/scala/fs2/io/net/Network.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/Network.scala rename to io/js-jvm/src/main/scala/fs2/io/net/Network.scala diff --git a/io/shared/src/main/scala/fs2/io/net/Socket.scala b/io/js-jvm/src/main/scala/fs2/io/net/Socket.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/Socket.scala rename to io/js-jvm/src/main/scala/fs2/io/net/Socket.scala diff --git a/io/shared/src/main/scala/fs2/io/net/SocketGroup.scala b/io/js-jvm/src/main/scala/fs2/io/net/SocketGroup.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/SocketGroup.scala rename to io/js-jvm/src/main/scala/fs2/io/net/SocketGroup.scala diff --git a/io/shared/src/main/scala/fs2/io/net/SocketOption.scala b/io/js-jvm/src/main/scala/fs2/io/net/SocketOption.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/SocketOption.scala rename to io/js-jvm/src/main/scala/fs2/io/net/SocketOption.scala diff --git a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala b/io/js-jvm/src/main/scala/fs2/io/net/tls/TLSContext.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala rename to io/js-jvm/src/main/scala/fs2/io/net/tls/TLSContext.scala diff --git a/io/shared/src/main/scala/fs2/io/net/tls/TLSLogger.scala b/io/js-jvm/src/main/scala/fs2/io/net/tls/TLSLogger.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/tls/TLSLogger.scala rename to io/js-jvm/src/main/scala/fs2/io/net/tls/TLSLogger.scala diff --git a/io/shared/src/main/scala/fs2/io/net/tls/TLSSocket.scala b/io/js-jvm/src/main/scala/fs2/io/net/tls/TLSSocket.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/tls/TLSSocket.scala rename to io/js-jvm/src/main/scala/fs2/io/net/tls/TLSSocket.scala diff --git a/io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala b/io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala rename to io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala diff --git a/io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala b/io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala similarity index 100% rename from io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala rename to io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala diff --git a/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala b/io/js-jvm/src/test/scala/fs2/io/net/tcp/SocketSuite.scala similarity index 100% rename from io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala rename to io/js-jvm/src/test/scala/fs2/io/net/tcp/SocketSuite.scala diff --git a/io/shared/src/test/scala/fs2/io/net/udp/UdpSuite.scala b/io/js-jvm/src/test/scala/fs2/io/net/udp/UdpSuite.scala similarity index 100% rename from io/shared/src/test/scala/fs2/io/net/udp/UdpSuite.scala rename to io/js-jvm/src/test/scala/fs2/io/net/udp/UdpSuite.scala diff --git a/io/shared/src/test/scala/fs2/io/net/unixsocket/UnixSocketsSuite.scala b/io/js-jvm/src/test/scala/fs2/io/net/unixsocket/UnixSocketsSuite.scala similarity index 100% rename from io/shared/src/test/scala/fs2/io/net/unixsocket/UnixSocketsSuite.scala rename to io/js-jvm/src/test/scala/fs2/io/net/unixsocket/UnixSocketsSuite.scala diff --git a/io/jvm/src/main/scala/fs2/io/DeprecatedWatcher.scala b/io/jvm-native/src/main/scala/fs2/io/DeprecatedWatcher.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/DeprecatedWatcher.scala rename to io/jvm-native/src/main/scala/fs2/io/DeprecatedWatcher.scala diff --git a/io/jvm/src/main/scala/fs2/io/compressionplatform.scala b/io/jvm-native/src/main/scala/fs2/io/compressionplatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/compressionplatform.scala rename to io/jvm-native/src/main/scala/fs2/io/compressionplatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/CopyFlag.scala b/io/jvm-native/src/main/scala/fs2/io/file/CopyFlag.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/CopyFlag.scala rename to io/jvm-native/src/main/scala/fs2/io/file/CopyFlag.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/DeprecatedFilesApi.scala b/io/jvm-native/src/main/scala/fs2/io/file/DeprecatedFilesApi.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/DeprecatedFilesApi.scala rename to io/jvm-native/src/main/scala/fs2/io/file/DeprecatedFilesApi.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/FileHandlePlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/FileHandlePlatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/FileHandlePlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/file/FileHandlePlatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/FilesPlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/Flag.scala b/io/jvm-native/src/main/scala/fs2/io/file/Flag.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/Flag.scala rename to io/jvm-native/src/main/scala/fs2/io/file/Flag.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/Path.scala b/io/jvm-native/src/main/scala/fs2/io/file/Path.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/Path.scala rename to io/jvm-native/src/main/scala/fs2/io/file/Path.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/PermissionsPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/PermissionsPlatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/PermissionsPlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/file/PermissionsPlatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/ReadCursorPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/ReadCursorPlatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/ReadCursorPlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/file/ReadCursorPlatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/Watcher.scala b/io/jvm-native/src/main/scala/fs2/io/file/Watcher.scala similarity index 97% rename from io/jvm/src/main/scala/fs2/io/file/Watcher.scala rename to io/jvm-native/src/main/scala/fs2/io/file/Watcher.scala index 8179e0ed02..171309ee09 100644 --- a/io/jvm/src/main/scala/fs2/io/file/Watcher.scala +++ b/io/jvm-native/src/main/scala/fs2/io/file/Watcher.scala @@ -215,11 +215,7 @@ object Watcher { path: Path, types: Seq[Watcher.EventType] = Nil, modifiers: Seq[WatchEvent.Modifier] = Nil - ): F[F[Unit]] = - Files[F].isDirectory(path).flatMap { dir => - if (dir) watchDirectory(path, types, modifiers) - else watchFile(path, types, modifiers) - } + ): F[F[Unit]] = ??? private def watchDirectory( path: Path, @@ -336,9 +332,9 @@ object Watcher { ).flatMap { cancel => val events: F[List[Event]] = F.blocking { var evs: List[Event.Created] = Nil - JFiles - .list(p.toNioPath) - .forEach(d => evs = Event.Created(Path.fromNioPath(d), 1) :: evs) + // JFiles + // .list(p.toNioPath) + // .forEach(d => evs = Event.Created(Path.fromNioPath(d), 1) :: evs) evs } events.map(cancel -> _) diff --git a/io/jvm/src/main/scala/fs2/io/file/WriteCursorPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/WriteCursorPlatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/WriteCursorPlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/file/WriteCursorPlatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/file/file.scala b/io/jvm-native/src/main/scala/fs2/io/file/file.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/file/file.scala rename to io/jvm-native/src/main/scala/fs2/io/file/file.scala diff --git a/io/jvm/src/test/scala/fs2/io/file/BaseFileSuite.scala b/io/jvm-native/src/test/scala/fs2/io/file/BaseFileSuite.scala similarity index 100% rename from io/jvm/src/test/scala/fs2/io/file/BaseFileSuite.scala rename to io/jvm-native/src/test/scala/fs2/io/file/BaseFileSuite.scala diff --git a/io/native/src/main/scala/fs2/io/ioplatform.scala b/io/native/src/main/scala/fs2/io/ioplatform.scala new file mode 100644 index 0000000000..9b4fff9876 --- /dev/null +++ b/io/native/src/main/scala/fs2/io/ioplatform.scala @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io + +import cats._ +import cats.effect.kernel.{Async, Outcome, Resource, Sync} +import cats.effect.kernel.implicits._ +import cats.effect.kernel.Deferred +import cats.syntax.all._ + +import java.io.{InputStream, OutputStream} +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import scala.reflect.ClassTag + +private[fs2] trait ioplatform { + type InterruptedIOException = java.io.InterruptedIOException + type ClosedChannelException = java.nio.channels.ClosedChannelException + + // + // STDIN/STDOUT Helpers + + /** Stream of bytes read asynchronously from standard input. */ + def stdin[F[_]: Sync](bufSize: Int): Stream[F, Byte] = + readInputStream(Sync[F].blocking(System.in), bufSize, false) + + /** Pipe of bytes that writes emitted values to standard output asynchronously. */ + def stdout[F[_]: Sync]: Pipe[F, Byte, Nothing] = + writeOutputStream(Sync[F].blocking(System.out), false) + + /** Pipe of bytes that writes emitted values to standard error asynchronously. */ + def stderr[F[_]: Sync]: Pipe[F, Byte, Nothing] = + writeOutputStream(Sync[F].blocking(System.err), false) + + /** Writes this stream to standard output asynchronously, converting each element to + * a sequence of bytes via `Show` and the given `Charset`. + */ + def stdoutLines[F[_]: Sync, O: Show]( + charset: Charset = StandardCharsets.UTF_8 + ): Pipe[F, O, Nothing] = + _.map(_.show).through(text.encode(charset)).through(stdout) + + /** Stream of `String` read asynchronously from standard input decoded in UTF-8. */ + def stdinUtf8[F[_]: Sync](bufSize: Int): Stream[F, String] = + stdin(bufSize).through(text.utf8.decode) + + /** Stream of bytes read asynchronously from the specified resource relative to the class `C`. + * @see [[readClassLoaderResource]] for a resource relative to a classloader. + */ + def readClassResource[F[_], C]( + name: String, + chunkSize: Int = 64 * 1024 + )(implicit F: Sync[F], ct: ClassTag[C]): Stream[F, Byte] = + Stream.eval(F.blocking(Option(ct.runtimeClass.getResourceAsStream(name)))).flatMap { + case Some(resource) => io.readInputStream(resource.pure, chunkSize) + case None => Stream.raiseError(new IOException(s"Resource $name not found")) + } + + /** Stream of bytes read asynchronously from the specified classloader resource. + * @see [[readClassResource]] for a resource relative to a class. + */ + def readClassLoaderResource[F[_]]( + name: String, + chunkSize: Int = 64 * 1024, + classLoader: ClassLoader = getClass().getClassLoader() + )(implicit F: Sync[F]): Stream[F, Byte] = + Stream.eval(F.blocking(Option(classLoader.getResourceAsStream(name)))).flatMap { + case Some(resource) => io.readInputStream(resource.pure, chunkSize) + case None => Stream.raiseError(new IOException(s"Resource $name not found")) + } + +} From 20a277a661fcda2668f97be5d0c9e739df1ddeee Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 5 Aug 2022 18:53:10 +0000 Subject: [PATCH 03/83] scodec, protocols native --- build.sbt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index 9484f74c8a..292137a453 100644 --- a/build.sbt +++ b/build.sbt @@ -222,10 +222,8 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("io")) .settings( name := "fs2-io", - tlVersionIntroduced ~= { _.updated("3", "3.1.0") } - ) - .platformsSettings(JVMPlatform, JSPlatform)( - libraryDependencies += "com.comcast" %%% "ip4s-core" % "3.1.3" + tlVersionIntroduced ~= { _.updated("3", "3.1.0") }, + libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.1-177-8abd977-SNAPSHOT" ) .jvmSettings( Test / fork := true, @@ -289,7 +287,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) nativeConfig ~= { _.withEmbedResources(true) } ) -lazy val scodec = crossProject(JVMPlatform, JSPlatform) +lazy val scodec = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("scodec")) .settings( name := "fs2-scodec", @@ -297,7 +295,7 @@ lazy val scodec = crossProject(JVMPlatform, JSPlatform) scalaVersion.value.startsWith("2.") ) "1.11.9" - else "2.1.0"), + else "2.2.0"), tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.2.0").toMap, tlJdkRelease := Some(8) ) @@ -306,7 +304,7 @@ lazy val scodec = crossProject(JVMPlatform, JSPlatform) ) .dependsOn(core % "compile->compile;test->test", io % "test") -lazy val protocols = crossProject(JVMPlatform, JSPlatform) +lazy val protocols = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("protocols")) .settings( name := "fs2-protocols", From 193adec04891d463dad646717067ee61e40bfe34 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 20 Aug 2022 05:23:15 +0000 Subject: [PATCH 04/83] Revert "scodec, protocols native" This reverts commit 20a277a661fcda2668f97be5d0c9e739df1ddeee. --- build.sbt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 292137a453..9484f74c8a 100644 --- a/build.sbt +++ b/build.sbt @@ -222,8 +222,10 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("io")) .settings( name := "fs2-io", - tlVersionIntroduced ~= { _.updated("3", "3.1.0") }, - libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.1-177-8abd977-SNAPSHOT" + tlVersionIntroduced ~= { _.updated("3", "3.1.0") } + ) + .platformsSettings(JVMPlatform, JSPlatform)( + libraryDependencies += "com.comcast" %%% "ip4s-core" % "3.1.3" ) .jvmSettings( Test / fork := true, @@ -287,7 +289,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) nativeConfig ~= { _.withEmbedResources(true) } ) -lazy val scodec = crossProject(JVMPlatform, JSPlatform, NativePlatform) +lazy val scodec = crossProject(JVMPlatform, JSPlatform) .in(file("scodec")) .settings( name := "fs2-scodec", @@ -295,7 +297,7 @@ lazy val scodec = crossProject(JVMPlatform, JSPlatform, NativePlatform) scalaVersion.value.startsWith("2.") ) "1.11.9" - else "2.2.0"), + else "2.1.0"), tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.2.0").toMap, tlJdkRelease := Some(8) ) @@ -304,7 +306,7 @@ lazy val scodec = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) .dependsOn(core % "compile->compile;test->test", io % "test") -lazy val protocols = crossProject(JVMPlatform, JSPlatform, NativePlatform) +lazy val protocols = crossProject(JVMPlatform, JSPlatform) .in(file("protocols")) .settings( name := "fs2-protocols", From 13e6dfe03e83bb9d63c21d993f405863b356510d Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 20 Aug 2022 05:31:57 +0000 Subject: [PATCH 05/83] Cross UnixSocketAddress for Native --- .../scala/fs2/io/net/unixsocket/UnixSocketAddressPlatform.scala | 0 .../src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename io/{js => js-native}/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddressPlatform.scala (100%) rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala (100%) diff --git a/io/js/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddressPlatform.scala b/io/js-native/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddressPlatform.scala similarity index 100% rename from io/js/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddressPlatform.scala rename to io/js-native/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddressPlatform.scala diff --git a/io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala b/io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala rename to io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSocketAddress.scala From 8dbaa4684ea8929a383272478f02e17a85226c79 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 29 Aug 2022 02:08:03 +0000 Subject: [PATCH 06/83] Reshuffling to cross Socket for native --- build.sbt | 17 +++--- .../fs2/io/net/SocketGroupPlatform.scala | 7 +-- .../fs2/io/net/SocketOptionPlatform.scala | 0 .../scala/fs2/io/net/SocketPlatform.scala | 0 .../src/main/scala/fs2/io/net/net.scala | 0 .../fs2/io/net/tcp/SocketSuitePlatform.scala | 0 .../src/main/scala/fs2/io/net/Network.scala | 36 +++++++++++ .../scala/fs2/io/net/NetworkPlatform.scala | 61 +++++++++++++++++++ .../src/main/scala/fs2/io/net/Socket.scala | 0 .../main/scala/fs2/io/net/SocketGroup.scala | 0 .../main/scala/fs2/io/net/SocketOption.scala | 0 .../scala/fs2/io/net/tcp/SocketSuite.scala | 0 12 files changed, 109 insertions(+), 12 deletions(-) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/net/SocketGroupPlatform.scala (94%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/net/SocketOptionPlatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/net/SocketPlatform.scala (100%) rename io/{jvm => jvm-native}/src/main/scala/fs2/io/net/net.scala (100%) rename io/{jvm => jvm-native}/src/test/scala/fs2/io/net/tcp/SocketSuitePlatform.scala (100%) create mode 100644 io/native/src/main/scala/fs2/io/net/Network.scala create mode 100644 io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/Socket.scala (100%) rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/SocketGroup.scala (100%) rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/SocketOption.scala (100%) rename io/{js-jvm => shared}/src/test/scala/fs2/io/net/tcp/SocketSuite.scala (100%) diff --git a/build.sbt b/build.sbt index 9484f74c8a..dea3ce6c97 100644 --- a/build.sbt +++ b/build.sbt @@ -190,9 +190,9 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % "2.8.0", "org.typelevel" %%% "cats-laws" % "2.8.0" % Test, - "com.armanbilge" %%% "cats-effect" % "3.4-f28b163-SNAPSHOT", - "com.armanbilge" %%% "cats-effect-laws" % "3.4-f28b163-SNAPSHOT" % Test, - "com.armanbilge" %%% "cats-effect-testkit" % "3.4-f28b163-SNAPSHOT" % Test, + "com.armanbilge" %%% "cats-effect" % "3.4-519e5ce-SNAPSHOT", + "com.armanbilge" %%% "cats-effect-laws" % "3.4-519e5ce-SNAPSHOT" % Test, + "com.armanbilge" %%% "cats-effect-testkit" % "3.4-519e5ce-SNAPSHOT" % Test, "org.scodec" %%% "scodec-bits" % "1.1.34", "org.typelevel" %%% "scalacheck-effect-munit" % "2.0-1a82a3d-SNAPSHOT" % Test, "com.armanbilge" %%% "munit-cats-effect" % "2.0-4e051ab-SNAPSHOT" % Test, @@ -222,10 +222,8 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("io")) .settings( name := "fs2-io", - tlVersionIntroduced ~= { _.updated("3", "3.1.0") } - ) - .platformsSettings(JVMPlatform, JSPlatform)( - libraryDependencies += "com.comcast" %%% "ip4s-core" % "3.1.3" + tlVersionIntroduced ~= { _.updated("3", "3.1.0") }, + libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.3-53-f37ec8e-SNAPSHOT", ) .jvmSettings( Test / fork := true, @@ -238,6 +236,11 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.1.0").toMap, scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) + .nativeSettings( + libraryDependencies ++= Seq( + "com.armanbilge" %%% "epollcat-net" % "0.0-ddff38f-SNAPSHOT" % Test + ) + ) .dependsOn(core % "compile->compile;test->test") .jsSettings( mimaBinaryIssueFilters ++= Seq( diff --git a/io/jvm/src/main/scala/fs2/io/net/SocketGroupPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/net/SocketGroupPlatform.scala similarity index 94% rename from io/jvm/src/main/scala/fs2/io/net/SocketGroupPlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/net/SocketGroupPlatform.scala index 25c5e5bb20..5d8cf96066 100644 --- a/io/jvm/src/main/scala/fs2/io/net/SocketGroupPlatform.scala +++ b/io/jvm-native/src/main/scala/fs2/io/net/SocketGroupPlatform.scala @@ -31,7 +31,6 @@ import java.nio.channels.{ CompletionHandler } import java.nio.channels.AsynchronousChannelGroup -import java.nio.channels.spi.AsynchronousChannelProvider import cats.syntax.all._ import cats.effect.kernel.{Async, Resource} import com.comcast.ip4s.{Host, IpAddress, Port, SocketAddress} @@ -49,8 +48,7 @@ private[net] trait SocketGroupCompanionPlatform { self: SocketGroup.type => ): Resource[F, Socket[F]] = { def setup: Resource[F, AsynchronousSocketChannel] = Resource.make(Async[F].delay { - val ch = - AsynchronousChannelProvider.provider.openAsynchronousSocketChannel(channelGroup) + val ch = AsynchronousSocketChannel.open(channelGroup) options.foreach(opt => ch.setOption(opt.key, opt.value)) ch })(ch => Async[F].delay(if (ch.isOpen) ch.close else ())) @@ -83,8 +81,7 @@ private[net] trait SocketGroupCompanionPlatform { self: SocketGroup.type => val setup: F[AsynchronousServerSocketChannel] = address.traverse(_.resolve[F]).flatMap { addr => Async[F].delay { - val ch = - AsynchronousChannelProvider.provider.openAsynchronousServerSocketChannel(channelGroup) + val ch = AsynchronousServerSocketChannel.open(channelGroup) ch.bind( new InetSocketAddress( addr.map(_.toInetAddress).orNull, diff --git a/io/jvm/src/main/scala/fs2/io/net/SocketOptionPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/net/SocketOptionPlatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/net/SocketOptionPlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/net/SocketOptionPlatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/net/SocketPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/net/SocketPlatform.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/net/SocketPlatform.scala rename to io/jvm-native/src/main/scala/fs2/io/net/SocketPlatform.scala diff --git a/io/jvm/src/main/scala/fs2/io/net/net.scala b/io/jvm-native/src/main/scala/fs2/io/net/net.scala similarity index 100% rename from io/jvm/src/main/scala/fs2/io/net/net.scala rename to io/jvm-native/src/main/scala/fs2/io/net/net.scala diff --git a/io/jvm/src/test/scala/fs2/io/net/tcp/SocketSuitePlatform.scala b/io/jvm-native/src/test/scala/fs2/io/net/tcp/SocketSuitePlatform.scala similarity index 100% rename from io/jvm/src/test/scala/fs2/io/net/tcp/SocketSuitePlatform.scala rename to io/jvm-native/src/test/scala/fs2/io/net/tcp/SocketSuitePlatform.scala diff --git a/io/native/src/main/scala/fs2/io/net/Network.scala b/io/native/src/main/scala/fs2/io/net/Network.scala new file mode 100644 index 0000000000..267d167076 --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/Network.scala @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io +package net + +sealed trait Network[F[_]] + extends NetworkPlatform[F] + with SocketGroup[F] { + +} + +object Network extends NetworkCompanionPlatform { + private[net] trait UnsealedNetwork[F[_]] extends Network[F] + + def apply[F[_]](implicit F: Network[F]): F.type = F +} diff --git a/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala b/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala new file mode 100644 index 0000000000..5a8f76299e --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io +package net + +import cats.effect.kernel.{Async, Resource} + +import com.comcast.ip4s.{Host, IpAddress, Port, SocketAddress} + +import java.net.ProtocolFamily +import java.nio.channels.AsynchronousChannelGroup + +private[net] trait NetworkPlatform[F[_]] + +private[net] trait NetworkCompanionPlatform { self: Network.type => + + implicit def forAsync[F[_]](implicit F: Async[F]): Network[F] = + new UnsealedNetwork[F] { + private lazy val globalSocketGroup = SocketGroup.unsafe[F](null) + + def client( + to: SocketAddress[Host], + options: List[SocketOption] + ): Resource[F, Socket[F]] = globalSocketGroup.client(to, options) + + def server( + address: Option[Host], + port: Option[Port], + options: List[SocketOption] + ): Stream[F, Socket[F]] = globalSocketGroup.server(address, port, options) + + def serverResource( + address: Option[Host], + port: Option[Port], + options: List[SocketOption] + ): Resource[F, (SocketAddress[IpAddress], Stream[F, Socket[F]])] = + globalSocketGroup.serverResource(address, port, options) + + } + +} diff --git a/io/js-jvm/src/main/scala/fs2/io/net/Socket.scala b/io/shared/src/main/scala/fs2/io/net/Socket.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/Socket.scala rename to io/shared/src/main/scala/fs2/io/net/Socket.scala diff --git a/io/js-jvm/src/main/scala/fs2/io/net/SocketGroup.scala b/io/shared/src/main/scala/fs2/io/net/SocketGroup.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/SocketGroup.scala rename to io/shared/src/main/scala/fs2/io/net/SocketGroup.scala diff --git a/io/js-jvm/src/main/scala/fs2/io/net/SocketOption.scala b/io/shared/src/main/scala/fs2/io/net/SocketOption.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/SocketOption.scala rename to io/shared/src/main/scala/fs2/io/net/SocketOption.scala diff --git a/io/js-jvm/src/test/scala/fs2/io/net/tcp/SocketSuite.scala b/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala similarity index 100% rename from io/js-jvm/src/test/scala/fs2/io/net/tcp/SocketSuite.scala rename to io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala From f161b65998aabde72924b1a62ee0a5f3cd078371 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 29 Aug 2022 02:21:34 +0000 Subject: [PATCH 07/83] Now it links --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index dea3ce6c97..08ca967975 100644 --- a/build.sbt +++ b/build.sbt @@ -238,7 +238,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat-net" % "0.0-ddff38f-SNAPSHOT" % Test + "com.armanbilge" %%% "epollcat-net" % "0.0-c5536da-SNAPSHOT" % Test ) ) .dependsOn(core % "compile->compile;test->test") From f98259c4160ad7915c1b8741c4d37a44b18384e1 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 29 Aug 2022 02:26:43 +0000 Subject: [PATCH 08/83] Configure the epoll runtime --- .../src/test/scala/fs2/io/Fs2IoSuite.scala | 29 +++++++++++++++++++ .../scala/fs2/io/net/tcp/SocketSuite.scala | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 io/native/src/test/scala/fs2/io/Fs2IoSuite.scala diff --git a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala new file mode 100644 index 0000000000..7d73620072 --- /dev/null +++ b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io + +import epollcat.unsafe.EpollRuntime + +abstract class Fs2IoSuite extends Fs2Suite { + override lazy val munitIoRuntime = EpollRuntime.global +} diff --git a/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala b/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala index fa240abd60..e56a8db519 100644 --- a/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala +++ b/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala @@ -31,7 +31,7 @@ import com.comcast.ip4s._ import scala.concurrent.duration._ import scala.concurrent.TimeoutException -class SocketSuite extends Fs2Suite with SocketSuitePlatform { +class SocketSuite extends Fs2IoSuite with SocketSuitePlatform { val timeout = 30.seconds From c96a3271901cb2bff4ac911cd720d1b2ecc129df Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 29 Aug 2022 04:40:56 +0000 Subject: [PATCH 09/83] Bumpity bump --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 08ca967975..1ad98e2629 100644 --- a/build.sbt +++ b/build.sbt @@ -238,7 +238,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat-net" % "0.0-c5536da-SNAPSHOT" % Test + "com.armanbilge" %%% "epollcat-net" % "0.0-640b910-SNAPSHOT" % Test ) ) .dependsOn(core % "compile->compile;test->test") From 6636379897ab95fb2f2e788a7f3a3a814acb965c Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 29 Aug 2022 17:31:14 +0000 Subject: [PATCH 10/83] bump epollcat --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1ad98e2629..bedafdc5d1 100644 --- a/build.sbt +++ b/build.sbt @@ -238,7 +238,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat-net" % "0.0-640b910-SNAPSHOT" % Test + "com.armanbilge" %%% "epollcat-net" % "0.0-fc8e4b2-SNAPSHOT" % Test ) ) .dependsOn(core % "compile->compile;test->test") From 99cb7fe8ce191020d6915799d5752f03e2bd7935 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 30 Aug 2022 02:49:09 +0000 Subject: [PATCH 11/83] bump epollcat --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bedafdc5d1..995d2a7cca 100644 --- a/build.sbt +++ b/build.sbt @@ -238,7 +238,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat-net" % "0.0-fc8e4b2-SNAPSHOT" % Test + "com.armanbilge" %%% "epollcat-net" % "0.0-2f64289-SNAPSHOT" % Test ) ) .dependsOn(core % "compile->compile;test->test") From 3d25a6a1723d160afba2a961fc0cfe17aca8cbe9 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 30 Aug 2022 02:53:52 +0000 Subject: [PATCH 12/83] Tweak assertion for Native --- core/js/src/test/scala/fs2/TestPlatform.scala | 1 + core/jvm/src/test/scala/fs2/TestPlatform.scala | 1 + core/native/src/test/scala/fs2/TestPlatform.scala | 1 + io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala | 4 ++-- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/js/src/test/scala/fs2/TestPlatform.scala b/core/js/src/test/scala/fs2/TestPlatform.scala index 0c9de713fe..da7c30cff6 100644 --- a/core/js/src/test/scala/fs2/TestPlatform.scala +++ b/core/js/src/test/scala/fs2/TestPlatform.scala @@ -23,4 +23,5 @@ package fs2 trait TestPlatform { def isJVM: Boolean = false + def isNative: Boolean = false } diff --git a/core/jvm/src/test/scala/fs2/TestPlatform.scala b/core/jvm/src/test/scala/fs2/TestPlatform.scala index 18c381ce8d..26b9ad75c3 100644 --- a/core/jvm/src/test/scala/fs2/TestPlatform.scala +++ b/core/jvm/src/test/scala/fs2/TestPlatform.scala @@ -23,4 +23,5 @@ package fs2 trait TestPlatform { def isJVM: Boolean = true + def isNative: Boolean = false } diff --git a/core/native/src/test/scala/fs2/TestPlatform.scala b/core/native/src/test/scala/fs2/TestPlatform.scala index 0c9de713fe..3ef6ce0ed3 100644 --- a/core/native/src/test/scala/fs2/TestPlatform.scala +++ b/core/native/src/test/scala/fs2/TestPlatform.scala @@ -23,4 +23,5 @@ package fs2 trait TestPlatform { def isJVM: Boolean = false + def isNative: Boolean = true } diff --git a/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala b/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala index e56a8db519..b7f0c761b2 100644 --- a/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala +++ b/io/shared/src/test/scala/fs2/io/net/tcp/SocketSuite.scala @@ -218,7 +218,7 @@ class SocketSuite extends Fs2IoSuite with SocketSuitePlatform { } } - test("read after timed out read not allowed on JVM") { + test("read after timed out read not allowed on JVM or Native") { val setup = for { serverSetup <- Network[IO].serverResource(Some(ip"127.0.0.1")) (bindAddress, server) = serverSetup @@ -239,7 +239,7 @@ class SocketSuite extends Fs2IoSuite with SocketSuitePlatform { client .readN(msg.size) .flatMap { c => - if (isJVM) { + if (isJVM || isNative) { assertEquals(c.size, 0) // Read again now that the pending read is no longer pending client.readN(msg.size).map(c => assertEquals(c.size, 0)) From 76ef2317925e53e44019b88091719ae56407e162 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 31 Aug 2022 19:01:32 +0000 Subject: [PATCH 13/83] Stub out native tls --- .../main/scala/fs2/io/net/tls/S2nConfig.scala | 26 +++++ .../scala/fs2/io/net/tls/SSLSession.scala | 37 ++++++ .../fs2/io/net/tls/TLSContextPlatform.scala | 61 ++++++++++ .../scala/fs2/io/net/tls/TLSParameters.scala | 108 ++++++++++++++++++ .../fs2/io/net/tls/TLSSocketPlatform.scala | 53 +++++++++ .../scala/fs2/io/net/tls/TLSContext.scala | 0 .../main/scala/fs2/io/net/tls/TLSLogger.scala | 0 .../main/scala/fs2/io/net/tls/TLSSocket.scala | 0 8 files changed, 285 insertions(+) create mode 100644 io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala create mode 100644 io/native/src/main/scala/fs2/io/net/tls/SSLSession.scala create mode 100644 io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala create mode 100644 io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala create mode 100644 io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/tls/TLSContext.scala (100%) rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/tls/TLSLogger.scala (100%) rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/tls/TLSSocket.scala (100%) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala new file mode 100644 index 0000000000..5930ce7a27 --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io.net.tls + +class S2nConfig { + +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/SSLSession.scala b/io/native/src/main/scala/fs2/io/net/tls/SSLSession.scala new file mode 100644 index 0000000000..7a293621f1 --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/SSLSession.scala @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io.net.tls + +import scodec.bits.ByteVector + +final class SSLSession private[tls] (val raw: ByteVector) { + + override def equals(obj: Any): Boolean = obj match { + case that: SSLSession => this.raw == that.raw + case _ => false + } + + override def hashCode: Int = raw.hashCode + + override def toString: String = s"SSLSession($raw)" + +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala new file mode 100644 index 0000000000..6a14b3239c --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io +package net +package tls + +import java.io.{FileInputStream, InputStream} +import cats.Applicative +import cats.effect.kernel.{Async, Resource} +import cats.syntax.all._ +import com.comcast.ip4s.{IpAddress, SocketAddress} + +import java.util.function.BiFunction +import java.nio.file.Path + +private[tls] trait TLSContextPlatform[F[_]] + +private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => + + private[tls] trait BuilderPlatform[F[_]] { + def fromS2nConfig(ctx: S2nConfig): TLSContext[F] + + /** Creates a `TLSContext` which trusts all certificates. */ + def insecure: F[TLSContext[F]] + } + + private[tls] trait BuilderCompanionPlatform { + + /** Creates a `TLSContext` from an `SSLContext`. */ + private[tls] final class AsyncBuilder[F[_]: Async] extends Builder[F] { + // Members declared in fs2.io.net.tls.TLSContext.Builder + def system: F[fs2.io.net.tls.TLSContext[F]] = ??? + + // Members declared in fs2.io.net.tls.TLSContextCompanionPlatform.BuilderPlatform + def fromS2nConfig(ctx: fs2.io.net.tls.S2nConfig): fs2.io.net.tls.TLSContext[F] = ??? + def insecure: F[fs2.io.net.tls.TLSContext[F]] = ??? + } + + } + +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala new file mode 100644 index 0000000000..00f70d4d38 --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io +package net +package tls + +import CollectionCompat._ + +/** Parameters used in creation of a TLS/DTLS session. + * See `javax.net.ssl.SSLParameters` for detailed documentation on each parameter. + * + * Note: `applicationProtocols`, `enableRetransmissions`, `maximumPacketSize`, and + * `handshakeApplicationProtocolSelector` require Java 9+. + */ +sealed trait TLSParameters { + // val algorithmConstraints: Option[AlgorithmConstraints] + // val applicationProtocols: Option[List[String]] + // val cipherSuites: Option[List[String]] + // val enableRetransmissions: Option[Boolean] + // val endpointIdentificationAlgorithm: Option[String] + // val maximumPacketSize: Option[Int] + // val protocols: Option[List[String]] + // val serverNames: Option[List[SNIServerName]] + // val sniMatchers: Option[List[SNIMatcher]] + // val useCipherSuitesOrder: Boolean + // val needClientAuth: Boolean + // val wantClientAuth: Boolean + // val handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] + + /** Converts to a `javax.net.ssl.SSLParameters` instance. + * + * `needClientAuth` and `wantClientAuth` are mutually exclusive on `SSLParameters`. If both set on this `TLSParameters`, then `needClientAuth` takes precedence. + */ + // def toSSLParameters: SSLParameters = { + // val p = new SSLParameters() + // algorithmConstraints.foreach(p.setAlgorithmConstraints) + // applicationProtocols.foreach(ap => p.setApplicationProtocols(ap.toArray)) + // cipherSuites.foreach(cs => p.setCipherSuites(cs.toArray)) + // enableRetransmissions.foreach(p.setEnableRetransmissions) + // endpointIdentificationAlgorithm.foreach(p.setEndpointIdentificationAlgorithm) + // maximumPacketSize.foreach(p.setMaximumPacketSize) + // protocols.foreach(ps => p.setProtocols(ps.toArray)) + // serverNames.foreach(sn => p.setServerNames(sn.asJava)) + // sniMatchers.foreach(sm => p.setSNIMatchers(sm.asJava)) + // p.setUseCipherSuitesOrder(useCipherSuitesOrder) + // if (needClientAuth) + // p.setNeedClientAuth(needClientAuth) + // else if (wantClientAuth) + // p.setWantClientAuth(wantClientAuth) + // p + // } +} + +object TLSParameters { + val Default: TLSParameters = TLSParameters() + + def apply( + // algorithmConstraints: Option[AlgorithmConstraints] = None, + // applicationProtocols: Option[List[String]] = None, + // cipherSuites: Option[List[String]] = None, + // enableRetransmissions: Option[Boolean] = None, + // endpointIdentificationAlgorithm: Option[String] = None, + // maximumPacketSize: Option[Int] = None, + // protocols: Option[List[String]] = None, + // serverNames: Option[List[SNIServerName]] = None, + // sniMatchers: Option[List[SNIMatcher]] = None, + // useCipherSuitesOrder: Boolean = false, + // needClientAuth: Boolean = false, + // wantClientAuth: Boolean = false, + // handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] = None + ): TLSParameters = ??? + + private case class DefaultTLSParameters( + // algorithmConstraints: Option[AlgorithmConstraints], + // applicationProtocols: Option[List[String]], + // cipherSuites: Option[List[String]], + // enableRetransmissions: Option[Boolean], + // endpointIdentificationAlgorithm: Option[String], + // maximumPacketSize: Option[Int], + // protocols: Option[List[String]], + // serverNames: Option[List[SNIServerName]], + // sniMatchers: Option[List[SNIMatcher]], + // useCipherSuitesOrder: Boolean, + // needClientAuth: Boolean, + // wantClientAuth: Boolean, + // handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] + ) extends TLSParameters +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala new file mode 100644 index 0000000000..f1cbecc31f --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io +package net +package tls + +import cats.effect.kernel.Async +import cats.effect.kernel.Resource + +private[tls] trait TLSSocketPlatform[F[_]] + +private[tls] trait TLSSocketCompanionPlatform { self: TLSSocket.type => + + private[tls] def forAsync[F[_]]: Resource[F, TLSSocket[F]] = ??? + + private[tls] final class AsyncTLSSocket[F[_]: Async] extends UnsealedTLSSocket[F] { + // Members declared in fs2.io.net.Socket + def endOfInput: F[Unit] = ??? + def endOfOutput: F[Unit] = ??? + def isOpen: F[Boolean] = ??? + def localAddress: F[com.comcast.ip4s.SocketAddress[com.comcast.ip4s.IpAddress]] = ??? + def read(maxBytes: Int): F[Option[fs2.Chunk[Byte]]] = ??? + def readN(numBytes: Int): F[fs2.Chunk[Byte]] = ??? + def reads: fs2.Stream[F, Byte] = ??? + def remoteAddress: F[com.comcast.ip4s.SocketAddress[com.comcast.ip4s.IpAddress]] = ??? + def write(bytes: fs2.Chunk[Byte]): F[Unit] = ??? + def writes: fs2.Pipe[F, Byte, Nothing] = ??? + + // Members declared in fs2.io.net.tls.TLSSocket + def applicationProtocol: F[String] = ??? + def session: F[fs2.io.net.tls.SSLSession] = ??? + } +} diff --git a/io/js-jvm/src/main/scala/fs2/io/net/tls/TLSContext.scala b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/tls/TLSContext.scala rename to io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala diff --git a/io/js-jvm/src/main/scala/fs2/io/net/tls/TLSLogger.scala b/io/shared/src/main/scala/fs2/io/net/tls/TLSLogger.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/tls/TLSLogger.scala rename to io/shared/src/main/scala/fs2/io/net/tls/TLSLogger.scala diff --git a/io/js-jvm/src/main/scala/fs2/io/net/tls/TLSSocket.scala b/io/shared/src/main/scala/fs2/io/net/tls/TLSSocket.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/tls/TLSSocket.scala rename to io/shared/src/main/scala/fs2/io/net/tls/TLSSocket.scala From c3b3c6012a949efbef7aef97652af0cba12c3bf1 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 31 Aug 2022 20:05:59 +0000 Subject: [PATCH 14/83] Beginnings of s2n binding --- .../src/main/scala/fs2/io/net/tls/s2n.scala | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 io/native/src/main/scala/fs2/io/net/tls/s2n.scala diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala new file mode 100644 index 0000000000..cf8fad2ace --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io.net.tls + +import scala.scalanative.unsafe._ +import scala.annotation.nowarn + +@extern +@link("s2n") +@nowarn +private[tls] object s2n { + + final val S2N_SUCCESS = 0 + final val S2N_FAILURE = -1 + final val S2N_CALLBACK_BLOCKED = -2 + + type s2n_mode = CUnsignedInt + final val S2N_SERVER = 0 + final val S2N_CLIENT = 1 + + type s2n_blocked_status = CUnsignedInt + final val S2N_NOT_BLOCKED = 0 + final val S2N_BLOCKED_ON_READ = 1 + final val S2N_BLOCKED_ON_WRITE = 2 + final val S2N_BLOCKED_ON_APPLICATION_INPUT = 3 + final val S2N_BLOCKED_ON_EARLY_DATA = 4 + + type s2n_config + type s2n_connection + + type s2n_recv_fn = CFuncPtr3[Ptr[Byte], Ptr[CUnsignedChar], CUnsignedInt, CInt] + type s2n_send_fn = CFuncPtr3[Ptr[Byte], Ptr[CUnsignedChar], CUnsignedInt, CInt] + + def s2n_init(): CInt = extern + + def s2n_cleanup(): CInt = extern + + def s2n_config_new(): Ptr[s2n_config] = extern + + def s2n_config_free(config: Ptr[s2n_config]): CInt = extern + + def s2n_config_disable_x509_verification(config: Ptr[s2n_config]): CInt = extern + + def s2n_connection_new(mode: s2n_mode): Ptr[s2n_connection] = extern + + def s2n_connection_set_config(conn: Ptr[s2n_connection], config: Ptr[s2n_config]): CInt = extern + + def s2n_connection_set_recv_ctx(conn: Ptr[s2n_connection], ctx: Ptr[Byte]): CInt = extern + + def s2n_connection_set_send_ctx(conn: Ptr[s2n_connection], ctx: Ptr[Byte]): CInt = extern + + def s2n_connection_set_recv_cb(conn: Ptr[s2n_connection], recv: s2n_recv_fn): CInt = extern + + def s2n_connection_set_send_cb(conn: Ptr[s2n_connection], send: s2n_recv_fn): CInt = extern + + def s2n_negotiate(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = extern + + def s2n_send( + conn: Ptr[s2n_connection], + buf: Ptr[Byte], + size: CSSize, + blocked: Ptr[s2n_blocked_status] + ): CInt = extern + + def s2n_recv( + conn: Ptr[s2n_connection], + buf: Ptr[Byte], + size: CSSize, + blocked: Ptr[s2n_blocked_status] + ): CInt = extern + + def s2n_peek(conn: Ptr[s2n_connection]): CUnsignedInt = extern + + def s2n_connection_free_handshake(conn: Ptr[s2n_connection]): CInt = extern + + def s2n_connection_free(conn: Ptr[s2n_connection]): CInt = extern + + def s2n_connection_shutdown(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = + extern + +} From 0541b8109ec0abc20d6b1b0116dd7095c5a097e2 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 31 Aug 2022 22:54:08 +0000 Subject: [PATCH 15/83] slow progress --- .../scala/fs2/io/net/tls/S2nConnection.scala | 48 +++++++++++++++++++ .../scala/fs2/io/net/tls/SSLException.scala | 31 ++++++++++++ .../src/main/scala/fs2/io/net/tls/s2n.scala | 4 ++ .../main/scala/fs2/io/net/tls/s2nutil.scala | 40 ++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala create mode 100644 io/native/src/main/scala/fs2/io/net/tls/SSLException.scala create mode 100644 io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala new file mode 100644 index 0000000000..322a493a2f --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io.net +package tls + +import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ + +import s2n._ +import s2nutil._ +import cats.effect.kernel.Async +import cats.effect.std.Dispatcher +import cats.effect.kernel.Resource + +private[tls] final class S2nConnection[F[_]]( +) + +private[tls] object S2nConnection { + def apply[F[_]]( + socket: Socket[F], + clientMode: Boolean + )(implicit F: Async[F]): Resource[F, S2nConnection[F]] = + for { + dispatcher <- Dispatcher.sequential[F] + conn <- Resource.make( + F.delay(guard(s2n_connection_new(if (clientMode) S2N_CLIENT.toUInt else S2N_SERVER.toUInt))) + )(conn => F.delay(guard(s2n_connection_free(conn)))) + } yield new S2nConnection() +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/SSLException.scala b/io/native/src/main/scala/fs2/io/net/tls/SSLException.scala new file mode 100644 index 0000000000..dddf726c16 --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/SSLException.scala @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io +package net.tls + +import scala.scalanative.unsafe._ + +class SSLException(message: String = null, cause: Throwable = null) + extends IOException(message, cause) + +private[tls] final class S2nException(error: CInt) + extends SSLException(fromCString(s2n.s2n_strerror(error, c"EN"))) diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index cf8fad2ace..4d4348b7b1 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -50,6 +50,8 @@ private[tls] object s2n { type s2n_recv_fn = CFuncPtr3[Ptr[Byte], Ptr[CUnsignedChar], CUnsignedInt, CInt] type s2n_send_fn = CFuncPtr3[Ptr[Byte], Ptr[CUnsignedChar], CUnsignedInt, CInt] + def s2n_errno_location(): Ptr[CInt] = extern + def s2n_init(): CInt = extern def s2n_cleanup(): CInt = extern @@ -60,6 +62,8 @@ private[tls] object s2n { def s2n_config_disable_x509_verification(config: Ptr[s2n_config]): CInt = extern + def s2n_strerror(error: CInt, lang: Ptr[CChar]): Ptr[CChar] = extern + def s2n_connection_new(mode: s2n_mode): Ptr[s2n_connection] = extern def s2n_connection_set_config(conn: Ptr[s2n_connection], config: Ptr[s2n_config]): CInt = extern diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala new file mode 100644 index 0000000000..f66efad3ae --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io.net.tls + +import scala.scalanative.annotation.alwaysinline +import scala.scalanative.unsafe._ + +import s2n._ + +private[tls] object s2nutil { + @alwaysinline def guard(thunk: => CInt): Unit = + if (thunk != S2N_SUCCESS) + throw new S2nException(!s2n_errno_location()) + + @alwaysinline def guard[A](thunk: => Ptr[A]): Ptr[A] = { + val rtn = thunk + if (rtn == null) + throw new S2nException(!s2n_errno_location()) + else rtn + } +} From 9dc9a3f21bb63ac849179e90f997301ff6663f06 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 10:32:13 +0000 Subject: [PATCH 16/83] Implement the callbacks --- .../main/scala/fs2/io/net/tls/S2nConfig.scala | 6 +- .../scala/fs2/io/net/tls/S2nConnection.scala | 159 +++++++++++++++++- .../src/main/scala/fs2/io/net/tls/s2n.scala | 4 +- .../main/scala/fs2/io/net/tls/s2nutil.scala | 8 + 4 files changed, 166 insertions(+), 11 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index 5930ce7a27..271d979006 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -21,6 +21,10 @@ package fs2.io.net.tls -class S2nConfig { +import scala.scalanative.unsafe._ + +import s2n._ + +final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) { } diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 322a493a2f..09435a6242 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -19,30 +19,173 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package fs2.io.net +package fs2 +package io.net package tls +import cats.effect.SyncIO +import cats.effect.kernel.Async +import cats.effect.kernel.Deferred +import cats.effect.kernel.Ref +import cats.effect.kernel.Resource +import cats.effect.std.Dispatcher +import cats.effect.std.Queue +import cats.effect.syntax.all._ +import cats.syntax.all._ +import scodec.bits.ByteVector + +import java.util.Collections +import java.util.IdentityHashMap +import java.util.concurrent.atomic.AtomicBoolean +import scala.scalanative.libc +import scala.scalanative.posix import scala.scalanative.unsafe._ import scala.scalanative.unsigned._ import s2n._ import s2nutil._ -import cats.effect.kernel.Async -import cats.effect.std.Dispatcher -import cats.effect.kernel.Resource -private[tls] final class S2nConnection[F[_]]( -) +private[tls] trait S2nConnection[F[_]] { + + def handshake: F[Unit] + + def read(n: Int): F[Chunk[Byte]] + + def write(bytes: Chunk[Byte]): F[Unit] + +} private[tls] object S2nConnection { def apply[F[_]]( socket: Socket[F], - clientMode: Boolean + clientMode: Boolean, + config: S2nConfig, + parameters: TLSParameters )(implicit F: Async[F]): Resource[F, S2nConnection[F]] = for { + gcRoot <- Resource.make( + F.delay(Collections.newSetFromMap[Any](new IdentityHashMap)) + )(gcr => F.delay(gcr.clear())) + dispatcher <- Dispatcher.sequential[F] + conn <- Resource.make( F.delay(guard(s2n_connection_new(if (clientMode) S2N_CLIENT.toUInt else S2N_SERVER.toUInt))) )(conn => F.delay(guard(s2n_connection_free(conn)))) - } yield new S2nConnection() + + _ <- F.delay(s2n_connection_set_config(conn, config.ptr)).toResource + + recvLatch <- F.deferred[Either[Throwable, Unit]].flatMap(F.ref(_)).toResource + recvBuffer <- Ref.in[Resource[F, *], SyncIO, Option[ByteVector]](Some(ByteVector.empty)) + recvQueue <- Queue.synchronous[F, Unit].toResource + _ <- Resource.eval { + F.delay { + val ctx = RecvCallbackContext(recvBuffer, recvQueue, dispatcher) + guard(s2n_connection_set_recv_ctx(conn, toPtr(ctx))) + guard(s2n_connection_set_recv_cb(conn, recvCallback[F](_, _, _))) + gcRoot.add(ctx) + } + } + _ <- socket.reads.chunks + .evalTap(_ => recvQueue.take) + .noneTerminate + .attempt + .foreach { + case Left(ex) => + recvLatch.get.flatMap(_.complete(Left(ex))).void + case Right(chunk) => + recvBuffer.set(chunk.map(_.toByteVector)).to[F] *> + F.deferred[Either[Throwable, Unit]] + .flatMap(recvLatch.getAndSet(_)) + .flatMap(_.complete(Either.unit)) + .void + } + .compile + .drain + .background + + sendAvailable <- F.delay(new AtomicBoolean).toResource + sendLatch <- F.deferred[Either[Throwable, Unit]].flatMap(F.ref(_)).toResource + _ <- Resource.eval { + F.delay { + val ctx = SendCallbackContext(sendAvailable, sendLatch, socket, dispatcher, F) + guard(s2n_connection_set_send_ctx(conn, toPtr(ctx))) + guard(s2n_connection_set_send_cb(conn, sendCallback[F](_, _, _))) + gcRoot.add(ctx) + } + } + + } yield new S2nConnection[F] { + + def handshake = ??? + + def read(n: Int) = ??? + + def write(bytes: Chunk[Byte]) = ??? + + } + + private final case class RecvCallbackContext[F[_]]( + recvBuffer: Ref[SyncIO, Option[ByteVector]], + recvQueue: Queue[F, Unit], + dispatcher: Dispatcher[F] + ) + + private def recvCallback[F[_]](ioContext: Ptr[Byte], buf: Ptr[Byte], len: CUnsignedInt): CInt = { + val ctx = fromPtr[RecvCallbackContext[F]](ioContext) + import ctx._ + + val read = recvBuffer.modify { + case Some(bytes) => + val (left, right) = bytes.splitAt(len.toLong) + (Some(right), Some(left)) + case None => (None, None) + } + + read.unsafeRunSync() match { + case Some(bytes) if bytes.nonEmpty => + bytes.copyToPtr(buf, 0) + bytes.length.toInt + case Some(_) => + dispatcher.unsafeRunAndForget(recvQueue.offer(())) + libc.errno.errno = posix.errno.EWOULDBLOCK + S2N_FAILURE + case None => 0 + } + } + + private final case class SendCallbackContext[F[_]]( + sendAvailable: AtomicBoolean, + sendLatch: Ref[F, Deferred[F, Either[Throwable, Unit]]], + socket: Socket[F], + dispatcher: Dispatcher[F], + async: Async[F] + ) + + private def sendCallback[F[_]](ioContext: Ptr[Byte], buf: Ptr[Byte], len: CUnsignedInt): CInt = { + val ctx = fromPtr[SendCallbackContext[F]](ioContext) + import ctx._ + implicit val F = async + + if (sendAvailable.getAndSet(false)) { + dispatcher.unsafeRunAndForget( + socket + .write(Chunk.byteVector(ByteVector.fromPtr(buf, len.toLong))) + .redeemWith( + ex => sendLatch.get.flatMap(_.complete(Left(ex))), + _ => + F.deferred[Either[Throwable, Unit]] + .flatMap(sendLatch.getAndSet(_)) + .flatMap(oldLatch => + F.delay(sendAvailable.set(true)) *> oldLatch.complete(Either.unit) + ) + ) + ) + len.toInt + } else { + libc.errno.errno = posix.errno.EWOULDBLOCK + S2N_FAILURE + } + } + } diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 4d4348b7b1..881ce4e20c 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -47,8 +47,8 @@ private[tls] object s2n { type s2n_config type s2n_connection - type s2n_recv_fn = CFuncPtr3[Ptr[Byte], Ptr[CUnsignedChar], CUnsignedInt, CInt] - type s2n_send_fn = CFuncPtr3[Ptr[Byte], Ptr[CUnsignedChar], CUnsignedInt, CInt] + type s2n_recv_fn = CFuncPtr3[Ptr[Byte], Ptr[Byte], CUnsignedInt, CInt] + type s2n_send_fn = CFuncPtr3[Ptr[Byte], Ptr[Byte], CUnsignedInt, CInt] def s2n_errno_location(): Ptr[CInt] = extern diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala index f66efad3ae..0846e09793 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala @@ -22,6 +22,8 @@ package fs2.io.net.tls import scala.scalanative.annotation.alwaysinline +import scala.scalanative.runtime +import scala.scalanative.runtime.Intrinsics import scala.scalanative.unsafe._ import s2n._ @@ -37,4 +39,10 @@ private[tls] object s2nutil { throw new S2nException(!s2n_errno_location()) else rtn } + + @alwaysinline def toPtr(a: AnyRef): Ptr[Byte] = + runtime.fromRawPtr(Intrinsics.castObjectToRawPtr(a)) + + @alwaysinline def fromPtr[A](ptr: Ptr[Byte]): A = + Intrinsics.castRawPtrToObject(runtime.toRawPtr(ptr)).asInstanceOf[A] } From 7abff17fdaafe7dd6a6716ce0e524172d8b2438e Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 15:09:58 +0000 Subject: [PATCH 17/83] handshake, read, write for s2n conn --- .../scala/fs2/io/net/tls/S2nConnection.scala | 90 +++++++++++++++++-- .../main/scala/fs2/io/net/tls/s2nutil.scala | 9 +- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 09435a6242..a68326475e 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -37,6 +37,7 @@ import scodec.bits.ByteVector import java.util.Collections import java.util.IdentityHashMap import java.util.concurrent.atomic.AtomicBoolean +import scala.annotation.switch import scala.scalanative.libc import scala.scalanative.posix import scala.scalanative.unsafe._ @@ -49,7 +50,7 @@ private[tls] trait S2nConnection[F[_]] { def handshake: F[Unit] - def read(n: Int): F[Chunk[Byte]] + def read(n: Long): F[Option[Chunk[Byte]]] def write(bytes: Chunk[Byte]): F[Unit] @@ -71,7 +72,7 @@ private[tls] object S2nConnection { conn <- Resource.make( F.delay(guard(s2n_connection_new(if (clientMode) S2N_CLIENT.toUInt else S2N_SERVER.toUInt))) - )(conn => F.delay(guard(s2n_connection_free(conn)))) + )(conn => F.delay(guard_(s2n_connection_free(conn)))) _ <- F.delay(s2n_connection_set_config(conn, config.ptr)).toResource @@ -81,8 +82,8 @@ private[tls] object S2nConnection { _ <- Resource.eval { F.delay { val ctx = RecvCallbackContext(recvBuffer, recvQueue, dispatcher) - guard(s2n_connection_set_recv_ctx(conn, toPtr(ctx))) - guard(s2n_connection_set_recv_cb(conn, recvCallback[F](_, _, _))) + guard_(s2n_connection_set_recv_ctx(conn, toPtr(ctx))) + guard_(s2n_connection_set_recv_cb(conn, recvCallback[F](_, _, _))) gcRoot.add(ctx) } } @@ -109,19 +110,90 @@ private[tls] object S2nConnection { _ <- Resource.eval { F.delay { val ctx = SendCallbackContext(sendAvailable, sendLatch, socket, dispatcher, F) - guard(s2n_connection_set_send_ctx(conn, toPtr(ctx))) - guard(s2n_connection_set_send_cb(conn, sendCallback[F](_, _, _))) + guard_(s2n_connection_set_send_ctx(conn, toPtr(ctx))) + guard_(s2n_connection_set_send_cb(conn, sendCallback[F](_, _, _))) gcRoot.add(ctx) } } } yield new S2nConnection[F] { - def handshake = ??? + def handshake = { + def go: F[Unit] = + (recvLatch.get, sendLatch.get).flatMapN { (recvLatch, sendLatch) => + F.delay { + val blocked = stackalloc[s2n_blocked_status]() + guard_(s2n_negotiate(conn, blocked)) + !blocked + }.flatMap { blocked => + (blocked.toInt: @switch) match { + case S2N_NOT_BLOCKED => + F.delay(guard_(s2n_connection_free_handshake(conn))) + case S2N_BLOCKED_ON_READ => + recvLatch.get >> go + case S2N_BLOCKED_ON_WRITE => + sendLatch.get >> go + case _ => + F.raiseError(new IllegalStateException(s"s2n_negotiate blocked: $blocked")) + } + } + } - def read(n: Int) = ??? + go + } + + def read(n: Long) = zone.use { implicit z => + F.delay(alloc[Byte](n)).flatMap { buf => + def go(i: Long): F[Option[Chunk[Byte]]] = recvLatch.get.flatMap { recvLatch => + F.delay[F[Option[Chunk[Byte]]]] { + val blocked = stackalloc[s2n_blocked_status]() + val readed = guard(s2n_recv(conn, buf + i, n - i, blocked)) + ((!blocked).toInt: @switch) match { + case S2N_NOT_BLOCKED => + val total = i + readed + if (total > 0) + F.pure(Some(Chunk.byteVector(ByteVector.fromPtr(buf, total)))) + else + F.pure(None) + case S2N_BLOCKED_ON_READ => + recvLatch.get >> go(i + readed) + case _ => + F.raiseError(new IllegalStateException(s"s2n_recv blocked: $blocked")) + } + }.flatten + } + + go(0) + } + } + + def write(bytes: Chunk[Byte]) = zone.use { implicit z => + val n = bytes.size.toLong + F.delay(alloc[Byte](n.toLong)) + .flatTap(buf => F.delay(bytes.toByteVector.copyToPtr(buf, 0))) + .flatMap { buf => + def go(i: Long): F[Unit] = sendLatch.get.flatMap { sendLatch => + F.delay[F[Unit]] { + val blocked = stackalloc[s2n_blocked_status]() + val wrote = guard(s2n_send(conn, buf + i, n - i, blocked)) + ((!blocked).toInt: @switch) match { + case S2N_NOT_BLOCKED => + val total = i + wrote + if (total == n) F.unit else go(total) + case S2N_BLOCKED_ON_WRITE => + sendLatch.get >> go(i + wrote) + case _ => + F.raiseError(new IllegalStateException(s"s2n_send blocked: $blocked")) + } + }.flatten + } + + go(0) + } + } - def write(bytes: Chunk[Byte]) = ??? + private def zone: Resource[F, Zone] = + Resource.make(F.delay(Zone.open()))(z => F.delay(z.close())) } diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala index 0846e09793..42c33d38fb 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala @@ -29,10 +29,17 @@ import scala.scalanative.unsafe._ import s2n._ private[tls] object s2nutil { - @alwaysinline def guard(thunk: => CInt): Unit = + @alwaysinline def guard_(thunk: => CInt): Unit = if (thunk != S2N_SUCCESS) throw new S2nException(!s2n_errno_location()) + @alwaysinline def guard[A](thunk: => CInt): CInt = { + val rtn = thunk + if (rtn < 0) + throw new S2nException(!s2n_errno_location()) + else rtn + } + @alwaysinline def guard[A](thunk: => Ptr[A]): Ptr[A] = { val rtn = thunk if (rtn == null) From a2675eb1b19b87510d5ba709ca2ca8cc22cb5563 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 15:47:51 +0000 Subject: [PATCH 18/83] Implement shutdown too --- .../scala/fs2/io/net/tls/S2nConnection.scala | 28 ++++++++++++++++++- .../src/main/scala/fs2/io/net/tls/s2n.scala | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index a68326475e..1b076be4d1 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -54,6 +54,8 @@ private[tls] trait S2nConnection[F[_]] { def write(bytes: Chunk[Byte]): F[Unit] + def shutdown: F[Unit] + } private[tls] object S2nConnection { @@ -162,7 +164,7 @@ private[tls] object S2nConnection { } }.flatten } - + go(0) } } @@ -192,6 +194,30 @@ private[tls] object S2nConnection { } } + def shutdown = { + def go: F[Unit] = + (recvLatch.get, sendLatch.get).flatMapN { (recvLatch, sendLatch) => + F.delay { + val blocked = stackalloc[s2n_blocked_status]() + guard_(s2n_shutdown(conn, blocked)) + !blocked + }.flatMap { blocked => + (blocked.toInt: @switch) match { + case S2N_NOT_BLOCKED => + F.unit + case S2N_BLOCKED_ON_READ => + recvLatch.get >> go + case S2N_BLOCKED_ON_WRITE => + sendLatch.get >> go + case _ => + F.raiseError(new IllegalStateException(s"s2n_shutdown blocked: $blocked")) + } + } + } + + go + } + private def zone: Resource[F, Zone] = Resource.make(F.delay(Zone.open()))(z => F.delay(z.close())) diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 881ce4e20c..85d95f1d7a 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -98,7 +98,7 @@ private[tls] object s2n { def s2n_connection_free(conn: Ptr[s2n_connection]): CInt = extern - def s2n_connection_shutdown(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = + def s2n_shutdown(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = extern } From f476b6e85977fac4d98d62eaa534c808a6eac6e7 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 16:27:46 +0000 Subject: [PATCH 19/83] native tls socket ... --- .../src/main/scala/fs2/io/net/Network.scala | 6 +- .../main/scala/fs2/io/net/tls/S2nConfig.scala | 4 +- .../scala/fs2/io/net/tls/S2nConnection.scala | 16 ++++ .../fs2/io/net/tls/TLSSocketPlatform.scala | 85 ++++++++++++++----- .../src/main/scala/fs2/io/net/tls/s2n.scala | 10 +++ 5 files changed, 94 insertions(+), 27 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/Network.scala b/io/native/src/main/scala/fs2/io/net/Network.scala index 267d167076..6c949629e2 100644 --- a/io/native/src/main/scala/fs2/io/net/Network.scala +++ b/io/native/src/main/scala/fs2/io/net/Network.scala @@ -23,11 +23,7 @@ package fs2 package io package net -sealed trait Network[F[_]] - extends NetworkPlatform[F] - with SocketGroup[F] { - -} +sealed trait Network[F[_]] extends NetworkPlatform[F] with SocketGroup[F] {} object Network extends NetworkCompanionPlatform { private[net] trait UnsealedNetwork[F[_]] extends Network[F] diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index 271d979006..bb8acf13f7 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -25,6 +25,4 @@ import scala.scalanative.unsafe._ import s2n._ -final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) { - -} +final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) {} diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 1b076be4d1..795f94eb50 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -56,6 +56,10 @@ private[tls] trait S2nConnection[F[_]] { def shutdown: F[Unit] + def applicationProtocol: F[String] + + def session: F[SSLSession] + } private[tls] object S2nConnection { @@ -218,6 +222,18 @@ private[tls] object S2nConnection { go } + def applicationProtocol = + F.delay(guard(s2n_get_application_protocol(conn))).map(fromCString(_)) + + def session = F.delay { + Zone { implicit z => + val len = guard(s2n_connection_get_session_length(conn)).toUInt + val buf = alloc[Byte](len) + val copied = guard(s2n_connection_get_session(conn, buf, len)) + new SSLSession(ByteVector.fromPtr(buf, copied.toLong)) + } + } + private def zone: Resource[F, Zone] = Resource.make(F.delay(Zone.open()))(z => F.delay(z.close())) diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala index f1cbecc31f..52f4d05211 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala @@ -26,28 +26,75 @@ package tls import cats.effect.kernel.Async import cats.effect.kernel.Resource +import cats.effect.std.Semaphore +import cats.syntax.all._ +import com.comcast.ip4s.IpAddress +import com.comcast.ip4s.SocketAddress private[tls] trait TLSSocketPlatform[F[_]] private[tls] trait TLSSocketCompanionPlatform { self: TLSSocket.type => - private[tls] def forAsync[F[_]]: Resource[F, TLSSocket[F]] = ??? - - private[tls] final class AsyncTLSSocket[F[_]: Async] extends UnsealedTLSSocket[F] { - // Members declared in fs2.io.net.Socket - def endOfInput: F[Unit] = ??? - def endOfOutput: F[Unit] = ??? - def isOpen: F[Boolean] = ??? - def localAddress: F[com.comcast.ip4s.SocketAddress[com.comcast.ip4s.IpAddress]] = ??? - def read(maxBytes: Int): F[Option[fs2.Chunk[Byte]]] = ??? - def readN(numBytes: Int): F[fs2.Chunk[Byte]] = ??? - def reads: fs2.Stream[F, Byte] = ??? - def remoteAddress: F[com.comcast.ip4s.SocketAddress[com.comcast.ip4s.IpAddress]] = ??? - def write(bytes: fs2.Chunk[Byte]): F[Unit] = ??? - def writes: fs2.Pipe[F, Byte, Nothing] = ??? - - // Members declared in fs2.io.net.tls.TLSSocket - def applicationProtocol: F[String] = ??? - def session: F[fs2.io.net.tls.SSLSession] = ??? - } + private[tls] def apply[F[_]: Async]( + socket: Socket[F], + connection: S2nConnection[F] + ): Resource[F, TLSSocket[F]] = + Resource.eval(mk(socket, connection)) <* + Resource.makeFull[F, Unit](poll => poll(connection.handshake))(_ => connection.shutdown) + + def mk[F[_]]( + socket: Socket[F], + connection: S2nConnection[F] + )(implicit F: Async[F]): F[TLSSocket[F]] = + for { + readSem <- Semaphore(1) + writeSem <- Semaphore(1) + } yield new UnsealedTLSSocket[F] { + def write(bytes: Chunk[Byte]): F[Unit] = + writeSem.permit.surround(connection.write(bytes)) + + private def read0(maxBytes: Int): F[Option[Chunk[Byte]]] = + connection.read(maxBytes.toLong) + + def readN(numBytes: Int): F[Chunk[Byte]] = + readSem.permit.use { _ => + def go(acc: Chunk[Byte]): F[Chunk[Byte]] = { + val toRead = numBytes - acc.size + if (toRead <= 0) F.pure(acc) + else + read0(toRead).flatMap { + case Some(chunk) => go(acc ++ chunk) + case None => F.pure(acc) + } + } + go(Chunk.empty) + } + + def read(maxBytes: Int): F[Option[Chunk[Byte]]] = + readSem.permit.surround(read0(maxBytes)) + + def reads: Stream[F, Byte] = + Stream.repeatEval(read(8192)).unNoneTerminate.unchunks + + def writes: Pipe[F, Byte, Nothing] = + _.chunks.foreach(write) + + def endOfOutput: F[Unit] = + socket.endOfOutput + + def endOfInput: F[Unit] = + socket.endOfInput + + def localAddress: F[SocketAddress[IpAddress]] = + socket.localAddress + + def remoteAddress: F[SocketAddress[IpAddress]] = + socket.remoteAddress + + def session: F[SSLSession] = connection.session + + def applicationProtocol: F[String] = connection.applicationProtocol + + def isOpen: F[Boolean] = socket.isOpen + } } diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 85d95f1d7a..9e0361f9ac 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -76,6 +76,8 @@ private[tls] object s2n { def s2n_connection_set_send_cb(conn: Ptr[s2n_connection], send: s2n_recv_fn): CInt = extern + def s2n_get_application_protocol(conn: Ptr[s2n_connection]): Ptr[CChar] = extern + def s2n_negotiate(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = extern def s2n_send( @@ -101,4 +103,12 @@ private[tls] object s2n { def s2n_shutdown(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = extern + def s2n_connection_get_session( + conn: Ptr[s2n_connection], + session: Ptr[Byte], + maxLength: CSize + ): CInt = extern + + def s2n_connection_get_session_length(conn: Ptr[s2n_connection]): CInt = extern + } From 0f9f9629f5d86bee6b2ae6e3df34698f01f6615e Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 17:38:14 +0000 Subject: [PATCH 20/83] a compiling test --- .../src/main/scala/fs2/io/net/Network.scala | 6 +- .../scala/fs2/io/net/NetworkPlatform.scala | 2 + .../main/scala/fs2/io/net/tls/S2nConfig.scala | 11 ++ .../fs2/io/net/tls/TLSContextPlatform.scala | 23 ++- .../scala/fs2/io/net/tls/TLSParameters.scala | 46 ++--- .../src/main/scala/fs2/io/net/tls/s2n.scala | 2 + .../scala/fs2/io/net/tls/TLSSocketSuite.scala | 182 ++++++++++++++++++ .../scala/fs2/io/net/tls/TLSContext.scala | 2 + 8 files changed, 238 insertions(+), 36 deletions(-) create mode 100644 io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala diff --git a/io/native/src/main/scala/fs2/io/net/Network.scala b/io/native/src/main/scala/fs2/io/net/Network.scala index 6c949629e2..a917cc00e1 100644 --- a/io/native/src/main/scala/fs2/io/net/Network.scala +++ b/io/native/src/main/scala/fs2/io/net/Network.scala @@ -23,7 +23,11 @@ package fs2 package io package net -sealed trait Network[F[_]] extends NetworkPlatform[F] with SocketGroup[F] {} +import fs2.io.net.tls.TLSContext + +sealed trait Network[F[_]] extends NetworkPlatform[F] with SocketGroup[F] { + def tlsContext: TLSContext.Builder[F] +} object Network extends NetworkCompanionPlatform { private[net] trait UnsealedNetwork[F[_]] extends Network[F] diff --git a/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala b/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala index 5a8f76299e..9515bf4e47 100644 --- a/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala @@ -29,6 +29,7 @@ import com.comcast.ip4s.{Host, IpAddress, Port, SocketAddress} import java.net.ProtocolFamily import java.nio.channels.AsynchronousChannelGroup +import fs2.io.net.tls.TLSContext private[net] trait NetworkPlatform[F[_]] @@ -56,6 +57,7 @@ private[net] trait NetworkCompanionPlatform { self: Network.type => ): Resource[F, (SocketAddress[IpAddress], Stream[F, Socket[F]])] = globalSocketGroup.serverResource(address, port, options) + def tlsContext: TLSContext.Builder[F] = TLSContext.Builder.forAsync } } diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index bb8acf13f7..6fc06fd9e0 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -21,8 +21,19 @@ package fs2.io.net.tls +import cats.effect.kernel.Resource +import cats.effect.kernel.Sync + import scala.scalanative.unsafe._ import s2n._ final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) {} + +object S2nConfig { + + def apply[F[_]]()(implicit F: Sync[F]): Resource[F, S2nConfig] = for { + cfg <- Resource.make(F.delay(s2n_config_new()))(cfg => F.delay(s2n_config_free(cfg))) + } yield new S2nConfig(cfg) + +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 6a14b3239c..63dba85f2b 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -48,12 +48,25 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => /** Creates a `TLSContext` from an `SSLContext`. */ private[tls] final class AsyncBuilder[F[_]: Async] extends Builder[F] { - // Members declared in fs2.io.net.tls.TLSContext.Builder - def system: F[fs2.io.net.tls.TLSContext[F]] = ??? + def system: F[TLSContext[F]] = ??? - // Members declared in fs2.io.net.tls.TLSContextCompanionPlatform.BuilderPlatform - def fromS2nConfig(ctx: fs2.io.net.tls.S2nConfig): fs2.io.net.tls.TLSContext[F] = ??? - def insecure: F[fs2.io.net.tls.TLSContext[F]] = ??? + def systemResource: Resource[F, TLSContext[F]] = S2nConfig().map(fromS2nConfig(_)) + + def insecure: F[TLSContext[F]] = ??? + + def fromS2nConfig(cfg: S2nConfig): TLSContext[F] = + new UnsealedTLSContext[F] { + def clientBuilder(socket: Socket[F]) = ??? + def serverBuilder(socket: Socket[F]) = ??? + + private def mkSocket( + socket: Socket[F], + clientMode: Boolean, + params: TLSParameters, + logger: TLSLogger[F] + ): Resource[F, TLSSocket[F]] = + S2nConnection(socket, clientMode, cfg, params).flatMap(TLSSocket(socket, _)) + } } } diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala index 00f70d4d38..b451c547e1 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala @@ -24,13 +24,15 @@ package io package net package tls -import CollectionCompat._ +import cats.effect.kernel.Sync -/** Parameters used in creation of a TLS/DTLS session. +import scala.scalanative.unsafe._ + +import s2n._ +import s2nutil._ + +/** Parameters used in creation of an s2n_connection. * See `javax.net.ssl.SSLParameters` for detailed documentation on each parameter. - * - * Note: `applicationProtocols`, `enableRetransmissions`, `maximumPacketSize`, and - * `handshakeApplicationProtocolSelector` require Java 9+. */ sealed trait TLSParameters { // val algorithmConstraints: Option[AlgorithmConstraints] @@ -40,35 +42,19 @@ sealed trait TLSParameters { // val endpointIdentificationAlgorithm: Option[String] // val maximumPacketSize: Option[Int] // val protocols: Option[List[String]] - // val serverNames: Option[List[SNIServerName]] + val serverName: Option[String] // val sniMatchers: Option[List[SNIMatcher]] // val useCipherSuitesOrder: Boolean // val needClientAuth: Boolean // val wantClientAuth: Boolean // val handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] - /** Converts to a `javax.net.ssl.SSLParameters` instance. - * - * `needClientAuth` and `wantClientAuth` are mutually exclusive on `SSLParameters`. If both set on this `TLSParameters`, then `needClientAuth` takes precedence. - */ - // def toSSLParameters: SSLParameters = { - // val p = new SSLParameters() - // algorithmConstraints.foreach(p.setAlgorithmConstraints) - // applicationProtocols.foreach(ap => p.setApplicationProtocols(ap.toArray)) - // cipherSuites.foreach(cs => p.setCipherSuites(cs.toArray)) - // enableRetransmissions.foreach(p.setEnableRetransmissions) - // endpointIdentificationAlgorithm.foreach(p.setEndpointIdentificationAlgorithm) - // maximumPacketSize.foreach(p.setMaximumPacketSize) - // protocols.foreach(ps => p.setProtocols(ps.toArray)) - // serverNames.foreach(sn => p.setServerNames(sn.asJava)) - // sniMatchers.foreach(sm => p.setSNIMatchers(sm.asJava)) - // p.setUseCipherSuitesOrder(useCipherSuitesOrder) - // if (needClientAuth) - // p.setNeedClientAuth(needClientAuth) - // else if (wantClientAuth) - // p.setWantClientAuth(wantClientAuth) - // p - // } + private[tls] def configure[F[_]](conn: Ptr[s2n_connection])(implicit F: Sync[F]): F[Unit] = + F.delay { + Zone { implicit z => + serverName.foreach(sn => guard_(s2n_set_server_name(conn, toCString(sn)))) + } + } } object TLSParameters { @@ -82,7 +68,7 @@ object TLSParameters { // endpointIdentificationAlgorithm: Option[String] = None, // maximumPacketSize: Option[Int] = None, // protocols: Option[List[String]] = None, - // serverNames: Option[List[SNIServerName]] = None, + serverName: Option[String] = None // sniMatchers: Option[List[SNIMatcher]] = None, // useCipherSuitesOrder: Boolean = false, // needClientAuth: Boolean = false, @@ -98,7 +84,7 @@ object TLSParameters { // endpointIdentificationAlgorithm: Option[String], // maximumPacketSize: Option[Int], // protocols: Option[List[String]], - // serverNames: Option[List[SNIServerName]], + serverName: Option[String] // sniMatchers: Option[List[SNIMatcher]], // useCipherSuitesOrder: Boolean, // needClientAuth: Boolean, diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 9e0361f9ac..7932a86171 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -76,6 +76,8 @@ private[tls] object s2n { def s2n_connection_set_send_cb(conn: Ptr[s2n_connection], send: s2n_recv_fn): CInt = extern + def s2n_set_server_name(conn: Ptr[s2n_connection], server_name: Ptr[CChar]): CInt = extern + def s2n_get_application_protocol(conn: Ptr[s2n_connection]): Ptr[CChar] = extern def s2n_negotiate(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = extern diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala new file mode 100644 index 0000000000..4c74895c57 --- /dev/null +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io +package net +package tls + +import scala.concurrent.duration._ + +import cats.effect.{IO, Resource} +import cats.syntax.all._ + +import com.comcast.ip4s._ + +class TLSSocketSuite extends Fs2IoSuite { + val size = 8192 + + group("TLSSocket") { + group("google") { + def googleSetup = + for { + // tlsContext <- Resource.pure( + // Network[IO].tlsContext.fromSecureContext( + // SecureContext(minVersion = protocol.some, maxVersion = protocol.some) + // ) + // ) + tlsContext <- Network[IO].tlsContext.systemResource + socket <- Network[IO].client(SocketAddress(host"google.com", port"443")) + tlsSocket <- tlsContext + .clientBuilder(socket) + .withParameters( + TLSParameters(serverName = "www.google.com".some) + ) + .build + } yield tlsSocket + + val googleDotCom = "GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n" + val httpOk = "HTTP/1.1 200 OK" + + def writesBeforeReading = + test("client writes before reading") { + Stream + .resource(googleSetup) + .flatMap { tlsSocket => + Stream(googleDotCom) + .covary[IO] + .through(text.utf8.encode) + .through(tlsSocket.writes) ++ + Stream.exec(tlsSocket.endOfOutput) ++ + tlsSocket.reads + .through(text.utf8.decode) + .through(text.lines) + } + .head + .compile + .string + .assertEquals(httpOk) + } + + def readsBeforeWriting = + test("client reads before writing") { + Stream + .resource(googleSetup) + .flatMap { socket => + val send = Stream(googleDotCom) + .through(text.utf8.encode) + .through(socket.writes) + val receive = socket.reads + .through(text.utf8.decode) + .through(text.lines) + + receive.concurrently(send.delayBy(100.millis)) + } + .head + .compile + .string + .assertEquals(httpOk) + } + + writesBeforeReading + readsBeforeWriting + } + + // test("echo") { + // val msg = Chunk.array(("Hello, world! " * 20000).getBytes) + + // val setup = for { + // tlsContext <- Resource.eval(testTlsContext) + // addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) + // (serverAddress, server) = addressAndConnections + // client <- Network[IO] + // .client(serverAddress) + // .flatMap( + // tlsContext + // .clientBuilder(_) + // .withParameters( + // TLSParameters(checkServerIdentity = + // Some((sn, _) => Either.cond(sn == "localhost", (), new RuntimeException())) + // ) + // ) + // .build + // ) + // } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client + + // Stream + // .resource(setup) + // .flatMap { case (server, clientSocket) => + // val echoServer = server.map { socket => + // socket.reads.chunks.foreach(socket.write(_)) + // }.parJoinUnbounded + + // val client = + // Stream.exec(clientSocket.write(msg)) ++ + // clientSocket.reads.take(msg.size.toLong) + + // client.concurrently(echoServer) + // } + // .compile + // .to(Chunk) + // .assertEquals(msg) + // } + + // test("error") { + // val msg = Chunk.array(("Hello, world! " * 20000).getBytes) + + // val setup = for { + // tlsContext <- Resource.eval(Network[IO].tlsContext.system) + // addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) + // (serverAddress, server) = addressAndConnections + // client <- Network[IO] + // .client(serverAddress) + // .flatMap( + // tlsContext + // .clientBuilder(_) + // .withParameters( + // TLSParameters(checkServerIdentity = + // Some((sn, _) => Either.cond(sn == "localhost", (), new RuntimeException())) + // ) + // ) + // .build + // ) + // } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client + + // Stream + // .resource(setup) + // .flatMap { case (server, clientSocket) => + // val echoServer = server.map { socket => + // socket.reads.chunks.foreach(socket.write(_)) + // }.parJoinUnbounded + + // val client = + // Stream.exec(clientSocket.write(msg)) ++ + // clientSocket.reads.take(msg.size.toLong) + + // client.concurrently(echoServer) + // } + // .compile + // .to(Chunk) + // .intercept[SSLException] + // } + + } +} diff --git a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala index 71d4d809ec..44ddedde45 100644 --- a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala +++ b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala @@ -71,6 +71,8 @@ object TLSContext extends TLSContextCompanionPlatform { /** Creates a `TLSContext` from the system default `SSLContext`. */ def system: F[TLSContext[F]] + def systemResource: Resource[F, TLSContext[F]] + } object Builder extends BuilderCompanionPlatform { From 6fa5c405c0ebc86ca55f94bdecdddddba6f8b7b1 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 21:08:58 +0000 Subject: [PATCH 21/83] Fix S2nConfig --- io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index 6fc06fd9e0..c0b9365d20 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -27,13 +27,20 @@ import cats.effect.kernel.Sync import scala.scalanative.unsafe._ import s2n._ +import s2nutil._ final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) {} object S2nConfig { + // we must call this *exactly* once, before doing anything else + // since you need an S2nConfig before you can do anything, this seems like the right place + s2n_init() + def apply[F[_]]()(implicit F: Sync[F]): Resource[F, S2nConfig] = for { - cfg <- Resource.make(F.delay(s2n_config_new()))(cfg => F.delay(s2n_config_free(cfg))) + cfg <- Resource.make(F.delay(guard(s2n_config_new())))(cfg => + F.delay(guard(s2n_config_free(cfg))) + ) } yield new S2nConfig(cfg) } From a29a1651b4e50611b825e325bcd565b3b52c45e7 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 21:25:45 +0000 Subject: [PATCH 22/83] Add some missing impls --- .../src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala | 4 +++- io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 63dba85f2b..584b9a3c56 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -56,7 +56,9 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => def fromS2nConfig(cfg: S2nConfig): TLSContext[F] = new UnsealedTLSContext[F] { - def clientBuilder(socket: Socket[F]) = ??? + def clientBuilder(socket: Socket[F]) = + SocketBuilder(mkSocket(socket, true, _, _)) + def serverBuilder(socket: Socket[F]) = ??? private def mkSocket( diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala index b451c547e1..0a7c4235a6 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala @@ -74,7 +74,7 @@ object TLSParameters { // needClientAuth: Boolean = false, // wantClientAuth: Boolean = false, // handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] = None - ): TLSParameters = ??? + ): TLSParameters = DefaultTLSParameters(serverName) private case class DefaultTLSParameters( // algorithmConstraints: Option[AlgorithmConstraints], From b5031a24e116d9a9276e832c86bbd9d2f5bdb7ce Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 21:54:15 +0000 Subject: [PATCH 23/83] Don't throw errors for blocking --- .../src/main/scala/fs2/io/net/tls/s2n.scala | 7 ++++++- .../main/scala/fs2/io/net/tls/s2nutil.scala | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 7932a86171..9c2b2ae723 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -33,6 +33,9 @@ private[tls] object s2n { final val S2N_FAILURE = -1 final val S2N_CALLBACK_BLOCKED = -2 + type s2n_error_type = CUnsignedInt + final val S2N_ERR_T_BLOCKED = 3 + type s2n_mode = CUnsignedInt final val S2N_SERVER = 0 final val S2N_CLIENT = 1 @@ -52,6 +55,8 @@ private[tls] object s2n { def s2n_errno_location(): Ptr[CInt] = extern + def s2n_error_get_type(error: CInt): CInt = extern + def s2n_init(): CInt = extern def s2n_cleanup(): CInt = extern @@ -77,7 +82,7 @@ private[tls] object s2n { def s2n_connection_set_send_cb(conn: Ptr[s2n_connection], send: s2n_recv_fn): CInt = extern def s2n_set_server_name(conn: Ptr[s2n_connection], server_name: Ptr[CChar]): CInt = extern - + def s2n_get_application_protocol(conn: Ptr[s2n_connection]): Ptr[CChar] = extern def s2n_negotiate(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = extern diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala index 42c33d38fb..c26f5e45f7 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala @@ -30,14 +30,22 @@ import s2n._ private[tls] object s2nutil { @alwaysinline def guard_(thunk: => CInt): Unit = - if (thunk != S2N_SUCCESS) - throw new S2nException(!s2n_errno_location()) + if (thunk != S2N_SUCCESS) { + val error = !s2n_errno_location() + val errorType = s2n_error_get_type(error) + if (errorType != S2N_ERR_T_BLOCKED) + throw new S2nException(error) + } @alwaysinline def guard[A](thunk: => CInt): CInt = { val rtn = thunk - if (rtn < 0) - throw new S2nException(!s2n_errno_location()) - else rtn + if (rtn < 0) { + val error = !s2n_errno_location() + val errorType = s2n_error_get_type(error) + if (errorType != S2N_ERR_T_BLOCKED) + throw new S2nException(error) + else rtn + } else rtn } @alwaysinline def guard[A](thunk: => Ptr[A]): Ptr[A] = { From f40954dfab8d53a4dd01899176f679145f946d8d Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 1 Sep 2022 22:48:44 +0000 Subject: [PATCH 24/83] Initialize sendAvailable to true --- io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 795f94eb50..e82b5a6939 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -111,7 +111,7 @@ private[tls] object S2nConnection { .drain .background - sendAvailable <- F.delay(new AtomicBoolean).toResource + sendAvailable <- F.delay(new AtomicBoolean(true)).toResource sendLatch <- F.deferred[Either[Throwable, Unit]].flatMap(F.ref(_)).toResource _ <- Resource.eval { F.delay { From 3bac1427efd7d113fde956b9c26bb8d4d1e39b07 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 2 Sep 2022 00:08:43 +0000 Subject: [PATCH 25/83] rethrow exceptions --- .../main/scala/fs2/io/net/tls/S2nConnection.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index e82b5a6939..dd3d5b2d9d 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -136,9 +136,9 @@ private[tls] object S2nConnection { case S2N_NOT_BLOCKED => F.delay(guard_(s2n_connection_free_handshake(conn))) case S2N_BLOCKED_ON_READ => - recvLatch.get >> go + recvLatch.get.rethrow >> go case S2N_BLOCKED_ON_WRITE => - sendLatch.get >> go + sendLatch.get.rethrow >> go case _ => F.raiseError(new IllegalStateException(s"s2n_negotiate blocked: $blocked")) } @@ -162,7 +162,7 @@ private[tls] object S2nConnection { else F.pure(None) case S2N_BLOCKED_ON_READ => - recvLatch.get >> go(i + readed) + recvLatch.get.rethrow >> go(i + readed) case _ => F.raiseError(new IllegalStateException(s"s2n_recv blocked: $blocked")) } @@ -187,7 +187,7 @@ private[tls] object S2nConnection { val total = i + wrote if (total == n) F.unit else go(total) case S2N_BLOCKED_ON_WRITE => - sendLatch.get >> go(i + wrote) + sendLatch.get.rethrow >> go(i + wrote) case _ => F.raiseError(new IllegalStateException(s"s2n_send blocked: $blocked")) } @@ -210,9 +210,9 @@ private[tls] object S2nConnection { case S2N_NOT_BLOCKED => F.unit case S2N_BLOCKED_ON_READ => - recvLatch.get >> go + recvLatch.get.rethrow >> go case S2N_BLOCKED_ON_WRITE => - sendLatch.get >> go + sendLatch.get.rethrow >> go case _ => F.raiseError(new IllegalStateException(s"s2n_shutdown blocked: $blocked")) } From 259c94504aa715464cff88d297817c570db88620 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 3 Sep 2022 02:01:53 +0000 Subject: [PATCH 26/83] setup sbt-vcpkg --- build.sbt | 15 +++++++++++++-- io/native/src/main/scala/fs2/io/net/tls/s2n.scala | 1 + project/plugins.sbt | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 995d2a7cca..ba85259171 100644 --- a/build.sbt +++ b/build.sbt @@ -223,7 +223,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( name := "fs2-io", tlVersionIntroduced ~= { _.updated("3", "3.1.0") }, - libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.3-53-f37ec8e-SNAPSHOT", + libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.3-53-f37ec8e-SNAPSHOT" ) .jvmSettings( Test / fork := true, @@ -239,8 +239,19 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .nativeSettings( libraryDependencies ++= Seq( "com.armanbilge" %%% "epollcat-net" % "0.0-2f64289-SNAPSHOT" % Test - ) + ), + vcpkgDependencies := Set("s2n"), + nativeConfig := { + val conf = nativeConfig.value + val manager = vcpkgManager.value + conf.withLinkingOptions( + conf.linkingOptions ++ + List(s"-L${manager.files("s2n").libDir}", s"-L${manager.files("openssl").libDir}") + ) + }, + Test / envVars ++= Map("S2N_DONT_MLOCK" -> "1") ) + .nativeEnablePlugins(VcpkgPlugin) .dependsOn(core % "compile->compile;test->test") .jsSettings( mimaBinaryIssueFilters ++= Seq( diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 9c2b2ae723..91a32ba8ae 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -26,6 +26,7 @@ import scala.annotation.nowarn @extern @link("s2n") +@link("crypto") @nowarn private[tls] object s2n { diff --git a/project/plugins.sbt b/project/plugins.sbt index ae1d5b638c..eb42ffc83d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,3 +7,4 @@ addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.2") +addSbtPlugin("com.indoorvivants.vcpkg" % "sbt-vcpkg" % "0.0.6") From 5388727a05fec88bd931352f4b421081cef0969c Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 3 Sep 2022 12:54:39 +0000 Subject: [PATCH 27/83] Actually invoke `vcpkgInstall` :) --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index ba85259171..26999e763b 100644 --- a/build.sbt +++ b/build.sbt @@ -243,6 +243,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) vcpkgDependencies := Set("s2n"), nativeConfig := { val conf = nativeConfig.value + val _ = vcpkgInstall.value val manager = vcpkgManager.value conf.withLinkingOptions( conf.linkingOptions ++ From 95e8828d5a33be1e570b5f4848b6370841f56045 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 3 Sep 2022 21:15:31 +0000 Subject: [PATCH 28/83] Refactor/simplify `S2nConnection` --- .../scala/fs2/io/net/tls/S2nConnection.scala | 196 ++++++------------ 1 file changed, 68 insertions(+), 128 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index dd3d5b2d9d..71e813ab3d 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -23,13 +23,8 @@ package fs2 package io.net package tls -import cats.effect.SyncIO import cats.effect.kernel.Async -import cats.effect.kernel.Deferred -import cats.effect.kernel.Ref import cats.effect.kernel.Resource -import cats.effect.std.Dispatcher -import cats.effect.std.Queue import cats.effect.syntax.all._ import cats.syntax.all._ import scodec.bits.ByteVector @@ -37,7 +32,7 @@ import scodec.bits.ByteVector import java.util.Collections import java.util.IdentityHashMap import java.util.concurrent.atomic.AtomicBoolean -import scala.annotation.switch +import java.util.concurrent.atomic.AtomicReference import scala.scalanative.libc import scala.scalanative.posix import scala.scalanative.unsafe._ @@ -74,48 +69,30 @@ private[tls] object S2nConnection { F.delay(Collections.newSetFromMap[Any](new IdentityHashMap)) )(gcr => F.delay(gcr.clear())) - dispatcher <- Dispatcher.sequential[F] - conn <- Resource.make( F.delay(guard(s2n_connection_new(if (clientMode) S2N_CLIENT.toUInt else S2N_SERVER.toUInt))) )(conn => F.delay(guard_(s2n_connection_free(conn)))) _ <- F.delay(s2n_connection_set_config(conn, config.ptr)).toResource - recvLatch <- F.deferred[Either[Throwable, Unit]].flatMap(F.ref(_)).toResource - recvBuffer <- Ref.in[Resource[F, *], SyncIO, Option[ByteVector]](Some(ByteVector.empty)) - recvQueue <- Queue.synchronous[F, Unit].toResource + readBuffer <- Resource.eval { + F.delay(new AtomicReference[Option[ByteVector]](Some(ByteVector.empty))) + } + readTasks <- F.delay(new AtomicReference[F[Unit]](F.unit)).toResource _ <- Resource.eval { F.delay { - val ctx = RecvCallbackContext(recvBuffer, recvQueue, dispatcher) + val ctx = RecvCallbackContext(readBuffer, readTasks, socket, F) guard_(s2n_connection_set_recv_ctx(conn, toPtr(ctx))) guard_(s2n_connection_set_recv_cb(conn, recvCallback[F](_, _, _))) gcRoot.add(ctx) } } - _ <- socket.reads.chunks - .evalTap(_ => recvQueue.take) - .noneTerminate - .attempt - .foreach { - case Left(ex) => - recvLatch.get.flatMap(_.complete(Left(ex))).void - case Right(chunk) => - recvBuffer.set(chunk.map(_.toByteVector)).to[F] *> - F.deferred[Either[Throwable, Unit]] - .flatMap(recvLatch.getAndSet(_)) - .flatMap(_.complete(Either.unit)) - .void - } - .compile - .drain - .background sendAvailable <- F.delay(new AtomicBoolean(true)).toResource - sendLatch <- F.deferred[Either[Throwable, Unit]].flatMap(F.ref(_)).toResource + writeTasks <- F.delay(new AtomicReference[F[Unit]](F.unit)).toResource _ <- Resource.eval { F.delay { - val ctx = SendCallbackContext(sendAvailable, sendLatch, socket, dispatcher, F) + val ctx = SendCallbackContext(sendAvailable, writeTasks, socket, F) guard_(s2n_connection_set_send_ctx(conn, toPtr(ctx))) guard_(s2n_connection_set_send_cb(conn, sendCallback[F](_, _, _))) gcRoot.add(ctx) @@ -124,103 +101,77 @@ private[tls] object S2nConnection { } yield new S2nConnection[F] { - def handshake = { - def go: F[Unit] = - (recvLatch.get, sendLatch.get).flatMapN { (recvLatch, sendLatch) => - F.delay { - val blocked = stackalloc[s2n_blocked_status]() - guard_(s2n_negotiate(conn, blocked)) - !blocked - }.flatMap { blocked => - (blocked.toInt: @switch) match { - case S2N_NOT_BLOCKED => - F.delay(guard_(s2n_connection_free_handshake(conn))) - case S2N_BLOCKED_ON_READ => - recvLatch.get.rethrow >> go - case S2N_BLOCKED_ON_WRITE => - sendLatch.get.rethrow >> go - case _ => - F.raiseError(new IllegalStateException(s"s2n_negotiate blocked: $blocked")) - } - } - } - - go - } + def handshake = + F.delay { + readTasks.set(F.unit) + writeTasks.set(F.unit) + val blocked = stackalloc[s2n_blocked_status]() + guard_(s2n_negotiate(conn, blocked)) + !blocked + }.productL { + val reads = F.delay(readTasks.get).flatten + val writes = F.delay(writeTasks.get).flatten + reads.both(writes) + }.iterateUntil(_.toInt == S2N_NOT_BLOCKED) *> + F.delay(guard_(s2n_connection_free_handshake(conn))) def read(n: Long) = zone.use { implicit z => F.delay(alloc[Byte](n)).flatMap { buf => - def go(i: Long): F[Option[Chunk[Byte]]] = recvLatch.get.flatMap { recvLatch => - F.delay[F[Option[Chunk[Byte]]]] { + def go(i: Long): F[Option[Chunk[Byte]]] = + F.delay { + readTasks.set(F.unit) val blocked = stackalloc[s2n_blocked_status]() val readed = guard(s2n_recv(conn, buf + i, n - i, blocked)) - ((!blocked).toInt: @switch) match { - case S2N_NOT_BLOCKED => - val total = i + readed + (!blocked, readed) + }.productL(F.delay(readTasks.get).flatten) + .flatMap { case (blocked, readed) => + val total = i + readed + if (blocked.toInt == S2N_NOT_BLOCKED) { if (total > 0) F.pure(Some(Chunk.byteVector(ByteVector.fromPtr(buf, total)))) else F.pure(None) - case S2N_BLOCKED_ON_READ => - recvLatch.get.rethrow >> go(i + readed) - case _ => - F.raiseError(new IllegalStateException(s"s2n_recv blocked: $blocked")) + } else go(total) } - }.flatten - } - go(0) } + } def write(bytes: Chunk[Byte]) = zone.use { implicit z => val n = bytes.size.toLong - F.delay(alloc[Byte](n.toLong)) + F.delay(alloc[Byte](n)) .flatTap(buf => F.delay(bytes.toByteVector.copyToPtr(buf, 0))) .flatMap { buf => - def go(i: Long): F[Unit] = sendLatch.get.flatMap { sendLatch => - F.delay[F[Unit]] { + def go(i: Long): F[Unit] = + F.delay { + writeTasks.set(F.unit) val blocked = stackalloc[s2n_blocked_status]() val wrote = guard(s2n_send(conn, buf + i, n - i, blocked)) - ((!blocked).toInt: @switch) match { - case S2N_NOT_BLOCKED => - val total = i + wrote - if (total == n) F.unit else go(total) - case S2N_BLOCKED_ON_WRITE => - sendLatch.get.rethrow >> go(i + wrote) - case _ => - F.raiseError(new IllegalStateException(s"s2n_send blocked: $blocked")) + (!blocked, wrote) + }.productL(F.delay(writeTasks.get).flatten) + .flatMap { case (blocked, wrote) => + val total = i + wrote + go(total).whenA(blocked.toInt == S2N_NOT_BLOCKED && total == n) } - }.flatten - } go(0) } } - def shutdown = { - def go: F[Unit] = - (recvLatch.get, sendLatch.get).flatMapN { (recvLatch, sendLatch) => - F.delay { - val blocked = stackalloc[s2n_blocked_status]() - guard_(s2n_shutdown(conn, blocked)) - !blocked - }.flatMap { blocked => - (blocked.toInt: @switch) match { - case S2N_NOT_BLOCKED => - F.unit - case S2N_BLOCKED_ON_READ => - recvLatch.get.rethrow >> go - case S2N_BLOCKED_ON_WRITE => - sendLatch.get.rethrow >> go - case _ => - F.raiseError(new IllegalStateException(s"s2n_shutdown blocked: $blocked")) - } - } - } - - go - } + def shutdown = + F.delay { + readTasks.set(F.unit) + writeTasks.set(F.unit) + val blocked = stackalloc[s2n_blocked_status]() + guard_(s2n_shutdown(conn, blocked)) + !blocked + }.productL { + val reads = F.delay(readTasks.get).flatten + val writes = F.delay(writeTasks.get).flatten + reads.both(writes) + }.iterateUntil(_.toInt == S2N_NOT_BLOCKED) + .void def applicationProtocol = F.delay(guard(s2n_get_application_protocol(conn))).map(fromCString(_)) @@ -240,28 +191,27 @@ private[tls] object S2nConnection { } private final case class RecvCallbackContext[F[_]]( - recvBuffer: Ref[SyncIO, Option[ByteVector]], - recvQueue: Queue[F, Unit], - dispatcher: Dispatcher[F] + readBuffer: AtomicReference[Option[ByteVector]], + readTasks: AtomicReference[F[Unit]], + socket: Socket[F], + async: Async[F] ) private def recvCallback[F[_]](ioContext: Ptr[Byte], buf: Ptr[Byte], len: CUnsignedInt): CInt = { val ctx = fromPtr[RecvCallbackContext[F]](ioContext) import ctx._ + implicit val F = async - val read = recvBuffer.modify { - case Some(bytes) => - val (left, right) = bytes.splitAt(len.toLong) - (Some(right), Some(left)) - case None => (None, None) - } + val read = readBuffer.getAndUpdate(_.map(_.drop(len.toLong))).map(_.take(len.toLong)) - read.unsafeRunSync() match { + read match { case Some(bytes) if bytes.nonEmpty => bytes.copyToPtr(buf, 0) bytes.length.toInt case Some(_) => - dispatcher.unsafeRunAndForget(recvQueue.offer(())) + val readTask = + socket.read(len.toInt).flatMap(b => F.delay(readBuffer.set(b.map(_.toByteVector)))).void + readTasks.getAndUpdate(_ *> readTask) libc.errno.errno = posix.errno.EWOULDBLOCK S2N_FAILURE case None => 0 @@ -270,9 +220,8 @@ private[tls] object S2nConnection { private final case class SendCallbackContext[F[_]]( sendAvailable: AtomicBoolean, - sendLatch: Ref[F, Deferred[F, Either[Throwable, Unit]]], + writeTasks: AtomicReference[F[Unit]], socket: Socket[F], - dispatcher: Dispatcher[F], async: Async[F] ) @@ -282,19 +231,10 @@ private[tls] object S2nConnection { implicit val F = async if (sendAvailable.getAndSet(false)) { - dispatcher.unsafeRunAndForget( - socket - .write(Chunk.byteVector(ByteVector.fromPtr(buf, len.toLong))) - .redeemWith( - ex => sendLatch.get.flatMap(_.complete(Left(ex))), - _ => - F.deferred[Either[Throwable, Unit]] - .flatMap(sendLatch.getAndSet(_)) - .flatMap(oldLatch => - F.delay(sendAvailable.set(true)) *> oldLatch.complete(Either.unit) - ) - ) - ) + val writeTask = + socket.write(Chunk.byteVector(ByteVector.fromPtr(buf, len.toLong))) *> + F.delay(sendAvailable.set(true)) + writeTasks.getAndUpdate(_ *> writeTask) len.toInt } else { libc.errno.errno = posix.errno.EWOULDBLOCK From f102a5e23520db1285bf7c4adce06da16a9d0e97 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 3 Sep 2022 21:23:52 +0000 Subject: [PATCH 29/83] Address warning --- io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index c0b9365d20..fed3f5f4bd 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -39,7 +39,7 @@ object S2nConfig { def apply[F[_]]()(implicit F: Sync[F]): Resource[F, S2nConfig] = for { cfg <- Resource.make(F.delay(guard(s2n_config_new())))(cfg => - F.delay(guard(s2n_config_free(cfg))) + F.delay(guard_(s2n_config_free(cfg))) ) } yield new S2nConfig(cfg) From 1225d5669e047a203c9f66b6ab738aac551654d0 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 00:47:05 +0000 Subject: [PATCH 30/83] Attempt shutdown but don't throw --- .../src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala index 52f4d05211..507ec2b5e0 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSSocketPlatform.scala @@ -40,7 +40,9 @@ private[tls] trait TLSSocketCompanionPlatform { self: TLSSocket.type => connection: S2nConnection[F] ): Resource[F, TLSSocket[F]] = Resource.eval(mk(socket, connection)) <* - Resource.makeFull[F, Unit](poll => poll(connection.handshake))(_ => connection.shutdown) + Resource.makeFull[F, Unit](poll => poll(connection.handshake))(_ => + connection.shutdown.attempt.void + ) def mk[F[_]]( socket: Socket[F], From 26d661aff4dc2b66811f8571fc907ff2763e84f2 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 00:53:46 +0000 Subject: [PATCH 31/83] Handle neg rtn from recv/send --- io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 71e813ab3d..ef70436a98 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -122,7 +122,7 @@ private[tls] object S2nConnection { readTasks.set(F.unit) val blocked = stackalloc[s2n_blocked_status]() val readed = guard(s2n_recv(conn, buf + i, n - i, blocked)) - (!blocked, readed) + (!blocked, Math.max(readed, 0)) }.productL(F.delay(readTasks.get).flatten) .flatMap { case (blocked, readed) => val total = i + readed @@ -148,7 +148,7 @@ private[tls] object S2nConnection { writeTasks.set(F.unit) val blocked = stackalloc[s2n_blocked_status]() val wrote = guard(s2n_send(conn, buf + i, n - i, blocked)) - (!blocked, wrote) + (!blocked, Math.max(wrote, 0)) }.productL(F.delay(writeTasks.get).flatten) .flatMap { case (blocked, wrote) => val total = i + wrote From 1c595757cc41a36cebbd4037737d33013da894e3 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 01:45:32 +0000 Subject: [PATCH 32/83] Fix loop condition --- io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index ef70436a98..0f2d54316f 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -152,7 +152,7 @@ private[tls] object S2nConnection { }.productL(F.delay(writeTasks.get).flatten) .flatMap { case (blocked, wrote) => val total = i + wrote - go(total).whenA(blocked.toInt == S2N_NOT_BLOCKED && total == n) + go(total).unlessA(blocked.toInt == S2N_NOT_BLOCKED && total == n) } go(0) From c77c66cffefca95b17174c0bd08a65da57b458b0 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 04:19:41 +0000 Subject: [PATCH 33/83] Use brew-installed s2n --- build.sbt | 26 +++++++++++++++----------- project/plugins.sbt | 1 - 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/build.sbt b/build.sbt index 26999e763b..955123a371 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,10 @@ ThisBuild / crossScalaVersions := Seq("3.1.3", "2.12.16", NewScala) ThisBuild / tlVersionIntroduced := Map("3" -> "3.0.3") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) +val isLinux = { + val osName = Option(System.getProperty("os.name")) + osName.exists(_.toLowerCase().contains("linux")) +} ThisBuild / tlCiReleaseBranches := List("main", "series/2.5.x") @@ -240,19 +244,19 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies ++= Seq( "com.armanbilge" %%% "epollcat-net" % "0.0-2f64289-SNAPSHOT" % Test ), - vcpkgDependencies := Set("s2n"), - nativeConfig := { - val conf = nativeConfig.value - val _ = vcpkgInstall.value - val manager = vcpkgManager.value - conf.withLinkingOptions( - conf.linkingOptions ++ - List(s"-L${manager.files("s2n").libDir}", s"-L${manager.files("openssl").libDir}") - ) + nativeConfig ~= { c => + if (isLinux) { // brew-installed s2n + c.withLinkingOptions(c.linkingOptions :+ "-L/home/linuxbrew/.linuxbrew/lib") + } else c }, - Test / envVars ++= Map("S2N_DONT_MLOCK" -> "1") + Test / envVars ++= { + val ldLibPath = + if (isLinux) + Map("LD_LIBRARY_PATH" -> "/home/linuxbrew/.linuxbrew/lib") + else Map.empty + Map("S2N_DONT_MLOCK" -> "1") ++ ldLibPath + } ) - .nativeEnablePlugins(VcpkgPlugin) .dependsOn(core % "compile->compile;test->test") .jsSettings( mimaBinaryIssueFilters ++= Seq( diff --git a/project/plugins.sbt b/project/plugins.sbt index eb42ffc83d..ae1d5b638c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,4 +7,3 @@ addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.2") -addSbtPlugin("com.indoorvivants.vcpkg" % "sbt-vcpkg" % "0.0.6") From 216a7211c6a95af985b4da5b33c354e597c835bc Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 04:28:06 +0000 Subject: [PATCH 34/83] Set `TLSParameters` on `S2nConnection` --- io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 0f2d54316f..3f1f4a8d2a 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -74,6 +74,7 @@ private[tls] object S2nConnection { )(conn => F.delay(guard_(s2n_connection_free(conn)))) _ <- F.delay(s2n_connection_set_config(conn, config.ptr)).toResource + _ <- parameters.configure(conn).toResource readBuffer <- Resource.eval { F.delay(new AtomicReference[Option[ByteVector]](Some(ByteVector.empty))) From 60fdb0d3ba3cbb24df5a0dfbfbaa71c1f8e385e5 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 15:23:52 +0000 Subject: [PATCH 35/83] Flesh out `S2nConfig` --- .../fs2/io/net/tls/CertChainAndKey.scala | 39 ++++++ .../main/scala/fs2/io/net/tls/S2nConfig.scala | 127 +++++++++++++++++- .../scala/fs2/io/net/tls/S2nConnection.scala | 6 +- .../fs2/io/net/tls/TLSContextPlatform.scala | 6 +- .../src/main/scala/fs2/io/net/tls/s2n.scala | 41 ++++++ .../main/scala/fs2/io/net/tls/s2nutil.scala | 8 ++ 6 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala diff --git a/io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala b/io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala new file mode 100644 index 0000000000..ecc69ca17b --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala @@ -0,0 +1,39 @@ +package fs2.io.net.tls + +import cats.effect.kernel.Resource +import cats.effect.kernel.Sync +import scodec.bits.ByteVector + +import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ + +import s2n._ +import s2nutil._ + +final class CertChainAndKey private (chainPem: ByteVector, privateKeyPem: ByteVector) { + private[tls] def toS2n[F[_]](implicit F: Sync[F]): Resource[F, Ptr[s2n_cert_chain_and_key]] = + Resource + .make(F.delay(guard(s2n_cert_chain_and_key_new())))(ccak => + F.delay(guard_(s2n_cert_chain_and_key_free(ccak))) + ) + .evalTap { certChainAndKey => + F.delay { + Zone { implicit z => + guard_ { + s2n_cert_chain_and_key_load_pem_bytes( + certChainAndKey, + chainPem.toPtr, + chainPem.length.toUInt, + privateKeyPem.toPtr, + privateKeyPem.length.toUInt + ) + } + } + } + } +} + +object CertChainAndKey { + def apply(chainPem: ByteVector, privateKeyPem: ByteVector): CertChainAndKey = + new CertChainAndKey(chainPem, privateKeyPem) +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index fed3f5f4bd..b411e05cf0 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -21,10 +21,15 @@ package fs2.io.net.tls +import cats.effect.SyncIO import cats.effect.kernel.Resource import cats.effect.kernel.Sync +import cats.effect.syntax.all._ +import cats.syntax.all._ +import scodec.bits.ByteVector import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ import s2n._ import s2nutil._ @@ -37,10 +42,122 @@ object S2nConfig { // since you need an S2nConfig before you can do anything, this seems like the right place s2n_init() - def apply[F[_]]()(implicit F: Sync[F]): Resource[F, S2nConfig] = for { - cfg <- Resource.make(F.delay(guard(s2n_config_new())))(cfg => - F.delay(guard_(s2n_config_free(cfg))) - ) - } yield new S2nConfig(cfg) + def builder: Builder = new BuilderImpl + + sealed abstract class Builder { + def build[F[_]: Sync]: Resource[F, S2nConfig] + + def withCertChainAndKeysToStore(certKeyPairs: List[CertChainAndKey]): Builder + def withPemsToTrustStore(pems: List[String]): Builder + def withWipedTrustStore: Builder + def withSendBufferSize(size: Int): Builder + def withVerifyHostCallback(cb: String => SyncIO[Boolean]): Builder + def withDisabledX509Verification: Builder + def withMaxCertChainDepth(maxDepth: Short): Builder + def withDHParams(dhparams: String): Builder + def withCipherPreferences(version: String): Builder + } + + private final case class BuilderImpl( + certKeyPairs: List[CertChainAndKey] = Nil, + pems: List[String] = Nil, + wipedTrustStore: Boolean = false, + sendBufferSize: Option[Int] = None, + verifyHostCallback: Option[String => SyncIO[Boolean]] = None, + disabledX509Verification: Boolean = false, + maxCertChainDepth: Option[Short] = None, + dhParams: Option[String] = None, + cipherPreferences: Option[String] = None + ) extends Builder { + + def build[F[_]](implicit F: Sync[F]): Resource[F, S2nConfig] = for { + gcRoot <- mkGcRoot[F] + + cfg <- Resource.make(F.delay(guard(s2n_config_new())))(cfg => + F.delay(guard_(s2n_config_free(cfg))) + ) + + _ <- F.delay(guard_(s2n_config_wipe_trust_store(cfg))).whenA(wipedTrustStore).toResource + + _ <- certKeyPairs.traverse_ { pair => + pair.toS2n.evalMap { ptr => + F.delay(guard_(s2n_config_add_cert_chain_and_key_to_store(cfg, ptr))) + } + } + + _ <- pems.traverse_ { pem => + F.delay { + Zone { implicit z => + guard_(s2n_config_add_pem_to_trust_store(cfg, toCString(pem))) + } + } + }.toResource + + _ <- sendBufferSize + .traverse(size => F.delay(guard_(s2n_config_set_send_buffer_size(cfg, size.toUInt)))) + .toResource + + _ <- verifyHostCallback.traverse_ { cb => + F.delay(gcRoot.add(cb)) *> + F.delay { + guard_(s2n_config_set_verify_host_callback(cfg, s2nVerifyHostFn(_, _, _), toPtr(cb))) + } + }.toResource + + _ <- F + .delay(guard_(s2n_config_disable_x509_verification(cfg))) + .whenA(disabledX509Verification) + .toResource + + _ <- maxCertChainDepth.traverse_ { depth => + F.delay(guard_(s2n_config_set_max_cert_chain_depth(cfg, depth.toUShort))) + }.toResource + + _ <- dhParams.traverse_ { pem => + F.delay { + Zone { implicit z => + guard_(s2n_config_add_dhparams(cfg, toCString(pem))) + } + } + }.toResource + + _ <- cipherPreferences.traverse_ { version => + F.delay { + Zone { implicit z => + guard_(s2n_config_set_cipher_preferences(cfg, toCString(version))) + } + } + }.toResource + } yield new S2nConfig(cfg) + + def withCertChainAndKeysToStore(certKeyPairs: List[CertChainAndKey]): Builder = + copy(certKeyPairs = certKeyPairs) + + def withPemsToTrustStore(pems: List[String]): Builder = + copy(pems = pems) + + def withWipedTrustStore: Builder = copy(wipedTrustStore = true) + + def withSendBufferSize(size: Int): Builder = copy(sendBufferSize = Some(size)) + + def withVerifyHostCallback(cb: String => SyncIO[Boolean]): Builder = + copy(verifyHostCallback = Some(cb)) + + def withDisabledX509Verification: Builder = copy(disabledX509Verification = true) + + def withMaxCertChainDepth(maxDepth: Short): Builder = copy(maxCertChainDepth = Some(maxDepth)) + + def withDHParams(dhparams: String): Builder = copy(dhParams = Some(dhparams)) + + def withCipherPreferences(version: String): Builder = copy(cipherPreferences = Some(version)) + + } + + private def s2nVerifyHostFn(hostName: Ptr[CChar], hostNameLen: CSize, data: Ptr[Byte]): Byte = { + val cb = fromPtr[String => SyncIO[Boolean]](data) + val hn = ByteVector.fromPtr(hostName, hostNameLen.toLong).decodeAsciiLenient + val trust = cb(hn).unsafeRunSync() + if (trust) 1 else 0 + } } diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 3f1f4a8d2a..87609fd02d 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -29,8 +29,6 @@ import cats.effect.syntax.all._ import cats.syntax.all._ import scodec.bits.ByteVector -import java.util.Collections -import java.util.IdentityHashMap import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference import scala.scalanative.libc @@ -65,9 +63,7 @@ private[tls] object S2nConnection { parameters: TLSParameters )(implicit F: Async[F]): Resource[F, S2nConnection[F]] = for { - gcRoot <- Resource.make( - F.delay(Collections.newSetFromMap[Any](new IdentityHashMap)) - )(gcr => F.delay(gcr.clear())) + gcRoot <- mkGcRoot conn <- Resource.make( F.delay(guard(s2n_connection_new(if (clientMode) S2N_CLIENT.toUInt else S2N_SERVER.toUInt))) diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 584b9a3c56..da95b5ffe2 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -50,10 +50,14 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => private[tls] final class AsyncBuilder[F[_]: Async] extends Builder[F] { def system: F[TLSContext[F]] = ??? - def systemResource: Resource[F, TLSContext[F]] = S2nConfig().map(fromS2nConfig(_)) + def systemResource: Resource[F, TLSContext[F]] = + S2nConfig.builder.build.map(fromS2nConfig(_)) def insecure: F[TLSContext[F]] = ??? + def insecureResource: Resource[F, TLSContext[F]] = + S2nConfig.builder.withDisabledX509Verification.build.map(fromS2nConfig(_)) + def fromS2nConfig(cfg: S2nConfig): TLSContext[F] = new UnsealedTLSContext[F] { def clientBuilder(socket: Socket[F]) = diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 91a32ba8ae..7e5f118b48 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -50,10 +50,13 @@ private[tls] object s2n { type s2n_config type s2n_connection + type s2n_cert_chain_and_key type s2n_recv_fn = CFuncPtr3[Ptr[Byte], Ptr[Byte], CUnsignedInt, CInt] type s2n_send_fn = CFuncPtr3[Ptr[Byte], Ptr[Byte], CUnsignedInt, CInt] + type s2n_verify_host_fn = CFuncPtr3[Ptr[CChar], CSize, Ptr[Byte], Byte] + def s2n_errno_location(): Ptr[CInt] = extern def s2n_error_get_type(error: CInt): CInt = extern @@ -66,8 +69,46 @@ private[tls] object s2n { def s2n_config_free(config: Ptr[s2n_config]): CInt = extern + def s2n_cert_chain_and_key_new(): Ptr[s2n_cert_chain_and_key] = extern + + def s2n_cert_chain_and_key_load_pem_bytes( + chain_and_key: Ptr[s2n_cert_chain_and_key], + chain_pem: Ptr[Byte], + chain_pem_len: CUnsignedInt, + private_key_pem: Ptr[Byte], + private_key_pem_len: CUnsignedInt + ): CInt = extern + + def s2n_cert_chain_and_key_free(cert_and_key: Ptr[s2n_cert_chain_and_key]): CInt = extern + + def s2n_config_add_cert_chain_and_key_to_store( + config: Ptr[s2n_config], + cert_key_pair: Ptr[s2n_cert_chain_and_key] + ): CInt = extern + + def s2n_config_add_pem_to_trust_store(config: Ptr[s2n_config], pem: Ptr[CChar]): CInt = extern + + def s2n_config_wipe_trust_store(config: Ptr[s2n_config]): CInt = extern + + def s2n_config_set_send_buffer_size(config: Ptr[s2n_config], size: CUnsignedInt): CInt = extern + + def s2n_config_set_verify_host_callback( + config: Ptr[s2n_config], + cb: s2n_verify_host_fn, + data: Ptr[Byte] + ): CInt = extern + def s2n_config_disable_x509_verification(config: Ptr[s2n_config]): CInt = extern + def s2n_config_set_max_cert_chain_depth( + config: Ptr[s2n_config], + max_depth: CUnsignedShort + ): CInt = extern + + def s2n_config_add_dhparams(config: Ptr[s2n_config], dhparams_pem: Ptr[CChar]): CInt = extern + + def s2n_config_set_cipher_preferences(config: Ptr[s2n_config], version: Ptr[CChar]): CInt = extern + def s2n_strerror(error: CInt, lang: Ptr[CChar]): Ptr[CChar] = extern def s2n_connection_new(mode: s2n_mode): Ptr[s2n_connection] = extern diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala index c26f5e45f7..828f19957e 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala @@ -27,6 +27,10 @@ import scala.scalanative.runtime.Intrinsics import scala.scalanative.unsafe._ import s2n._ +import cats.effect.kernel.Resource +import cats.effect.kernel.Sync +import java.util.Collections +import java.util.IdentityHashMap private[tls] object s2nutil { @alwaysinline def guard_(thunk: => CInt): Unit = @@ -60,4 +64,8 @@ private[tls] object s2nutil { @alwaysinline def fromPtr[A](ptr: Ptr[Byte]): A = Intrinsics.castRawPtrToObject(runtime.toRawPtr(ptr)).asInstanceOf[A] + + def mkGcRoot[F[_]](implicit F: Sync[F]) = Resource.make( + F.delay(Collections.newSetFromMap[Any](new IdentityHashMap)) + )(gcr => F.delay(gcr.clear())) } From 7a2e079936171aa61463d2802c82edeb78747f93 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 17:07:42 +0000 Subject: [PATCH 36/83] Flesh out `TLSParameters` --- .../scala/fs2/io/net/tls/CertAuthType.scala | 29 ++++ .../fs2/io/net/tls/CertChainAndKey.scala | 21 +++ .../main/scala/fs2/io/net/tls/S2nConfig.scala | 15 +- .../scala/fs2/io/net/tls/S2nConnection.scala | 2 +- .../scala/fs2/io/net/tls/TLSParameters.scala | 135 ++++++++++++------ .../src/main/scala/fs2/io/net/tls/s2n.scala | 25 ++++ .../main/scala/fs2/io/net/tls/s2nutil.scala | 18 ++- 7 files changed, 185 insertions(+), 60 deletions(-) create mode 100644 io/native/src/main/scala/fs2/io/net/tls/CertAuthType.scala diff --git a/io/native/src/main/scala/fs2/io/net/tls/CertAuthType.scala b/io/native/src/main/scala/fs2/io/net/tls/CertAuthType.scala new file mode 100644 index 0000000000..83c3c1f2f1 --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/tls/CertAuthType.scala @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io.net.tls + +sealed abstract class CertAuthType +object CertAuthType { + object None extends CertAuthType + object Required extends CertAuthType + object Optional extends CertAuthType +} diff --git a/io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala b/io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala index ecc69ca17b..7fa9153ead 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/CertChainAndKey.scala @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + package fs2.io.net.tls import cats.effect.kernel.Resource diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index b411e05cf0..a5ec293cf3 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -26,7 +26,6 @@ import cats.effect.kernel.Resource import cats.effect.kernel.Sync import cats.effect.syntax.all._ import cats.syntax.all._ -import scodec.bits.ByteVector import scala.scalanative.unsafe._ import scala.scalanative.unsigned._ @@ -34,7 +33,12 @@ import scala.scalanative.unsigned._ import s2n._ import s2nutil._ -final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) {} +/** + * An `s2n_config`. + * + * See [[https://github.com/aws/s2n-tls/ s2n-tls]] for detailed documentation. + */ +final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) object S2nConfig { @@ -153,11 +157,4 @@ object S2nConfig { } - private def s2nVerifyHostFn(hostName: Ptr[CChar], hostNameLen: CSize, data: Ptr[Byte]): Byte = { - val cb = fromPtr[String => SyncIO[Boolean]](data) - val hn = ByteVector.fromPtr(hostName, hostNameLen.toLong).decodeAsciiLenient - val trust = cb(hn).unsafeRunSync() - if (trust) 1 else 0 - } - } diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 87609fd02d..9b20c918a5 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -70,7 +70,7 @@ private[tls] object S2nConnection { )(conn => F.delay(guard_(s2n_connection_free(conn)))) _ <- F.delay(s2n_connection_set_config(conn, config.ptr)).toResource - _ <- parameters.configure(conn).toResource + _ <- parameters.configure(conn) readBuffer <- Resource.eval { F.delay(new AtomicReference[Option[ByteVector]](Some(ByteVector.empty))) diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala index 0a7c4235a6..536665564d 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSParameters.scala @@ -24,71 +24,114 @@ package io package net package tls +import cats.effect.SyncIO +import cats.effect.kernel.Resource import cats.effect.kernel.Sync +import cats.effect.syntax.all._ +import cats.syntax.all._ +import scodec.bits.ByteVector import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ import s2n._ import s2nutil._ /** Parameters used in creation of an s2n_connection. - * See `javax.net.ssl.SSLParameters` for detailed documentation on each parameter. + * See [[https://github.com/aws/s2n-tls/ s2n-tls]] for detailed documentation on each parameter. */ sealed trait TLSParameters { - // val algorithmConstraints: Option[AlgorithmConstraints] - // val applicationProtocols: Option[List[String]] - // val cipherSuites: Option[List[String]] - // val enableRetransmissions: Option[Boolean] - // val endpointIdentificationAlgorithm: Option[String] - // val maximumPacketSize: Option[Int] - // val protocols: Option[List[String]] + val protocolPreferences: Option[List[String]] + val cipherPreferences: Option[String] val serverName: Option[String] - // val sniMatchers: Option[List[SNIMatcher]] - // val useCipherSuitesOrder: Boolean - // val needClientAuth: Boolean - // val wantClientAuth: Boolean - // val handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] + val verifyHostCallback: Option[String => SyncIO[Boolean]] + val clientAuthType: Option[CertAuthType] - private[tls] def configure[F[_]](conn: Ptr[s2n_connection])(implicit F: Sync[F]): F[Unit] = - F.delay { - Zone { implicit z => - serverName.foreach(sn => guard_(s2n_set_server_name(conn, toCString(sn)))) - } - } + private[tls] def configure[F[_]]( + conn: Ptr[s2n_connection] + )(implicit F: Sync[F]): Resource[F, Unit] = + for { + gcRoot <- mkGcRoot + + _ <- protocolPreferences.toList.flatten.traverse { protocol => + ByteVector.encodeAscii(protocol).liftTo[F].flatMap { protocolBytes => + F.delay { + Zone { implicit z => + guard_ { + s2n_connection_append_protocol_preference( + conn, + protocolBytes.toPtr, + protocolBytes.length.toByte + ) + } + } + } + } + }.toResource + + _ <- cipherPreferences.traverse_ { version => + F.delay { + Zone { implicit z => + guard_(s2n_connection_set_cipher_preferences(conn, toCString(version))) + } + } + }.toResource + + _ <- serverName.traverse { sn => + F.delay { + Zone { implicit z => + guard_(s2n_set_server_name(conn, toCString(sn))) + } + } + }.toResource + + _ <- verifyHostCallback.traverse_ { cb => + F.delay(gcRoot.add(cb)) *> + F.delay { + guard_( + s2n_connection_set_verify_host_callback( + conn, + s2nVerifyHostFn(_, _, _), + toPtr(cb) + ) + ) + } + }.toResource + + _ <- clientAuthType.traverse_ { authType => + val ord = authType match { + case CertAuthType.None => S2N_CERT_AUTH_NONE + case CertAuthType.Optional => S2N_CERT_AUTH_OPTIONAL + case CertAuthType.Required => S2N_CERT_AUTH_REQUIRED + } + F.delay(guard_(s2n_connection_set_client_auth_type(conn, ord.toUInt))) + }.toResource + } yield () } object TLSParameters { val Default: TLSParameters = TLSParameters() def apply( - // algorithmConstraints: Option[AlgorithmConstraints] = None, - // applicationProtocols: Option[List[String]] = None, - // cipherSuites: Option[List[String]] = None, - // enableRetransmissions: Option[Boolean] = None, - // endpointIdentificationAlgorithm: Option[String] = None, - // maximumPacketSize: Option[Int] = None, - // protocols: Option[List[String]] = None, - serverName: Option[String] = None - // sniMatchers: Option[List[SNIMatcher]] = None, - // useCipherSuitesOrder: Boolean = false, - // needClientAuth: Boolean = false, - // wantClientAuth: Boolean = false, - // handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] = None - ): TLSParameters = DefaultTLSParameters(serverName) + protocolPreferences: Option[List[String]] = None, + cipherPreferences: Option[String] = None, + serverName: Option[String] = None, + verifyHostCallback: Option[String => SyncIO[Boolean]] = None, + clientAuthType: Option[CertAuthType] = None + ): TLSParameters = DefaultTLSParameters( + protocolPreferences, + cipherPreferences, + serverName, + verifyHostCallback, + clientAuthType + ) private case class DefaultTLSParameters( - // algorithmConstraints: Option[AlgorithmConstraints], - // applicationProtocols: Option[List[String]], - // cipherSuites: Option[List[String]], - // enableRetransmissions: Option[Boolean], - // endpointIdentificationAlgorithm: Option[String], - // maximumPacketSize: Option[Int], - // protocols: Option[List[String]], - serverName: Option[String] - // sniMatchers: Option[List[SNIMatcher]], - // useCipherSuitesOrder: Boolean, - // needClientAuth: Boolean, - // wantClientAuth: Boolean, - // handshakeApplicationProtocolSelector: Option[(SSLEngine, List[String]) => String] + protocolPreferences: Option[List[String]], + cipherPreferences: Option[String], + serverName: Option[String], + verifyHostCallback: Option[String => SyncIO[Boolean]], + clientAuthType: Option[CertAuthType] ) extends TLSParameters + } diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 7e5f118b48..5f2e023bb6 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -48,6 +48,11 @@ private[tls] object s2n { final val S2N_BLOCKED_ON_APPLICATION_INPUT = 3 final val S2N_BLOCKED_ON_EARLY_DATA = 4 + type s2n_cert_auth_type = CUnsignedInt + final val S2N_CERT_AUTH_NONE = 0 + final val S2N_CERT_AUTH_REQUIRED = 1 + final val S2N_CERT_AUTH_OPTIONAL = 2 + type s2n_config type s2n_connection type s2n_cert_chain_and_key @@ -123,6 +128,21 @@ private[tls] object s2n { def s2n_connection_set_send_cb(conn: Ptr[s2n_connection], send: s2n_recv_fn): CInt = extern + def s2n_connection_set_verify_host_callback( + conn: Ptr[s2n_connection], + cb: s2n_verify_host_fn, + data: Ptr[Byte] + ): CInt = extern + + def s2n_connection_set_cipher_preferences(conn: Ptr[s2n_connection], version: Ptr[CChar]): CInt = + extern + + def s2n_connection_append_protocol_preference( + conn: Ptr[s2n_connection], + protocol: Ptr[Byte], + protocol_len: Byte + ): CInt = extern + def s2n_set_server_name(conn: Ptr[s2n_connection], server_name: Ptr[CChar]): CInt = extern def s2n_get_application_protocol(conn: Ptr[s2n_connection]): Ptr[CChar] = extern @@ -152,6 +172,11 @@ private[tls] object s2n { def s2n_shutdown(conn: Ptr[s2n_connection], blocked: Ptr[s2n_blocked_status]): CInt = extern + def s2n_connection_set_client_auth_type( + conn: Ptr[s2n_connection], + client_auth_type: s2n_cert_auth_type + ): CInt = extern + def s2n_connection_get_session( conn: Ptr[s2n_connection], session: Ptr[Byte], diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala index 828f19957e..8e2e33c8c9 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2nutil.scala @@ -21,16 +21,19 @@ package fs2.io.net.tls +import cats.effect.SyncIO +import cats.effect.kernel.Resource +import cats.effect.kernel.Sync +import scodec.bits.ByteVector + +import java.util.Collections +import java.util.IdentityHashMap import scala.scalanative.annotation.alwaysinline import scala.scalanative.runtime import scala.scalanative.runtime.Intrinsics import scala.scalanative.unsafe._ import s2n._ -import cats.effect.kernel.Resource -import cats.effect.kernel.Sync -import java.util.Collections -import java.util.IdentityHashMap private[tls] object s2nutil { @alwaysinline def guard_(thunk: => CInt): Unit = @@ -68,4 +71,11 @@ private[tls] object s2nutil { def mkGcRoot[F[_]](implicit F: Sync[F]) = Resource.make( F.delay(Collections.newSetFromMap[Any](new IdentityHashMap)) )(gcr => F.delay(gcr.clear())) + + def s2nVerifyHostFn(hostName: Ptr[CChar], hostNameLen: CSize, data: Ptr[Byte]): Byte = { + val cb = fromPtr[String => SyncIO[Boolean]](data) + val hn = ByteVector.fromPtr(hostName, hostNameLen.toLong).decodeAsciiLenient + val trust = cb(hn).unsafeRunSync() + if (trust) 1 else 0 + } } From adcce7cb9580bef85fc1b1a62b4fce18458545c9 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 17:42:10 +0000 Subject: [PATCH 37/83] Progress on TLS suite --- .../scala/fs2/io/net/tls/TLSSocketSuite.scala | 108 ++++++++++-------- .../test/scala/fs2/io/net/tls/TLSSuite.scala | 44 +++++++ io/shared/src/test/resources/cert.pem | 21 ++++ io/shared/src/test/resources/key.pem | 28 +++++ 4 files changed, 155 insertions(+), 46 deletions(-) create mode 100644 io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala create mode 100644 io/shared/src/test/resources/cert.pem create mode 100644 io/shared/src/test/resources/key.pem diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 4c74895c57..0869fc9bb8 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -31,24 +31,22 @@ import cats.syntax.all._ import com.comcast.ip4s._ -class TLSSocketSuite extends Fs2IoSuite { +class TLSSocketSuite extends TLSSuite { val size = 8192 group("TLSSocket") { group("google") { - def googleSetup = + def googleSetup(version: String) = for { - // tlsContext <- Resource.pure( - // Network[IO].tlsContext.fromSecureContext( - // SecureContext(minVersion = protocol.some, maxVersion = protocol.some) - // ) - // ) tlsContext <- Network[IO].tlsContext.systemResource socket <- Network[IO].client(SocketAddress(host"google.com", port"443")) tlsSocket <- tlsContext .clientBuilder(socket) .withParameters( - TLSParameters(serverName = "www.google.com".some) + TLSParameters( + serverName = "www.google.com".some, + cipherPreferences = version.some + ) ) .build } yield tlsSocket @@ -56,16 +54,16 @@ class TLSSocketSuite extends Fs2IoSuite { val googleDotCom = "GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n" val httpOk = "HTTP/1.1 200 OK" - def writesBeforeReading = - test("client writes before reading") { + def writesBeforeReading(protocol: String) = + test(s"$protocol - client writes before reading") { Stream - .resource(googleSetup) + .resource(googleSetup(protocol)) .flatMap { tlsSocket => Stream(googleDotCom) .covary[IO] .through(text.utf8.encode) - .through(tlsSocket.writes) ++ - Stream.exec(tlsSocket.endOfOutput) ++ + .through(tlsSocket.writes) + .onFinalize(tlsSocket.endOfOutput) ++ tlsSocket.reads .through(text.utf8.decode) .through(text.lines) @@ -76,10 +74,10 @@ class TLSSocketSuite extends Fs2IoSuite { .assertEquals(httpOk) } - def readsBeforeWriting = - test("client reads before writing") { + def readsBeforeWriting(protocol: String) = + test(s"$protocol - client reads before writing") { Stream - .resource(googleSetup) + .resource(googleSetup(protocol)) .flatMap { socket => val send = Stream(googleDotCom) .through(text.utf8.encode) @@ -96,29 +94,49 @@ class TLSSocketSuite extends Fs2IoSuite { .assertEquals(httpOk) } - writesBeforeReading - readsBeforeWriting + // https://github.com/aws/s2n-tls/blob/8556f56bc6386dc8bfd8cceb6a38b97215a6f32e/docs/USAGE-GUIDE.md#security-policies + List("default", "default_tls13" /*, "rfc9151"*/ ).foreach { protocol => + writesBeforeReading(protocol) + readsBeforeWriting(protocol) + } } - // test("echo") { + test("echo") { + val msg = Chunk.array(("Hello, world! " * 20000).getBytes) + + val setup = for { + tlsContext <- testTlsContext + addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) + (serverAddress, server) = addressAndConnections + client <- Network[IO].client(serverAddress).flatMap(tlsContext.client(_)) + } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client + + Stream + .resource(setup) + .flatMap { case (server, clientSocket) => + val echoServer = server.map { socket => + socket.reads.chunks.foreach(socket.write(_)) + }.parJoinUnbounded + + val client = + Stream.exec(clientSocket.write(msg)).onFinalize(clientSocket.endOfOutput) ++ + clientSocket.reads.take(msg.size.toLong) + + client.concurrently(echoServer) + } + .compile + .to(Chunk) + .assertEquals(msg) + } + + // test("error") { // val msg = Chunk.array(("Hello, world! " * 20000).getBytes) // val setup = for { - // tlsContext <- Resource.eval(testTlsContext) + // tlsContext <- Resource.eval(Network[IO].tlsContext.system) // addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) // (serverAddress, server) = addressAndConnections - // client <- Network[IO] - // .client(serverAddress) - // .flatMap( - // tlsContext - // .clientBuilder(_) - // .withParameters( - // TLSParameters(checkServerIdentity = - // Some((sn, _) => Either.cond(sn == "localhost", (), new RuntimeException())) - // ) - // ) - // .build - // ) + // client <- Network[IO].client(serverAddress).flatMap(tlsContext.client(_)) // } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client // Stream @@ -129,33 +147,32 @@ class TLSSocketSuite extends Fs2IoSuite { // }.parJoinUnbounded // val client = - // Stream.exec(clientSocket.write(msg)) ++ + // Stream.exec(clientSocket.write(msg)).onFinalize(clientSocket.endOfOutput) ++ // clientSocket.reads.take(msg.size.toLong) // client.concurrently(echoServer) // } // .compile // .to(Chunk) - // .assertEquals(msg) + // .intercept[SSLHandshakeException] // } - // test("error") { + // test("echo insecure client with Endpoint verification") { // val msg = Chunk.array(("Hello, world! " * 20000).getBytes) // val setup = for { - // tlsContext <- Resource.eval(Network[IO].tlsContext.system) + // clientContext <- Resource.eval(TLSContext.Builder.forAsync[IO].insecure) + // tlsContext <- Resource.eval(testTlsContext) // addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) // (serverAddress, server) = addressAndConnections // client <- Network[IO] // .client(serverAddress) - // .flatMap( - // tlsContext - // .clientBuilder(_) + // .flatMap(s => + // clientContext + // .clientBuilder(s) // .withParameters( - // TLSParameters(checkServerIdentity = - // Some((sn, _) => Either.cond(sn == "localhost", (), new RuntimeException())) - // ) - // ) + // TLSParameters.apply(endpointIdentificationAlgorithm = Some("HTTPS")) + // ) // makes test fail if using X509TrustManager, passes if using X509ExtendedTrustManager // .build // ) // } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client @@ -168,15 +185,14 @@ class TLSSocketSuite extends Fs2IoSuite { // }.parJoinUnbounded // val client = - // Stream.exec(clientSocket.write(msg)) ++ + // Stream.exec(clientSocket.write(msg)).onFinalize(clientSocket.endOfOutput) ++ // clientSocket.reads.take(msg.size.toLong) // client.concurrently(echoServer) // } // .compile // .to(Chunk) - // .intercept[SSLException] + // .assertEquals(msg) // } - } } diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala new file mode 100644 index 0000000000..82c597a857 --- /dev/null +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io +package net +package tls + +import cats.effect.IO +import cats.effect.kernel.Resource +import scodec.bits.ByteVector + +abstract class TLSSuite extends Fs2IoSuite { + def testTlsContext: Resource[IO, TLSContext[IO]] = for { + cert <- Resource.eval { + readClassResource[IO, TLSSuite]("/cert.pem").compile.to(ByteVector) + } + key <- Resource.eval { + readClassResource[IO, TLSSuite]("/key.pem").compile.to(ByteVector) + } + cfg <- S2nConfig.builder.withCertChainAndKeysToStore(List(CertChainAndKey(cert, key))).build[IO] + } yield Network[IO].tlsContext.fromS2nConfig(cfg) + + val logger = TLSLogger.Disabled + // val logger = TLSLogger.Enabled(msg => IO(println(s"\u001b[33m${msg}\u001b[0m"))) +} diff --git a/io/shared/src/test/resources/cert.pem b/io/shared/src/test/resources/cert.pem new file mode 100644 index 0000000000..7515c37d5f --- /dev/null +++ b/io/shared/src/test/resources/cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIEW6AYvzANBgkqhkiG9w0BAQsFADBuMRAwDgYDVQQGEwdV +bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD +VQQKEwdVbmtub3duMRIwEAYDVQQLEwlGUzIgVGVzdHMxEDAOBgNVBAMTB1Vua25v +d24wIBcNMTkxMjAyMTMyOTM3WhgPMjExODA2MjYxMzI5MzdaMG4xEDAOBgNVBAYT +B1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vua25vd24xEDAO +BgNVBAoTB1Vua25vd24xEjAQBgNVBAsTCUZTMiBUZXN0czEQMA4GA1UEAxMHVW5r +bm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN4RczREVYw7GQF4 +E/dhwmFiGKi5G+0cGcyPQzv1Say3U6V8Haqpn0/aeVVY30T6/Nttu+0kGKv9uQlY +yeHeyPzqdjkRhSVoDCLahAxWSK8cV/B4r9SyynLxsDIoHAzkE0mGcBfsZ1Hpxe6/ +tLZ9SMZunpuQBMLKlE6sELgeY5Iz4ZptlFADr8SIqxhrnc3GYFWCubu3A7d6h5K1 +XMfXn+itYmb/y6TGr/8T5YjipvQuJFPv0WNahErZzOkDM/r4mp1BNKDWawJuyh3/ +6TudzkJUxK2eQowE6TWOeIB9WioMC0YlGUgylZe2vq9AWJkTjPq3duXZlj+Hqnqq +ZOQvWBcCAwEAAaMhMB8wHQYDVR0OBBYEFMEuQ/pix1rrPmvAh/KqSfxFLKfhMA0G +CSqGSIb3DQEBCwUAA4IBAQDX/Nr2FuVhXPkZRrFFVrzqIbNO3ROnkURXXJSXus5v +nvmQOXWHtE5Uy1f6z1iKCYUs6l+M5+YLmxooTZvaoAC6tjPDskIyPGQGVG4MB03E +ahLSBaRGJEZXIHLU9s6lgK0YoZCnhHdD+TXalxS4Jv6ieZJCKbWpkQomYYPWyTC6 +lR5XSEBPhPNRzo0wjla9a6th+Zc3KnTzTsQHg65IU0DIQeAIuxG0v3xh4NOmGi2Z +LX5q0qhf9uk88HUwocILO9TLshlTPAF4puf0vS4MICw46g4YonDz7k5VQmzy0ZAV +c6ew1WF0PfBk/3o4F0plkj5nbem57iOU0znKfI0ZYXoR +-----END CERTIFICATE----- diff --git a/io/shared/src/test/resources/key.pem b/io/shared/src/test/resources/key.pem new file mode 100644 index 0000000000..7096c07b66 --- /dev/null +++ b/io/shared/src/test/resources/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDeEXM0RFWMOxkB +eBP3YcJhYhiouRvtHBnMj0M79Umst1OlfB2qqZ9P2nlVWN9E+vzbbbvtJBir/bkJ +WMnh3sj86nY5EYUlaAwi2oQMVkivHFfweK/Usspy8bAyKBwM5BNJhnAX7GdR6cXu +v7S2fUjGbp6bkATCypROrBC4HmOSM+GabZRQA6/EiKsYa53NxmBVgrm7twO3eoeS +tVzH15/orWJm/8ukxq//E+WI4qb0LiRT79FjWoRK2czpAzP6+JqdQTSg1msCbsod +/+k7nc5CVMStnkKMBOk1jniAfVoqDAtGJRlIMpWXtr6vQFiZE4z6t3bl2ZY/h6p6 +qmTkL1gXAgMBAAECggEBAMJ6v8zvZ4hXHVAnDD1jlStaEMR60NU3/fQjJzu0VqB3 +MT9FUmnrAUWazRYMrgQoVxgIo0NMkHrXypw/8RXp2VV+NKlICbY3yCEiA/EWA7Ov +++fymfKJ3jkKJ0fVzrMPb0C+Bx88f0PCmwC7TZVgZUK7EBam6zR426eGk2Hb41He +kcUG0Q68modQO172m3bPqNsa32bsjvAZ813KLSs8xFHhJajTjX5kXG+PmN1fenTx +8cvzOAET62O4XEXsYqD+iENyvqkRkysfqa9uzWyhLiip6NZXJHPuNKi9usVL+ZNj +glJXFMP0K/TFYqgMBnoJF3Y60NMBlTpTyDu1kP0/nCkCgYEA+g/cxpz+1yPEIs+y +cX4qEp2VJpj4HuJdZKingtApg1aSAlAcNf9UZl7EsGz3vf/x/hX6YtTLF9cbyuIR +P+CYEevWJwiLjTjMbd+Z86oeYr/sLqz4+98W3RGTAcRfLyEeM/fc7MEwTSeyIhpZ +Gi+HbxGJMvbj+8tUh6KFd9lDu8MCgYEA41dpfo2MnyA9f2OrEC2Joll/9Kgz+3l9 +jZIRQ4YID3Rawi9xLi9Z6aiIudAGFT9hcFU11akIVz6yXvhRGmE6gQ6bim5ebaJe +69EHchuBl9Pszi7/UUS4cxksVnNwJjrAHXnoESU0DTWRh3AUkBEsKOd0DkSGOma+ +MEI+JDe2cR0CgYEA9Jgfc4aNHxM0/nf6K1kk/iB1i9OEn3D7uUHe1+2VLYq4Ntr1 +PTwK6jc4XPm5Onfn1Ija6WELZr5ZyRFnnfupw53TU0rgdbpg+/gDNnvoTN89vkoj +IPsN+h7+lHPoRsk2Kc8AofQ1ssJpU0JCdYKYDuQwN1GXnus8O4+Uza4OutECgYAr +IeCABDcT0bgZPT2tWhZs2PIv5uHF6mzpuTbRStKoq/i0MvAURSOX80PNjSw6R8Yi +2+fU27cbZmfNIOuyR5Qj/DOCdiIwRsgfkY8KFTHnLmwVSlFih9k+7R2+YTR77FWa +whBHgHl5sBomSht8oeVw9UjNlC6rUebvnQHROUjB+QKBgQDj1g/t+B5lZ03Pc00h +NQxMqTR6wAp3hPX5Wen44+o0ktGkArRP3Iw3x5764EgUudn2p1NibCrjCnj2avCi +Gkkbl0wWcFZiOsvmBL/0ngxdPA5bfwiKKeIavltWufXXHl1fs0C4UIu9fHWpV6z7 +NRVO87Wp3BK13bCGUD0DvAdtpA== +-----END PRIVATE KEY----- From 5d5b1a357edd64b335fac9bdd8ad2e83ecf73f68 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 18:01:54 +0000 Subject: [PATCH 38/83] Unhang the test --- .../main/scala/fs2/io/net/tls/TLSContextPlatform.scala | 3 ++- .../src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index da95b5ffe2..320fe29375 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -63,7 +63,8 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => def clientBuilder(socket: Socket[F]) = SocketBuilder(mkSocket(socket, true, _, _)) - def serverBuilder(socket: Socket[F]) = ??? + def serverBuilder(socket: Socket[F]) = + SocketBuilder(mkSocket(socket, false, _, _)) private def mkSocket( socket: Socket[F], diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 0869fc9bb8..f333eab9de 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -101,14 +101,14 @@ class TLSSocketSuite extends TLSSuite { } } - test("echo") { + test("echo".only) { val msg = Chunk.array(("Hello, world! " * 20000).getBytes) val setup = for { tlsContext <- testTlsContext addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) (serverAddress, server) = addressAndConnections - client <- Network[IO].client(serverAddress).flatMap(tlsContext.client(_)) + client = Network[IO].client(serverAddress).flatMap(tlsContext.client(_)) } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client Stream @@ -118,9 +118,10 @@ class TLSSocketSuite extends TLSSuite { socket.reads.chunks.foreach(socket.write(_)) }.parJoinUnbounded - val client = - Stream.exec(clientSocket.write(msg)).onFinalize(clientSocket.endOfOutput) ++ + val client = Stream.resource(clientSocket).flatMap { clientSocket => + Stream.exec(clientSocket.write(msg)) ++ clientSocket.reads.take(msg.size.toLong) + } client.concurrently(echoServer) } From 3de7f5cf8b7e53579c6ea82c5c5ef788d10a3c41 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 18:20:02 +0000 Subject: [PATCH 39/83] Fix the echo test --- .../test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 11 +++++++++-- .../src/test/scala/fs2/io/net/tls/TLSSuite.scala | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index f333eab9de..ce10dbde57 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -101,14 +101,21 @@ class TLSSocketSuite extends TLSSuite { } } - test("echo".only) { + test("echo") { val msg = Chunk.array(("Hello, world! " * 20000).getBytes) val setup = for { tlsContext <- testTlsContext addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) (serverAddress, server) = addressAndConnections - client = Network[IO].client(serverAddress).flatMap(tlsContext.client(_)) + client = Network[IO] + .client(serverAddress) + .flatMap( + tlsContext + .clientBuilder(_) + .withParameters(TLSParameters(serverName = Some("Unknown"))) + .build + ) } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client Stream diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala index 82c597a857..7be178cc08 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala @@ -36,7 +36,10 @@ abstract class TLSSuite extends Fs2IoSuite { key <- Resource.eval { readClassResource[IO, TLSSuite]("/key.pem").compile.to(ByteVector) } - cfg <- S2nConfig.builder.withCertChainAndKeysToStore(List(CertChainAndKey(cert, key))).build[IO] + cfg <- S2nConfig.builder + .withCertChainAndKeysToStore(List(CertChainAndKey(cert, key))) + .withPemsToTrustStore(List(cert.decodeAscii.toOption.get)) + .build[IO] } yield Network[IO].tlsContext.fromS2nConfig(cfg) val logger = TLSLogger.Disabled From 667acf77427e6a3b8ccac9cc0c82a7e3f0d0bcf8 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 18:30:44 +0000 Subject: [PATCH 40/83] All JVM-inspired tests passing --- .../scala/fs2/io/net/tls/TLSSocketSuite.scala | 129 +++++++++--------- .../scala/fs2/io/net/tls/TLSContext.scala | 2 + 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index ce10dbde57..12b7d192b5 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -137,70 +137,69 @@ class TLSSocketSuite extends TLSSuite { .assertEquals(msg) } - // test("error") { - // val msg = Chunk.array(("Hello, world! " * 20000).getBytes) - - // val setup = for { - // tlsContext <- Resource.eval(Network[IO].tlsContext.system) - // addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) - // (serverAddress, server) = addressAndConnections - // client <- Network[IO].client(serverAddress).flatMap(tlsContext.client(_)) - // } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client - - // Stream - // .resource(setup) - // .flatMap { case (server, clientSocket) => - // val echoServer = server.map { socket => - // socket.reads.chunks.foreach(socket.write(_)) - // }.parJoinUnbounded - - // val client = - // Stream.exec(clientSocket.write(msg)).onFinalize(clientSocket.endOfOutput) ++ - // clientSocket.reads.take(msg.size.toLong) - - // client.concurrently(echoServer) - // } - // .compile - // .to(Chunk) - // .intercept[SSLHandshakeException] - // } - - // test("echo insecure client with Endpoint verification") { - // val msg = Chunk.array(("Hello, world! " * 20000).getBytes) - - // val setup = for { - // clientContext <- Resource.eval(TLSContext.Builder.forAsync[IO].insecure) - // tlsContext <- Resource.eval(testTlsContext) - // addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) - // (serverAddress, server) = addressAndConnections - // client <- Network[IO] - // .client(serverAddress) - // .flatMap(s => - // clientContext - // .clientBuilder(s) - // .withParameters( - // TLSParameters.apply(endpointIdentificationAlgorithm = Some("HTTPS")) - // ) // makes test fail if using X509TrustManager, passes if using X509ExtendedTrustManager - // .build - // ) - // } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client - - // Stream - // .resource(setup) - // .flatMap { case (server, clientSocket) => - // val echoServer = server.map { socket => - // socket.reads.chunks.foreach(socket.write(_)) - // }.parJoinUnbounded - - // val client = - // Stream.exec(clientSocket.write(msg)).onFinalize(clientSocket.endOfOutput) ++ - // clientSocket.reads.take(msg.size.toLong) - - // client.concurrently(echoServer) - // } - // .compile - // .to(Chunk) - // .assertEquals(msg) - // } + test("error") { + val msg = Chunk.array(("Hello, world! " * 20000).getBytes) + + val setup = for { + tlsContext <- Network[IO].tlsContext.systemResource + addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) + (serverAddress, server) = addressAndConnections + client = Network[IO] + .client(serverAddress) + .flatMap( + tlsContext + .clientBuilder(_) + .withParameters(TLSParameters(serverName = Some("Unknown"))) + .build + ) + } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client + + Stream + .resource(setup) + .flatMap { case (server, clientSocket) => + val echoServer = server.map { socket => + socket.reads.chunks.foreach(socket.write(_)) + }.parJoinUnbounded + + val client = Stream.resource(clientSocket).flatMap { clientSocket => + Stream.exec(clientSocket.write(msg)) ++ + clientSocket.reads.take(msg.size.toLong) + } + client.concurrently(echoServer) + } + .compile + .to(Chunk) + .intercept[SSLException] + } + + test("echo insecure client") { + val msg = Chunk.array(("Hello, world! " * 20000).getBytes) + + val setup = for { + clientContext <- Network[IO].tlsContext.insecureResource + tlsContext <- testTlsContext + addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) + (serverAddress, server) = addressAndConnections + client = Network[IO].client(serverAddress).flatMap(clientContext.client(_)) + } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client + + Stream + .resource(setup) + .flatMap { case (server, clientSocket) => + val echoServer = server.map { socket => + socket.reads.chunks.foreach(socket.write(_)) + }.parJoinUnbounded + + val client = Stream.resource(clientSocket).flatMap { clientSocket => + Stream.exec(clientSocket.write(msg)) ++ + clientSocket.reads.take(msg.size.toLong) + } + + client.concurrently(echoServer) + } + .compile + .to(Chunk) + .assertEquals(msg) + } } } diff --git a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala index 44ddedde45..7d603f210e 100644 --- a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala +++ b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala @@ -73,6 +73,8 @@ object TLSContext extends TLSContextCompanionPlatform { def systemResource: Resource[F, TLSContext[F]] + def insecureResource: Resource[F, TLSContext[F]] + } object Builder extends BuilderCompanionPlatform { From 7179b2459bc2ce15fcbb22ba4e6995d545efa0c6 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 18:39:51 +0000 Subject: [PATCH 41/83] Add test for mTLS client verification --- .../scala/fs2/io/net/tls/TLSSocketSuite.scala | 44 +++++++++++++++++++ .../test/scala/fs2/io/net/tls/TLSSuite.scala | 9 ++++ 2 files changed, 53 insertions(+) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 12b7d192b5..470c694e77 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -172,6 +172,50 @@ class TLSSocketSuite extends TLSSuite { .intercept[SSLException] } + test("mTLS client verification") { + val msg = Chunk.array(("Hello, world! " * 100).getBytes) + + val setup = for { + serverContext <- testTlsContext + clientContext <- testClientTlsContext + addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) + (serverAddress, server) = addressAndConnections + client = Network[IO] + .client(serverAddress) + .flatMap( + clientContext + .clientBuilder(_) + .withParameters(TLSParameters(serverName = Some("Unknown"))) + .build + ) + } yield server.flatMap(s => + Stream.resource( + serverContext + .serverBuilder(s) + .withParameters(TLSParameters(clientAuthType = CertAuthType.Required.some)) // mTLS + .build + ) + ) -> client + + Stream + .resource(setup) + .flatMap { case (server, clientSocket) => + val echoServer = server.map { socket => + socket.reads.chunks.foreach(socket.write(_)) + }.parJoinUnbounded + + val client = Stream.resource(clientSocket).flatMap { clientSocket => + Stream.exec(clientSocket.write(msg)) ++ + clientSocket.reads.take(msg.size.toLong) + } + + client.concurrently(echoServer) + } + .compile + .to(Chunk) + .intercept[SSLException] + } + test("echo insecure client") { val msg = Chunk.array(("Hello, world! " * 20000).getBytes) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala index 7be178cc08..304c683f7c 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala @@ -42,6 +42,15 @@ abstract class TLSSuite extends Fs2IoSuite { .build[IO] } yield Network[IO].tlsContext.fromS2nConfig(cfg) + def testClientTlsContext: Resource[IO, TLSContext[IO]] = for { + cert <- Resource.eval { + readClassResource[IO, TLSSuite]("/cert.pem").compile.to(ByteVector) + } + cfg <- S2nConfig.builder + .withPemsToTrustStore(List(cert.decodeAscii.toOption.get)) + .build[IO] + } yield Network[IO].tlsContext.fromS2nConfig(cfg) + val logger = TLSLogger.Disabled // val logger = TLSLogger.Enabled(msg => IO(println(s"\u001b[33m${msg}\u001b[0m"))) } From a2930cc56cbe1e210654c535cdd8169bc5feb267 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 21:26:20 +0000 Subject: [PATCH 42/83] Write blinding test --- .../scala/fs2/io/net/tls/TLSSocketSuite.scala | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 470c694e77..0be71f3a24 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -245,5 +245,44 @@ class TLSSocketSuite extends TLSSuite { .to(Chunk) .assertEquals(msg) } + + test("blinding".only) { + val msg = Chunk.array(("Hello, world! " * 20000).getBytes) + + val setup = for { + tlsContext <- testTlsContext + addressAndConnections <- Network[IO].serverResource(Some(ip"127.0.0.1")) + (serverAddress, server) = addressAndConnections + client = Network[IO].client(serverAddress).flatMap(tlsContext.client(_)) + } yield server.flatMap(s => Stream.resource(tlsContext.server(s))) -> client + + val echo = Stream + .resource(setup) + .flatMap { case (server, clientSocket) => + val echoServer = server.map { socket => + socket.reads.chunks.foreach(socket.write(_)) + }.parJoinUnbounded + + val client = Stream.resource(clientSocket).flatMap { clientSocket => + Stream.exec(clientSocket.write(msg)) ++ + clientSocket.reads.take(msg.size.toLong) + } + + client.concurrently(echoServer) + } + .compile + .drain + + val checkStarvation = IO.sleep(3.seconds).timed.flatMap { case (duration, _) => + IO(assert(clue(duration) < 4.seconds, "starved during blinding")) + } + + val checkBlinding = + echo.attempt.flatMap(result => IO(assert(clue(result).isLeft))).timed.flatMap { + case (duration, _) => IO(assert(clue(duration) > 9.seconds), "didn't blind on error") + } + + checkStarvation.both(checkBlinding).void + } } } From bcc68a15b1df6546bf26aca2a60bd26790f226d9 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sun, 4 Sep 2022 22:05:51 +0000 Subject: [PATCH 43/83] Implement self-service blinding --- .../main/scala/fs2/io/net/tls/S2nConnection.scala | 13 ++++++++++++- io/native/src/main/scala/fs2/io/net/tls/s2n.scala | 8 ++++++++ .../test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 9b20c918a5..219119dca0 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -31,6 +31,7 @@ import scodec.bits.ByteVector import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference +import scala.concurrent.duration._ import scala.scalanative.libc import scala.scalanative.posix import scala.scalanative.unsafe._ @@ -69,8 +70,11 @@ private[tls] object S2nConnection { F.delay(guard(s2n_connection_new(if (clientMode) S2N_CLIENT.toUInt else S2N_SERVER.toUInt))) )(conn => F.delay(guard_(s2n_connection_free(conn)))) - _ <- F.delay(s2n_connection_set_config(conn, config.ptr)).toResource + _ <- F.delay(guard_(s2n_connection_set_config(conn, config.ptr))).toResource _ <- parameters.configure(conn) + _ <- F.delay { + guard_(s2n_connection_set_blinding(conn, S2N_SELF_SERVICE_BLINDING.toUInt)) + }.toResource readBuffer <- Resource.eval { F.delay(new AtomicReference[Option[ByteVector]](Some(ByteVector.empty))) @@ -105,6 +109,8 @@ private[tls] object S2nConnection { val blocked = stackalloc[s2n_blocked_status]() guard_(s2n_negotiate(conn, blocked)) !blocked + }.guaranteeCase { oc => + blindingSleep.whenA(oc.isError) }.productL { val reads = F.delay(readTasks.get).flatten val writes = F.delay(writeTasks.get).flatten @@ -120,6 +126,8 @@ private[tls] object S2nConnection { val blocked = stackalloc[s2n_blocked_status]() val readed = guard(s2n_recv(conn, buf + i, n - i, blocked)) (!blocked, Math.max(readed, 0)) + }.guaranteeCase { oc => + blindingSleep.whenA(oc.isError) }.productL(F.delay(readTasks.get).flatten) .flatMap { case (blocked, readed) => val total = i + readed @@ -185,6 +193,9 @@ private[tls] object S2nConnection { private def zone: Resource[F, Zone] = Resource.make(F.delay(Zone.open()))(z => F.delay(z.close())) + private def blindingSleep: F[Unit] = + F.delay(s2n_connection_get_delay(conn).toLong.nanos) + .flatMap(delay => F.sleep(delay)) } private final case class RecvCallbackContext[F[_]]( diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 5f2e023bb6..95b9a90c70 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -53,6 +53,10 @@ private[tls] object s2n { final val S2N_CERT_AUTH_REQUIRED = 1 final val S2N_CERT_AUTH_OPTIONAL = 2 + type s2n_blinding = CUnsignedInt + final val S2N_BUILT_IN_BLINDING = 0 + final val S2N_SELF_SERVICE_BLINDING = 1 + type s2n_config type s2n_connection type s2n_cert_chain_and_key @@ -134,6 +138,10 @@ private[tls] object s2n { data: Ptr[Byte] ): CInt = extern + def s2n_connection_set_blinding(conn: Ptr[s2n_connection], blinding: s2n_blinding): CInt = extern + + def s2n_connection_get_delay(conn: Ptr[s2n_connection]): CUnsignedLong = extern + def s2n_connection_set_cipher_preferences(conn: Ptr[s2n_connection], version: Ptr[CChar]): CInt = extern diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 0be71f3a24..99830fe3df 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -246,7 +246,7 @@ class TLSSocketSuite extends TLSSuite { .assertEquals(msg) } - test("blinding".only) { + test("blinding") { val msg = Chunk.array(("Hello, world! " * 20000).getBytes) val setup = for { From acd0326aac47681d6a53cc38733163e489161a19 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 5 Sep 2022 00:09:11 +0000 Subject: [PATCH 44/83] Implement async private key ops --- .../main/scala/fs2/io/net/tls/S2nConfig.scala | 37 +++++++++++++++++-- .../scala/fs2/io/net/tls/S2nConnection.scala | 18 ++++++++- .../src/main/scala/fs2/io/net/tls/s2n.scala | 25 +++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index a5ec293cf3..e29385ab69 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -29,13 +29,13 @@ import cats.syntax.all._ import scala.scalanative.unsafe._ import scala.scalanative.unsigned._ +import scala.util.control.NonFatal import s2n._ import s2nutil._ -/** - * An `s2n_config`. - * +/** An `s2n_config`. + * * See [[https://github.com/aws/s2n-tls/ s2n-tls]] for detailed documentation. */ final class S2nConfig private (private[tls] val ptr: Ptr[s2n_config]) @@ -81,6 +81,10 @@ object S2nConfig { F.delay(guard_(s2n_config_free(cfg))) ) + _ <- F.delay { + guard_(s2n_config_set_async_pkey_callback(cfg, asyncPkeyCallback[F](_, _))) + }.toResource + _ <- F.delay(guard_(s2n_config_wipe_trust_store(cfg))).whenA(wipedTrustStore).toResource _ <- certKeyPairs.traverse_ { pair => @@ -157,4 +161,31 @@ object S2nConfig { } + // we manually schedule expensive private-key operations so we can add fairness boundaries + private def asyncPkeyCallback[F[_]](conn: Ptr[s2n_connection], op: Ptr[s2n_async_pkey_op]): CInt = + try { + val data = fromPtr[S2nConnection.ConnectionContext[F]](guard(s2n_connection_get_ctx(conn))) + import data._ + implicit val F = async + + val cert = guard(s2n_connection_get_selected_cert(conn)) + val key = guard(s2n_cert_chain_and_key_get_private_key(cert)) + + val task = F.cede *> + F.delay { + guard_(s2n_async_pkey_op_perform(op, key)) + guard_(s2n_async_pkey_op_apply(op, conn)) + }.guarantee(F.cede) + + privateKeyTasks.getAndUpdate { tasks => + (tasks *> task).guarantee(F.delay(guard_(s2n_async_pkey_op_free(op)))) + } + + S2N_SUCCESS + } catch { + case NonFatal(_) => + guard_(s2n_async_pkey_op_free(op)) + S2N_FAILURE + } + } diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 219119dca0..47275ac178 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -76,6 +76,15 @@ private[tls] object S2nConnection { guard_(s2n_connection_set_blinding(conn, S2N_SELF_SERVICE_BLINDING.toUInt)) }.toResource + privateKeyTasks <- F.delay(new AtomicReference[F[Unit]](F.unit)).toResource + _ <- Resource.eval { + F.delay { + val ctx = ConnectionContext(privateKeyTasks, F) + guard_(s2n_connection_set_ctx(conn, toPtr(ctx))) + gcRoot.add(ctx) + } + } + readBuffer <- Resource.eval { F.delay(new AtomicReference[Option[ByteVector]](Some(ByteVector.empty))) } @@ -106,6 +115,7 @@ private[tls] object S2nConnection { F.delay { readTasks.set(F.unit) writeTasks.set(F.unit) + privateKeyTasks.set(F.unit) val blocked = stackalloc[s2n_blocked_status]() guard_(s2n_negotiate(conn, blocked)) !blocked @@ -114,7 +124,8 @@ private[tls] object S2nConnection { }.productL { val reads = F.delay(readTasks.get).flatten val writes = F.delay(writeTasks.get).flatten - reads.both(writes) + val pkeyOps = F.delay(privateKeyTasks.get).flatten + reads.both(writes).both(pkeyOps) }.iterateUntil(_.toInt == S2N_NOT_BLOCKED) *> F.delay(guard_(s2n_connection_free_handshake(conn))) @@ -198,6 +209,11 @@ private[tls] object S2nConnection { .flatMap(delay => F.sleep(delay)) } + final case class ConnectionContext[F[_]]( + privateKeyTasks: AtomicReference[F[Unit]], + async: Async[F] + ) + private final case class RecvCallbackContext[F[_]]( readBuffer: AtomicReference[Option[ByteVector]], readTasks: AtomicReference[F[Unit]], diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index 95b9a90c70..d502e7c064 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -60,12 +60,16 @@ private[tls] object s2n { type s2n_config type s2n_connection type s2n_cert_chain_and_key + type s2n_async_pkey_op + type s2n_cert_private_key type s2n_recv_fn = CFuncPtr3[Ptr[Byte], Ptr[Byte], CUnsignedInt, CInt] type s2n_send_fn = CFuncPtr3[Ptr[Byte], Ptr[Byte], CUnsignedInt, CInt] type s2n_verify_host_fn = CFuncPtr3[Ptr[CChar], CSize, Ptr[Byte], Byte] + type s2n_async_pkey_fn = CFuncPtr2[Ptr[s2n_connection], Ptr[s2n_async_pkey_op], CInt] + def s2n_errno_location(): Ptr[CInt] = extern def s2n_error_get_type(error: CInt): CInt = extern @@ -90,6 +94,10 @@ private[tls] object s2n { def s2n_cert_chain_and_key_free(cert_and_key: Ptr[s2n_cert_chain_and_key]): CInt = extern + def s2n_cert_chain_and_key_get_private_key( + cert_and_key: Ptr[s2n_cert_chain_and_key] + ): Ptr[s2n_cert_private_key] = extern + def s2n_config_add_cert_chain_and_key_to_store( config: Ptr[s2n_config], cert_key_pair: Ptr[s2n_cert_chain_and_key] @@ -124,6 +132,10 @@ private[tls] object s2n { def s2n_connection_set_config(conn: Ptr[s2n_connection], config: Ptr[s2n_config]): CInt = extern + def s2n_connection_set_ctx(conn: Ptr[s2n_connection], ctx: Ptr[Byte]): CInt = extern + + def s2n_connection_get_ctx(conn: Ptr[s2n_connection]): Ptr[Byte] = extern + def s2n_connection_set_recv_ctx(conn: Ptr[s2n_connection], ctx: Ptr[Byte]): CInt = extern def s2n_connection_set_send_ctx(conn: Ptr[s2n_connection], ctx: Ptr[Byte]): CInt = extern @@ -193,4 +205,17 @@ private[tls] object s2n { def s2n_connection_get_session_length(conn: Ptr[s2n_connection]): CInt = extern + def s2n_connection_get_selected_cert(conn: Ptr[s2n_connection]): Ptr[s2n_cert_chain_and_key] = + extern + + def s2n_config_set_async_pkey_callback(config: Ptr[s2n_config], fn: s2n_async_pkey_fn): CInt = + extern + + def s2n_async_pkey_op_perform(op: Ptr[s2n_async_pkey_op], key: Ptr[s2n_cert_private_key]): CInt = + extern + + def s2n_async_pkey_op_apply(op: Ptr[s2n_async_pkey_op], conn: Ptr[s2n_connection]): CInt = extern + + def s2n_async_pkey_op_free(op: Ptr[s2n_async_pkey_op]): CInt = extern + } From d1dfae60f12c1d76ef335560f15cb7748f11ba5f Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 5 Sep 2022 02:19:37 +0000 Subject: [PATCH 45/83] Plug potential memory leak --- io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala | 6 +++--- .../src/main/scala/fs2/io/net/tls/S2nConnection.scala | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index e29385ab69..b21eba2a1a 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -176,10 +176,10 @@ object S2nConfig { guard_(s2n_async_pkey_op_perform(op, key)) guard_(s2n_async_pkey_op_apply(op, conn)) }.guarantee(F.cede) + val cleanup = F.delay(guard_(s2n_async_pkey_op_free(op))) - privateKeyTasks.getAndUpdate { tasks => - (tasks *> task).guarantee(F.delay(guard_(s2n_async_pkey_op_free(op)))) - } + privateKeyTasks.getAndUpdate(_ *> task) + privateKeyCleanupTasks.getAndUpdate(_ *> cleanup) S2N_SUCCESS } catch { diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 47275ac178..6d3765b6fd 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -77,9 +77,10 @@ private[tls] object S2nConnection { }.toResource privateKeyTasks <- F.delay(new AtomicReference[F[Unit]](F.unit)).toResource + privateKeyCleanupTasks <- F.delay(new AtomicReference[F[Unit]](F.unit)).toResource _ <- Resource.eval { F.delay { - val ctx = ConnectionContext(privateKeyTasks, F) + val ctx = ConnectionContext(privateKeyTasks, privateKeyCleanupTasks, F) guard_(s2n_connection_set_ctx(conn, toPtr(ctx))) gcRoot.add(ctx) } @@ -116,6 +117,7 @@ private[tls] object S2nConnection { readTasks.set(F.unit) writeTasks.set(F.unit) privateKeyTasks.set(F.unit) + privateKeyCleanupTasks.set(F.unit) val blocked = stackalloc[s2n_blocked_status]() guard_(s2n_negotiate(conn, blocked)) !blocked @@ -126,6 +128,8 @@ private[tls] object S2nConnection { val writes = F.delay(writeTasks.get).flatten val pkeyOps = F.delay(privateKeyTasks.get).flatten reads.both(writes).both(pkeyOps) + }.guarantee { + F.delay(privateKeyCleanupTasks.get).flatten }.iterateUntil(_.toInt == S2N_NOT_BLOCKED) *> F.delay(guard_(s2n_connection_free_handshake(conn))) @@ -211,6 +215,7 @@ private[tls] object S2nConnection { final case class ConnectionContext[F[_]]( privateKeyTasks: AtomicReference[F[Unit]], + privateKeyCleanupTasks: AtomicReference[F[Unit]], async: Async[F] ) From 65664f049926e3e14a2072d4a9df2f6be9791eb6 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 5 Sep 2022 02:27:58 +0000 Subject: [PATCH 46/83] Even more careful --- io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala index b21eba2a1a..7c97b17d67 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConfig.scala @@ -179,7 +179,7 @@ object S2nConfig { val cleanup = F.delay(guard_(s2n_async_pkey_op_free(op))) privateKeyTasks.getAndUpdate(_ *> task) - privateKeyCleanupTasks.getAndUpdate(_ *> cleanup) + privateKeyCleanupTasks.getAndUpdate(_.guarantee(cleanup)) S2N_SUCCESS } catch { From 90193b0245ffc6e8f7fc0bf0dc28b85821aca165 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 5 Sep 2022 02:45:17 +0000 Subject: [PATCH 47/83] Don't force linking to openssl --- io/native/src/main/scala/fs2/io/net/tls/s2n.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala index d502e7c064..e31d03270d 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/s2n.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/s2n.scala @@ -26,7 +26,6 @@ import scala.annotation.nowarn @extern @link("s2n") -@link("crypto") @nowarn private[tls] object s2n { From 518574f24cb83ca02d504d5e4f891da04a274368 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 02:30:04 +0000 Subject: [PATCH 48/83] update round --- build.sbt | 16 +++++++--------- project/plugins.sbt | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/build.sbt b/build.sbt index e23db29419..4c202bf13b 100644 --- a/build.sbt +++ b/build.sbt @@ -180,8 +180,6 @@ lazy val root = tlCrossRootProject lazy val IntegrationTest = config("it").extend(Test) -ThisBuild / resolvers ++= Resolver.sonatypeOssRepos("snapshots") - lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("core")) .configs(IntegrationTest) @@ -196,12 +194,12 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % "2.8.0", "org.typelevel" %%% "cats-laws" % "2.8.0" % Test, - "com.armanbilge" %%% "cats-effect" % "3.4-519e5ce-SNAPSHOT", - "com.armanbilge" %%% "cats-effect-laws" % "3.4-519e5ce-SNAPSHOT" % Test, - "com.armanbilge" %%% "cats-effect-testkit" % "3.4-519e5ce-SNAPSHOT" % Test, + "org.typelevel" %%% "cats-effect" % "3.3.14-1-5d11fe9", + "org.typelevel" %%% "cats-effect-laws" % "3.3.14-1-5d11fe9" % Test, + "org.typelevel" %%% "cats-effect-testkit" % "3.3.14-1-5d11fe9" % Test, "org.scodec" %%% "scodec-bits" % "1.1.34", - "org.typelevel" %%% "scalacheck-effect-munit" % "2.0-1a82a3d-SNAPSHOT" % Test, - "com.armanbilge" %%% "munit-cats-effect" % "2.0-4e051ab-SNAPSHOT" % Test, + "org.typelevel" %%% "scalacheck-effect-munit" % "2.0-9366e44" % Test, + "org.typelevel" %%% "munit-cats-effect" % "2.0-5e03bfc" % Test, "org.typelevel" %%% "discipline-munit" % "2.0.0-M3" % Test ), tlJdkRelease := Some(8), @@ -229,7 +227,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( name := "fs2-io", tlVersionIntroduced ~= { _.updated("3", "3.1.0") }, - libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.3-53-f37ec8e-SNAPSHOT" + libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.3-83-cfa2ec1" ) .jvmSettings( Test / fork := true, @@ -244,7 +242,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat-net" % "0.0-2f64289-SNAPSHOT" % Test + "com.armanbilge" %%% "epollcat" % "0.0-70bf67b" % Test ), nativeConfig ~= { c => if (isLinux) { // brew-installed s2n diff --git a/project/plugins.sbt b/project/plugins.sbt index c46043ca5c..a559a49479 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,7 +2,7 @@ val sbtTypelevelVersion = "0.4.13" addSbtPlugin("org.typelevel" % "sbt-typelevel" % sbtTypelevelVersion) addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % sbtTypelevelVersion) addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.5") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.7") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") From 2123d94eacc71b6a363d27b59a9f3a79c7124c9a Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 02:51:29 +0000 Subject: [PATCH 49/83] Revert some hacks --- .../src/main/scala/fs2/io/file/Watcher.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/io/jvm-native/src/main/scala/fs2/io/file/Watcher.scala b/io/jvm-native/src/main/scala/fs2/io/file/Watcher.scala index 171309ee09..8179e0ed02 100644 --- a/io/jvm-native/src/main/scala/fs2/io/file/Watcher.scala +++ b/io/jvm-native/src/main/scala/fs2/io/file/Watcher.scala @@ -215,7 +215,11 @@ object Watcher { path: Path, types: Seq[Watcher.EventType] = Nil, modifiers: Seq[WatchEvent.Modifier] = Nil - ): F[F[Unit]] = ??? + ): F[F[Unit]] = + Files[F].isDirectory(path).flatMap { dir => + if (dir) watchDirectory(path, types, modifiers) + else watchFile(path, types, modifiers) + } private def watchDirectory( path: Path, @@ -332,9 +336,9 @@ object Watcher { ).flatMap { cancel => val events: F[List[Event]] = F.blocking { var evs: List[Event.Created] = Nil - // JFiles - // .list(p.toNioPath) - // .forEach(d => evs = Event.Created(Path.fromNioPath(d), 1) :: evs) + JFiles + .list(p.toNioPath) + .forEach(d => evs = Event.Created(Path.fromNioPath(d), 1) :: evs) evs } events.map(cancel -> _) From 31e66b815d76abed328d2f143b0244638ef138bc Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 03:13:57 +0000 Subject: [PATCH 50/83] workflow --- .github/workflows/ci.yml | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4771c6d7e..0c727f936a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: os: [ubuntu-latest] scala: [3.1.3, 2.12.16, 2.13.8] java: [temurin@17] - project: [rootJS, rootJVM] + project: [rootJS, rootJVM, rootNative] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) @@ -77,6 +77,10 @@ jobs: if: matrix.project == 'rootJS' run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' Test/scalaJSLinkerResult + - name: nativeLink + if: matrix.project == 'rootNative' + run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' Test/nativeLink + - name: Test run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' test @@ -96,11 +100,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/2.5.x') - run: mkdir -p target protocols/js/target unidocs/target .js/target core/js/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target benchmark/target project/target + run: mkdir -p target protocols/js/target io/native/target unidocs/target .js/target core/native/target core/js/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target benchmark/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/2.5.x') - run: tar cf targets.tar target protocols/js/target unidocs/target .js/target core/js/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target benchmark/target project/target + run: tar cf targets.tar target protocols/js/target io/native/target unidocs/target .js/target core/native/target core/js/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target benchmark/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/2.5.x') @@ -173,6 +177,16 @@ jobs: tar xf targets.tar rm targets.tar + - name: Download target directories (3.1.3, rootNative) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-rootNative + + - name: Inflate target directories (3.1.3, rootNative) + run: | + tar xf targets.tar + rm targets.tar + - name: Download target directories (2.12.16, rootJS) uses: actions/download-artifact@v2 with: @@ -193,6 +207,16 @@ jobs: tar xf targets.tar rm targets.tar + - name: Download target directories (2.12.16, rootNative) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-rootNative + + - name: Inflate target directories (2.12.16, rootNative) + run: | + tar xf targets.tar + rm targets.tar + - name: Download target directories (2.13.8, rootJS) uses: actions/download-artifact@v2 with: @@ -213,6 +237,16 @@ jobs: tar xf targets.tar rm targets.tar + - name: Download target directories (2.13.8, rootNative) + uses: actions/download-artifact@v2 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootNative + + - name: Inflate target directories (2.13.8, rootNative) + run: | + tar xf targets.tar + rm targets.tar + - name: Import signing key if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' run: echo $PGP_SECRET | base64 -di | gpg --import From d193c41a09af61c9d1b1f10fe0df9a9419e74d02 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 04:41:16 +0000 Subject: [PATCH 51/83] share jvm/native stdin/out/err + resource code --- .../src/main/scala/fs2/io/iojvmnative.scala | 92 +++++++++++++++++++ io/jvm/src/main/scala/fs2/io/ioplatform.scala | 56 +---------- .../src/main/scala/fs2/io/ioplatform.scala | 58 +----------- 3 files changed, 94 insertions(+), 112 deletions(-) create mode 100644 io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala diff --git a/io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala b/io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala new file mode 100644 index 0000000000..0179b3abe3 --- /dev/null +++ b/io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io + +import cats._ +import cats.effect.kernel.{Async, Outcome, Resource, Sync} +import cats.effect.kernel.implicits._ +import cats.effect.kernel.Deferred +import cats.syntax.all._ + +import java.io.{InputStream, OutputStream} +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import scala.reflect.ClassTag + +private[fs2] trait iojvmnative { + type InterruptedIOException = java.io.InterruptedIOException + type ClosedChannelException = java.nio.channels.ClosedChannelException + + // + // STDIN/STDOUT Helpers + + /** Stream of bytes read asynchronously from standard input. */ + def stdin[F[_]: Sync](bufSize: Int): Stream[F, Byte] = + readInputStream(Sync[F].blocking(System.in), bufSize, false) + + /** Pipe of bytes that writes emitted values to standard output asynchronously. */ + def stdout[F[_]: Sync]: Pipe[F, Byte, Nothing] = + writeOutputStream(Sync[F].blocking(System.out), false) + + /** Pipe of bytes that writes emitted values to standard error asynchronously. */ + def stderr[F[_]: Sync]: Pipe[F, Byte, Nothing] = + writeOutputStream(Sync[F].blocking(System.err), false) + + /** Writes this stream to standard output asynchronously, converting each element to + * a sequence of bytes via `Show` and the given `Charset`. + */ + def stdoutLines[F[_]: Sync, O: Show]( + charset: Charset = StandardCharsets.UTF_8 + ): Pipe[F, O, Nothing] = + _.map(_.show).through(text.encode(charset)).through(stdout) + + /** Stream of `String` read asynchronously from standard input decoded in UTF-8. */ + def stdinUtf8[F[_]: Sync](bufSize: Int): Stream[F, String] = + stdin(bufSize).through(text.utf8.decode) + + /** Stream of bytes read asynchronously from the specified resource relative to the class `C`. + * @see [[readClassLoaderResource]] for a resource relative to a classloader. + */ + def readClassResource[F[_], C]( + name: String, + chunkSize: Int = 64 * 1024 + )(implicit F: Sync[F], ct: ClassTag[C]): Stream[F, Byte] = + Stream.eval(F.blocking(Option(ct.runtimeClass.getResourceAsStream(name)))).flatMap { + case Some(resource) => io.readInputStream(resource.pure, chunkSize) + case None => Stream.raiseError(new IOException(s"Resource $name not found")) + } + + /** Stream of bytes read asynchronously from the specified classloader resource. + * @see [[readClassResource]] for a resource relative to a class. + */ + def readClassLoaderResource[F[_]]( + name: String, + chunkSize: Int = 64 * 1024, + classLoader: ClassLoader = getClass().getClassLoader() + )(implicit F: Sync[F]): Stream[F, Byte] = + Stream.eval(F.blocking(Option(classLoader.getResourceAsStream(name)))).flatMap { + case Some(resource) => io.readInputStream(resource.pure, chunkSize) + case None => Stream.raiseError(new IOException(s"Resource $name not found")) + } + +} diff --git a/io/jvm/src/main/scala/fs2/io/ioplatform.scala b/io/jvm/src/main/scala/fs2/io/ioplatform.scala index 6e5e334ae9..4ef02084b9 100644 --- a/io/jvm/src/main/scala/fs2/io/ioplatform.scala +++ b/io/jvm/src/main/scala/fs2/io/ioplatform.scala @@ -34,9 +34,7 @@ import java.nio.charset.Charset import java.nio.charset.StandardCharsets import scala.reflect.ClassTag -private[fs2] trait ioplatform { - type InterruptedIOException = java.io.InterruptedIOException - type ClosedChannelException = java.nio.channels.ClosedChannelException +private[fs2] trait ioplatform extends iojvmnative { /** Pipe that converts a stream of bytes to a stream that will emit a single `java.io.InputStream`, * that is closed whenever the resulting stream terminates. @@ -111,56 +109,4 @@ private[fs2] trait ioplatform { } } - // - // STDIN/STDOUT Helpers - - /** Stream of bytes read asynchronously from standard input. */ - def stdin[F[_]: Sync](bufSize: Int): Stream[F, Byte] = - readInputStream(Sync[F].blocking(System.in), bufSize, false) - - /** Pipe of bytes that writes emitted values to standard output asynchronously. */ - def stdout[F[_]: Sync]: Pipe[F, Byte, Nothing] = - writeOutputStream(Sync[F].blocking(System.out), false) - - /** Pipe of bytes that writes emitted values to standard error asynchronously. */ - def stderr[F[_]: Sync]: Pipe[F, Byte, Nothing] = - writeOutputStream(Sync[F].blocking(System.err), false) - - /** Writes this stream to standard output asynchronously, converting each element to - * a sequence of bytes via `Show` and the given `Charset`. - */ - def stdoutLines[F[_]: Sync, O: Show]( - charset: Charset = StandardCharsets.UTF_8 - ): Pipe[F, O, Nothing] = - _.map(_.show).through(text.encode(charset)).through(stdout) - - /** Stream of `String` read asynchronously from standard input decoded in UTF-8. */ - def stdinUtf8[F[_]: Sync](bufSize: Int): Stream[F, String] = - stdin(bufSize).through(text.utf8.decode) - - /** Stream of bytes read asynchronously from the specified resource relative to the class `C`. - * @see [[readClassLoaderResource]] for a resource relative to a classloader. - */ - def readClassResource[F[_], C]( - name: String, - chunkSize: Int = 64 * 1024 - )(implicit F: Sync[F], ct: ClassTag[C]): Stream[F, Byte] = - Stream.eval(F.blocking(Option(ct.runtimeClass.getResourceAsStream(name)))).flatMap { - case Some(resource) => io.readInputStream(resource.pure, chunkSize) - case None => Stream.raiseError(new IOException(s"Resource $name not found")) - } - - /** Stream of bytes read asynchronously from the specified classloader resource. - * @see [[readClassResource]] for a resource relative to a class. - */ - def readClassLoaderResource[F[_]]( - name: String, - chunkSize: Int = 64 * 1024, - classLoader: ClassLoader = getClass().getClassLoader() - )(implicit F: Sync[F]): Stream[F, Byte] = - Stream.eval(F.blocking(Option(classLoader.getResourceAsStream(name)))).flatMap { - case Some(resource) => io.readInputStream(resource.pure, chunkSize) - case None => Stream.raiseError(new IOException(s"Resource $name not found")) - } - } diff --git a/io/native/src/main/scala/fs2/io/ioplatform.scala b/io/native/src/main/scala/fs2/io/ioplatform.scala index 9b4fff9876..e70d86df12 100644 --- a/io/native/src/main/scala/fs2/io/ioplatform.scala +++ b/io/native/src/main/scala/fs2/io/ioplatform.scala @@ -33,60 +33,4 @@ import java.nio.charset.Charset import java.nio.charset.StandardCharsets import scala.reflect.ClassTag -private[fs2] trait ioplatform { - type InterruptedIOException = java.io.InterruptedIOException - type ClosedChannelException = java.nio.channels.ClosedChannelException - - // - // STDIN/STDOUT Helpers - - /** Stream of bytes read asynchronously from standard input. */ - def stdin[F[_]: Sync](bufSize: Int): Stream[F, Byte] = - readInputStream(Sync[F].blocking(System.in), bufSize, false) - - /** Pipe of bytes that writes emitted values to standard output asynchronously. */ - def stdout[F[_]: Sync]: Pipe[F, Byte, Nothing] = - writeOutputStream(Sync[F].blocking(System.out), false) - - /** Pipe of bytes that writes emitted values to standard error asynchronously. */ - def stderr[F[_]: Sync]: Pipe[F, Byte, Nothing] = - writeOutputStream(Sync[F].blocking(System.err), false) - - /** Writes this stream to standard output asynchronously, converting each element to - * a sequence of bytes via `Show` and the given `Charset`. - */ - def stdoutLines[F[_]: Sync, O: Show]( - charset: Charset = StandardCharsets.UTF_8 - ): Pipe[F, O, Nothing] = - _.map(_.show).through(text.encode(charset)).through(stdout) - - /** Stream of `String` read asynchronously from standard input decoded in UTF-8. */ - def stdinUtf8[F[_]: Sync](bufSize: Int): Stream[F, String] = - stdin(bufSize).through(text.utf8.decode) - - /** Stream of bytes read asynchronously from the specified resource relative to the class `C`. - * @see [[readClassLoaderResource]] for a resource relative to a classloader. - */ - def readClassResource[F[_], C]( - name: String, - chunkSize: Int = 64 * 1024 - )(implicit F: Sync[F], ct: ClassTag[C]): Stream[F, Byte] = - Stream.eval(F.blocking(Option(ct.runtimeClass.getResourceAsStream(name)))).flatMap { - case Some(resource) => io.readInputStream(resource.pure, chunkSize) - case None => Stream.raiseError(new IOException(s"Resource $name not found")) - } - - /** Stream of bytes read asynchronously from the specified classloader resource. - * @see [[readClassResource]] for a resource relative to a class. - */ - def readClassLoaderResource[F[_]]( - name: String, - chunkSize: Int = 64 * 1024, - classLoader: ClassLoader = getClass().getClassLoader() - )(implicit F: Sync[F]): Stream[F, Byte] = - Stream.eval(F.blocking(Option(classLoader.getResourceAsStream(name)))).flatMap { - case Some(resource) => io.readInputStream(resource.pure, chunkSize) - case None => Stream.raiseError(new IOException(s"Resource $name not found")) - } - -} +private[fs2] trait ioplatform extends iojvmnative From 6f059227de26dec2c302529e5c4f3cb6fd06b770 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 04:48:41 +0000 Subject: [PATCH 52/83] version introduced --- build.sbt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 4c202bf13b..0d06995b04 100644 --- a/build.sbt +++ b/build.sbt @@ -180,6 +180,10 @@ lazy val root = tlCrossRootProject lazy val IntegrationTest = config("it").extend(Test) +lazy val commonNativeSettings = Seq( + tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.2.15").toMap +) + lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("core")) .configs(IntegrationTest) @@ -206,7 +210,6 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) Compile / doc / scalacOptions ++= (if (scalaVersion.value.startsWith("2.")) Seq("-nowarn") else Nil) ) - .nativeConfigure(_.disablePlugins(DoctestPlugin)) lazy val coreJVM = core.jvm .settings( @@ -222,6 +225,10 @@ lazy val coreJS = core.js scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) +lazy val coreNative = core.native + .disablePlugins(DoctestPlugin) + .settings(commonNativeSettings) + lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("io")) .settings( @@ -240,6 +247,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.1.0").toMap, scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) + .nativeSettings(commonNativeSettings) .nativeSettings( libraryDependencies ++= Seq( "com.armanbilge" %%% "epollcat" % "0.0-70bf67b" % Test From ee502df36bb4cefd68e958b31bfc03924ddca74b Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 05:11:09 +0000 Subject: [PATCH 53/83] Compile fixing in io --- .../fs2/io/net/tls/TLSContextPlatform.scala | 6 ++++++ .../src/main/scala/fs2/io/iojvmnative.scala | 4 +--- io/jvm/src/main/scala/fs2/io/ioplatform.scala | 4 ---- .../fs2/io/net/tls/TLSContextPlatform.scala | 8 +++++++- .../src/main/scala/fs2/io/ioplatform.scala | 11 ----------- .../scala/fs2/io/net/NetworkPlatform.scala | 2 -- .../fs2/io/net/tls/TLSContextPlatform.scala | 18 +++--------------- .../scala/fs2/io/net/tls/TLSSocketSuite.scala | 2 +- .../main/scala/fs2/io/net/tls/TLSContext.scala | 6 +----- 9 files changed, 19 insertions(+), 42 deletions(-) diff --git a/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 0c2fabe46e..2d7733fe11 100644 --- a/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -143,8 +143,14 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => def system: F[TLSContext[F]] = Async[F].delay(fromSecureContext(SecureContext.default)) + def systemResource: Resource[F, TLSContext[F]] = + Resource.eval(system) + def insecure: F[TLSContext[F]] = Async[F].delay(fromSecureContext(SecureContext.default, insecure = true)) + + def insecureResource: Resource[F, TLSContext[F]] = + Resource.eval(insecure) } } } diff --git a/io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala b/io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala index 0179b3abe3..4066bafc78 100644 --- a/io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala +++ b/io/jvm-native/src/main/scala/fs2/io/iojvmnative.scala @@ -23,12 +23,10 @@ package fs2 package io import cats._ -import cats.effect.kernel.{Async, Outcome, Resource, Sync} +import cats.effect.kernel.Sync import cats.effect.kernel.implicits._ -import cats.effect.kernel.Deferred import cats.syntax.all._ -import java.io.{InputStream, OutputStream} import java.nio.charset.Charset import java.nio.charset.StandardCharsets import scala.reflect.ClassTag diff --git a/io/jvm/src/main/scala/fs2/io/ioplatform.scala b/io/jvm/src/main/scala/fs2/io/ioplatform.scala index 4ef02084b9..1bf51864aa 100644 --- a/io/jvm/src/main/scala/fs2/io/ioplatform.scala +++ b/io/jvm/src/main/scala/fs2/io/ioplatform.scala @@ -22,7 +22,6 @@ package fs2 package io -import cats._ import cats.effect.kernel.{Async, Outcome, Resource, Sync} import cats.effect.kernel.implicits._ import cats.effect.kernel.Deferred @@ -30,9 +29,6 @@ import cats.syntax.all._ import fs2.io.internal.PipedStreamBuffer import java.io.{InputStream, OutputStream} -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import scala.reflect.ClassTag private[fs2] trait ioplatform extends iojvmnative { diff --git a/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 19189e4826..1decde35d9 100644 --- a/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -220,7 +220,7 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => sslEngine.flatMap(TLSEngine[F](_, binding, logger)) } } - + def insecure: F[TLSContext[F]] = Async[F] .blocking { @@ -259,9 +259,15 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => } .map(fromSSLContext(_)) + def insecureResource: Resource[F, TLSContext[F]] = + Resource.eval(insecure) + def system: F[TLSContext[F]] = Async[F].blocking(SSLContext.getDefault).map(fromSSLContext(_)) + def systemResource: Resource[F, TLSContext[F]] = + Resource.eval(system) + def fromKeyStoreFile( file: Path, storePassword: Array[Char], diff --git a/io/native/src/main/scala/fs2/io/ioplatform.scala b/io/native/src/main/scala/fs2/io/ioplatform.scala index e70d86df12..4cdd4d1f8f 100644 --- a/io/native/src/main/scala/fs2/io/ioplatform.scala +++ b/io/native/src/main/scala/fs2/io/ioplatform.scala @@ -22,15 +22,4 @@ package fs2 package io -import cats._ -import cats.effect.kernel.{Async, Outcome, Resource, Sync} -import cats.effect.kernel.implicits._ -import cats.effect.kernel.Deferred -import cats.syntax.all._ - -import java.io.{InputStream, OutputStream} -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import scala.reflect.ClassTag - private[fs2] trait ioplatform extends iojvmnative diff --git a/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala b/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala index 9515bf4e47..f2aba3b524 100644 --- a/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/NetworkPlatform.scala @@ -27,8 +27,6 @@ import cats.effect.kernel.{Async, Resource} import com.comcast.ip4s.{Host, IpAddress, Port, SocketAddress} -import java.net.ProtocolFamily -import java.nio.channels.AsynchronousChannelGroup import fs2.io.net.tls.TLSContext private[net] trait NetworkPlatform[F[_]] diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 320fe29375..07a57c9c8a 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -24,14 +24,7 @@ package io package net package tls -import java.io.{FileInputStream, InputStream} -import cats.Applicative import cats.effect.kernel.{Async, Resource} -import cats.syntax.all._ -import com.comcast.ip4s.{IpAddress, SocketAddress} - -import java.util.function.BiFunction -import java.nio.file.Path private[tls] trait TLSContextPlatform[F[_]] @@ -39,22 +32,15 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => private[tls] trait BuilderPlatform[F[_]] { def fromS2nConfig(ctx: S2nConfig): TLSContext[F] - - /** Creates a `TLSContext` which trusts all certificates. */ - def insecure: F[TLSContext[F]] } private[tls] trait BuilderCompanionPlatform { /** Creates a `TLSContext` from an `SSLContext`. */ private[tls] final class AsyncBuilder[F[_]: Async] extends Builder[F] { - def system: F[TLSContext[F]] = ??? - def systemResource: Resource[F, TLSContext[F]] = S2nConfig.builder.build.map(fromS2nConfig(_)) - def insecure: F[TLSContext[F]] = ??? - def insecureResource: Resource[F, TLSContext[F]] = S2nConfig.builder.withDisabledX509Verification.build.map(fromS2nConfig(_)) @@ -71,8 +57,10 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => clientMode: Boolean, params: TLSParameters, logger: TLSLogger[F] - ): Resource[F, TLSSocket[F]] = + ): Resource[F, TLSSocket[F]] = { + val _ = logger S2nConnection(socket, clientMode, cfg, params).flatMap(TLSSocket(socket, _)) + } } } diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 99830fe3df..94bcfd083a 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -26,7 +26,7 @@ package tls import scala.concurrent.duration._ -import cats.effect.{IO, Resource} +import cats.effect.IO import cats.syntax.all._ import com.comcast.ip4s._ diff --git a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala index 88ea81a11f..bd5fff7ead 100644 --- a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala +++ b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala @@ -69,13 +69,9 @@ object TLSContext extends TLSContextCompanionPlatform { trait Builder[F[_]] extends BuilderPlatform[F] { /** Creates a `TLSContext` from the system default `SSLContext`. */ - def system: F[TLSContext[F]] - - /** Creates a `TLSContext` which trusts all certificates. */ - def insecure: F[TLSContext[F]] - def systemResource: Resource[F, TLSContext[F]] + /** Creates a `TLSContext` which trusts all certificates. */ def insecureResource: Resource[F, TLSContext[F]] } From 5b8179dd08e304ceb781cd9ca0f127fd9d029b23 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 18:16:56 +0000 Subject: [PATCH 54/83] scodec native --- .github/workflows/ci.yml | 4 ++-- build.sbt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c727f936a..09f99dfaf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,11 +100,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/2.5.x') - run: mkdir -p target protocols/js/target io/native/target unidocs/target .js/target core/native/target core/js/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target benchmark/target project/target + run: mkdir -p target protocols/js/target io/native/target unidocs/target .js/target core/native/target core/js/target protocols/native/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target scodec/native/target benchmark/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/2.5.x') - run: tar cf targets.tar target protocols/js/target io/native/target unidocs/target .js/target core/native/target core/js/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target benchmark/target project/target + run: tar cf targets.tar target protocols/js/target io/native/target unidocs/target .js/target core/native/target core/js/target protocols/native/target mdoc/target core/jvm/target .jvm/target .native/target scodec/jvm/target scodec/js/target io/js/target reactive-streams/target io/jvm/target protocols/jvm/target scodec/native/target benchmark/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/series/2.5.x') diff --git a/build.sbt b/build.sbt index 0d06995b04..bf7eb227a4 100644 --- a/build.sbt +++ b/build.sbt @@ -317,14 +317,14 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) nativeConfig ~= { _.withEmbedResources(true) } ) -lazy val scodec = crossProject(JVMPlatform, JSPlatform) +lazy val scodec = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("scodec")) .settings( name := "fs2-scodec", libraryDependencies += "org.scodec" %%% "scodec-core" % (if ( scalaVersion.value.startsWith("2.") ) - "1.11.9" + "1.11.10" else "2.1.0"), tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.2.0").toMap, tlJdkRelease := Some(8) @@ -334,7 +334,7 @@ lazy val scodec = crossProject(JVMPlatform, JSPlatform) ) .dependsOn(core % "compile->compile;test->test", io % "test") -lazy val protocols = crossProject(JVMPlatform, JSPlatform) +lazy val protocols = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("protocols")) .settings( name := "fs2-protocols", From 857d160865f4f9c823ab3d4b76b50da85d013978 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 18:20:04 +0000 Subject: [PATCH 55/83] Version introduced --- build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sbt b/build.sbt index bf7eb227a4..96bc0524fb 100644 --- a/build.sbt +++ b/build.sbt @@ -332,6 +332,7 @@ lazy val scodec = crossProject(JVMPlatform, JSPlatform, NativePlatform) .jsSettings( scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) + .nativeSettings(commonNativeSettings) .dependsOn(core % "compile->compile;test->test", io % "test") lazy val protocols = crossProject(JVMPlatform, JSPlatform, NativePlatform) @@ -344,6 +345,7 @@ lazy val protocols = crossProject(JVMPlatform, JSPlatform, NativePlatform) .jsSettings( scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) + .nativeSettings(commonNativeSettings) .dependsOn(core % "compile->compile;test->test", scodec, io) lazy val reactiveStreams = project From 967fb9e0d4ce1928b1c64d8a04f81653c1271c79 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 18:25:27 +0000 Subject: [PATCH 56/83] Bump epollcat --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 96bc0524fb..c0f79df734 100644 --- a/build.sbt +++ b/build.sbt @@ -250,7 +250,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .nativeSettings(commonNativeSettings) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat" % "0.0-70bf67b" % Test + "com.armanbilge" %%% "epollcat" % "0.0-3c7bdac" % Test ), nativeConfig ~= { c => if (isLinux) { // brew-installed s2n From 785be47fc6c6b07aaabd609ffa884ec035700b0f Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 18:28:44 +0000 Subject: [PATCH 57/83] Cross-compile unix socket trait, no impl tho --- .../net/unixsocket/UnixSocketsPlatform.scala | 24 +++++++++++++++++++ .../fs2/io/net/unixsocket/UnixSockets.scala | 0 2 files changed, 24 insertions(+) create mode 100644 io/native/src/main/scala/fs2/io/net/unixsocket/UnixSocketsPlatform.scala rename io/{js-jvm => shared}/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala (100%) diff --git a/io/native/src/main/scala/fs2/io/net/unixsocket/UnixSocketsPlatform.scala b/io/native/src/main/scala/fs2/io/net/unixsocket/UnixSocketsPlatform.scala new file mode 100644 index 0000000000..e5599efece --- /dev/null +++ b/io/native/src/main/scala/fs2/io/net/unixsocket/UnixSocketsPlatform.scala @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2.io.net.unixsocket + +private[unixsocket] trait UnixSocketsCompanionPlatform diff --git a/io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala b/io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala similarity index 100% rename from io/js-jvm/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala rename to io/shared/src/main/scala/fs2/io/net/unixsocket/UnixSockets.scala From 0db9d72e40ed45207d2eb97827d6b4f1d60cc99e Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 18:31:04 +0000 Subject: [PATCH 58/83] formatting --- io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 1decde35d9..6c1aed2b8e 100644 --- a/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -220,7 +220,7 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => sslEngine.flatMap(TLSEngine[F](_, binding, logger)) } } - + def insecure: F[TLSContext[F]] = Async[F] .blocking { From 3b9ebe072d989cef8cdd039eeb47f3aa6ae8745a Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:09:53 +0000 Subject: [PATCH 59/83] Fix io test compile --- io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 2d7733fe11..ba43770137 100644 --- a/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -38,6 +38,8 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => private[tls] trait BuilderPlatform[F[_]] { def fromSecureContext(context: SecureContext): TLSContext[F] + def system: F[TLSContext[F]] + def insecure: F[TLSContext[F]] } private[tls] trait BuilderCompanionPlatform { From 1cdd943459a7d81a8168f1a5234ab0f22f8d9904 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:25:24 +0000 Subject: [PATCH 60/83] Fix deprecated override --- io/native/src/test/scala/fs2/io/Fs2IoSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala index 7d73620072..ba45d08acb 100644 --- a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala +++ b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -25,5 +25,5 @@ package io import epollcat.unsafe.EpollRuntime abstract class Fs2IoSuite extends Fs2Suite { - override lazy val munitIoRuntime = EpollRuntime.global + override lazy val munitIORuntime = EpollRuntime.global } From 8b70f202c989f5994fa180a5c074f946698a4bc8 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:26:00 +0000 Subject: [PATCH 61/83] forgot to commit --- .../src/test/scala/fs2/io/Fs2IoSuite.scala | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 io/js-jvm/src/test/scala/fs2/io/Fs2IoSuite.scala diff --git a/io/js-jvm/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/js-jvm/src/test/scala/fs2/io/Fs2IoSuite.scala new file mode 100644 index 0000000000..76c8042f20 --- /dev/null +++ b/io/js-jvm/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package fs2 +package io + +abstract class Fs2IoSuite extends Fs2Suite From ef3e0faa65c8f059fc7b0914dba6466bcd48170e Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:32:38 +0000 Subject: [PATCH 62/83] Fix assertion --- io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 94bcfd083a..0e9366c94a 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -279,7 +279,7 @@ class TLSSocketSuite extends TLSSuite { val checkBlinding = echo.attempt.flatMap(result => IO(assert(clue(result).isLeft))).timed.flatMap { - case (duration, _) => IO(assert(clue(duration) > 9.seconds), "didn't blind on error") + case (duration, _) => IO(assert(clue(duration) > 9.seconds, "didn't blind on error")) } checkStarvation.both(checkBlinding).void From 86008bb0004ca7d3b572eb5b09de4bf4c5bc9da8 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:38:59 +0000 Subject: [PATCH 63/83] Install s2n in ci --- .github/workflows/ci.yml | 4 ++++ build.sbt | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09f99dfaf4..ddebf951d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,10 @@ jobs: ~/Library/Caches/Coursier/v1 key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + - name: Install s2n + if: startsWith(matrix.project, 'rootNative') + run: brew install s2n + - name: Check that workflows are up to date run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' 'project /' githubWorkflowCheck diff --git a/build.sbt b/build.sbt index c0f79df734..4a05aa87a8 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,12 @@ ThisBuild / crossScalaVersions := Seq("3.1.3", "2.12.16", NewScala) ThisBuild / tlVersionIntroduced := Map("3" -> "3.0.3") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) +ThisBuild / githubWorkflowBuildPreamble += + WorkflowStep.Run( + List("brew install s2n"), + name = Some("Install s2n"), + cond = Some("startsWith(matrix.project, 'rootNative')") + ) val isLinux = { val osName = Option(System.getProperty("os.name")) osName.exists(_.toLowerCase().contains("linux")) From 549bd37f20d532db4c66e4f488dcdc52e6d99825 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:39:54 +0000 Subject: [PATCH 64/83] Bump the other scodec --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 4a05aa87a8..e2042d0eae 100644 --- a/build.sbt +++ b/build.sbt @@ -331,7 +331,7 @@ lazy val scodec = crossProject(JVMPlatform, JSPlatform, NativePlatform) scalaVersion.value.startsWith("2.") ) "1.11.10" - else "2.1.0"), + else "2.2.0"), tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "3.2.0").toMap, tlJdkRelease := Some(8) ) From dacd75af8d609d17969a4446d9204e82401555df Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:52:39 +0000 Subject: [PATCH 65/83] seal tlx context builder, fix compile --- io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala | 2 +- io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala | 4 +++- .../src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala | 2 +- io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index ba43770137..01be081296 100644 --- a/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/js/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -43,7 +43,7 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => } private[tls] trait BuilderCompanionPlatform { - private[tls] final class AsyncBuilder[F[_]](implicit F: Async[F]) extends Builder[F] { + private[tls] final class AsyncBuilder[F[_]](implicit F: Async[F]) extends UnsealedBuilder[F] { def fromSecureContext( context: SecureContext, diff --git a/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 6c1aed2b8e..681f7793ef 100644 --- a/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/jvm/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -100,6 +100,8 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => private[tls] trait BuilderPlatform[F[_]] { def fromSSLContext(ctx: SSLContext): TLSContext[F] + def system: F[TLSContext[F]] + def insecure: F[TLSContext[F]] /** Creates a `TLSContext` from the specified key store file. */ def fromKeyStoreFile( @@ -125,7 +127,7 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => private[tls] trait BuilderCompanionPlatform { /** Creates a `TLSContext` from an `SSLContext`. */ - private[tls] final class AsyncBuilder[F[_]: Async] extends Builder[F] { + private[tls] final class AsyncBuilder[F[_]: Async] extends UnsealedBuilder[F] { def fromSSLContext( ctx: SSLContext diff --git a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala index 07a57c9c8a..ad1a7e3971 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/TLSContextPlatform.scala @@ -37,7 +37,7 @@ private[tls] trait TLSContextCompanionPlatform { self: TLSContext.type => private[tls] trait BuilderCompanionPlatform { /** Creates a `TLSContext` from an `SSLContext`. */ - private[tls] final class AsyncBuilder[F[_]: Async] extends Builder[F] { + private[tls] final class AsyncBuilder[F[_]: Async] extends UnsealedBuilder[F] { def systemResource: Resource[F, TLSContext[F]] = S2nConfig.builder.build.map(fromS2nConfig(_)) diff --git a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala index bd5fff7ead..56ea6bf789 100644 --- a/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala +++ b/io/shared/src/main/scala/fs2/io/net/tls/TLSContext.scala @@ -66,7 +66,7 @@ object TLSContext extends TLSContextCompanionPlatform { private[tls] trait UnsealedTLSContext[F[_]] extends TLSContext[F] - trait Builder[F[_]] extends BuilderPlatform[F] { + sealed trait Builder[F[_]] extends BuilderPlatform[F] { /** Creates a `TLSContext` from the system default `SSLContext`. */ def systemResource: Resource[F, TLSContext[F]] @@ -76,6 +76,8 @@ object TLSContext extends TLSContextCompanionPlatform { } + private[tls] trait UnsealedBuilder[F[_]] extends Builder[F] + object Builder extends BuilderCompanionPlatform { def forAsync[F[_]: Async]: Builder[F] = new AsyncBuilder } From 9ed131b61c47e9013359fdcb8474b91544866b30 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 19:58:56 +0000 Subject: [PATCH 66/83] mima filters --- build.sbt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e2042d0eae..80fd419b22 100644 --- a/build.sbt +++ b/build.sbt @@ -170,7 +170,12 @@ ThisBuild / mimaBinaryIssueFilters ++= Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("fs2.ChunkCompanionPlatform.makeArrayBuilder"), ProblemFilters.exclude[ReversedMissingMethodProblem]("fs2.concurrent.Channel.trySend"), ProblemFilters.exclude[ReversedMissingMethodProblem]("fs2.compression.Compression.gunzip"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("fs2.io.net.tls.TLSContext#Builder.insecure") + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "fs2.io.net.tls.TLSContext#Builder.systemResource" + ), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "fs2.io.net.tls.TLSContext#Builder.insecureResource" + ) ) lazy val root = tlCrossRootProject From 31be8b9fdb2287376d8c82a5801117d1514e5da2 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 20:19:46 +0000 Subject: [PATCH 67/83] sigh --- io/native/src/test/scala/fs2/io/Fs2IoSuite.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala index ba45d08acb..2c5c00d36e 100644 --- a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala +++ b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -24,6 +24,8 @@ package io import epollcat.unsafe.EpollRuntime +import scala.annotation.nowarn + abstract class Fs2IoSuite extends Fs2Suite { - override lazy val munitIORuntime = EpollRuntime.global + override lazy val munitIoRuntime = EpollRuntime.global: @nowarn } From ea0c72132f3aa5edf1213832a190c4a0edb6d52d Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 20:20:26 +0000 Subject: [PATCH 68/83] Or try this maybe? --- io/native/src/test/scala/fs2/io/Fs2IoSuite.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala index 2c5c00d36e..fcb4338366 100644 --- a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala +++ b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -24,8 +24,6 @@ package io import epollcat.unsafe.EpollRuntime -import scala.annotation.nowarn - abstract class Fs2IoSuite extends Fs2Suite { - override lazy val munitIoRuntime = EpollRuntime.global: @nowarn + override val munitIORuntime = EpollRuntime.global } From c1cd60d8937752cab00255e84bdafd958d5d087d Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 20:29:41 +0000 Subject: [PATCH 69/83] Revert "Or try this maybe?" This reverts commit ea0c72132f3aa5edf1213832a190c4a0edb6d52d. --- io/native/src/test/scala/fs2/io/Fs2IoSuite.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala index fcb4338366..2c5c00d36e 100644 --- a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala +++ b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -24,6 +24,8 @@ package io import epollcat.unsafe.EpollRuntime +import scala.annotation.nowarn + abstract class Fs2IoSuite extends Fs2Suite { - override val munitIORuntime = EpollRuntime.global + override lazy val munitIoRuntime = EpollRuntime.global: @nowarn } From 41f8c18c4c32085e863a4bff5aaf34b2f097fcfe Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 20:30:06 +0000 Subject: [PATCH 70/83] No idea --- io/native/src/test/scala/fs2/io/Fs2IoSuite.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala index 2c5c00d36e..7d73620072 100644 --- a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala +++ b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -24,8 +24,6 @@ package io import epollcat.unsafe.EpollRuntime -import scala.annotation.nowarn - abstract class Fs2IoSuite extends Fs2Suite { - override lazy val munitIoRuntime = EpollRuntime.global: @nowarn + override lazy val munitIoRuntime = EpollRuntime.global } From dd1f3e6f8e4e986383998861e280ec7ec1852c34 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 21:04:18 +0000 Subject: [PATCH 71/83] Try try again --- io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 2 ++ io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 0e9366c94a..1c49daa386 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -32,6 +32,8 @@ import cats.syntax.all._ import com.comcast.ip4s._ class TLSSocketSuite extends TLSSuite { + override lazy val munitIoRuntime = EpollRuntime.global + val size = 8192 group("TLSSocket") { diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala index 304c683f7c..29448f6af3 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala @@ -28,7 +28,7 @@ import cats.effect.IO import cats.effect.kernel.Resource import scodec.bits.ByteVector -abstract class TLSSuite extends Fs2IoSuite { +abstract class TLSSuite extends Fs2Suite { def testTlsContext: Resource[IO, TLSContext[IO]] = for { cert <- Resource.eval { readClassResource[IO, TLSSuite]("/cert.pem").compile.to(ByteVector) From dc1099c4a4c84bef91c7d72b3d485ad0074874a8 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 21:23:02 +0000 Subject: [PATCH 72/83] stupid --- io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 1c49daa386..b6c321a6a3 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -28,8 +28,8 @@ import scala.concurrent.duration._ import cats.effect.IO import cats.syntax.all._ - import com.comcast.ip4s._ +import epollcat.unsafe.EpollRuntime class TLSSocketSuite extends TLSSuite { override lazy val munitIoRuntime = EpollRuntime.global From 92c656783bb328c4173d648aaac08b56d9563710 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 22:19:45 +0000 Subject: [PATCH 73/83] I obviously don't know how this works --- io/native/src/test/scala/fs2/io/Fs2IoSuite.scala | 2 +- io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 4 ---- io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala | 2 +- io/shared/src/test/scala/fs2/io/IoSuite.scala | 2 +- io/shared/src/test/scala/fs2/io/file/FilesSuite.scala | 2 +- io/shared/src/test/scala/fs2/io/file/PathSuite.scala | 2 +- .../src/test/scala/fs2/io/file/PosixPermissionsSuite.scala | 2 +- 7 files changed, 6 insertions(+), 10 deletions(-) diff --git a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala index 7d73620072..40512de1ab 100644 --- a/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala +++ b/io/native/src/test/scala/fs2/io/Fs2IoSuite.scala @@ -25,5 +25,5 @@ package io import epollcat.unsafe.EpollRuntime abstract class Fs2IoSuite extends Fs2Suite { - override lazy val munitIoRuntime = EpollRuntime.global + override def munitIORuntime = EpollRuntime.global } diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index b6c321a6a3..772af0293b 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -29,12 +29,8 @@ import scala.concurrent.duration._ import cats.effect.IO import cats.syntax.all._ import com.comcast.ip4s._ -import epollcat.unsafe.EpollRuntime class TLSSocketSuite extends TLSSuite { - override lazy val munitIoRuntime = EpollRuntime.global - - val size = 8192 group("TLSSocket") { group("google") { diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala index 29448f6af3..304c683f7c 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSuite.scala @@ -28,7 +28,7 @@ import cats.effect.IO import cats.effect.kernel.Resource import scodec.bits.ByteVector -abstract class TLSSuite extends Fs2Suite { +abstract class TLSSuite extends Fs2IoSuite { def testTlsContext: Resource[IO, TLSContext[IO]] = for { cert <- Resource.eval { readClassResource[IO, TLSSuite]("/cert.pem").compile.to(ByteVector) diff --git a/io/shared/src/test/scala/fs2/io/IoSuite.scala b/io/shared/src/test/scala/fs2/io/IoSuite.scala index 23ffd78022..2790ab1d78 100644 --- a/io/shared/src/test/scala/fs2/io/IoSuite.scala +++ b/io/shared/src/test/scala/fs2/io/IoSuite.scala @@ -28,7 +28,7 @@ import org.scalacheck.effect.PropF.forAllF import java.io.ByteArrayInputStream import java.io.InputStream -class IoSuite extends Fs2Suite { +class IoSuite extends Fs2IoSuite { group("readInputStream") { test("non-buffered") { forAllF { (bytes: Array[Byte], chunkSize0: Int) => diff --git a/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala b/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala index 25d20cca72..21d024ec67 100644 --- a/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala +++ b/io/shared/src/test/scala/fs2/io/file/FilesSuite.scala @@ -29,7 +29,7 @@ import cats.syntax.all._ import scala.concurrent.duration._ -class FilesSuite extends Fs2Suite with BaseFileSuite { +class FilesSuite extends Fs2IoSuite with BaseFileSuite { group("readAll") { test("retrieves whole content of a file") { diff --git a/io/shared/src/test/scala/fs2/io/file/PathSuite.scala b/io/shared/src/test/scala/fs2/io/file/PathSuite.scala index d25a80305f..e0229b32df 100644 --- a/io/shared/src/test/scala/fs2/io/file/PathSuite.scala +++ b/io/shared/src/test/scala/fs2/io/file/PathSuite.scala @@ -31,7 +31,7 @@ import org.scalacheck.Cogen import org.scalacheck.Gen import org.scalacheck.Prop.forAll -class PathSuite extends Fs2Suite { +class PathSuite extends Fs2IoSuite { implicit val arbitraryPath: Arbitrary[Path] = Arbitrary(for { names <- Gen.listOf(Gen.alphaNumStr) diff --git a/io/shared/src/test/scala/fs2/io/file/PosixPermissionsSuite.scala b/io/shared/src/test/scala/fs2/io/file/PosixPermissionsSuite.scala index 79f116bde1..17df211606 100644 --- a/io/shared/src/test/scala/fs2/io/file/PosixPermissionsSuite.scala +++ b/io/shared/src/test/scala/fs2/io/file/PosixPermissionsSuite.scala @@ -23,7 +23,7 @@ package fs2 package io package file -class PosixPermissionsSuite extends Fs2Suite { +class PosixPermissionsSuite extends Fs2IoSuite { test("construction") { val cases = Seq( "777" -> "rwxrwxrwx", From 7902cbf7a67c5231031387727fc6a7586f8d5516 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 10 Sep 2022 22:26:27 +0000 Subject: [PATCH 74/83] fix compile --- io/shared/src/test/scala/fs2/io/IoSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/shared/src/test/scala/fs2/io/IoSuite.scala b/io/shared/src/test/scala/fs2/io/IoSuite.scala index 2790ab1d78..387d32adad 100644 --- a/io/shared/src/test/scala/fs2/io/IoSuite.scala +++ b/io/shared/src/test/scala/fs2/io/IoSuite.scala @@ -28,7 +28,7 @@ import org.scalacheck.effect.PropF.forAllF import java.io.ByteArrayInputStream import java.io.InputStream -class IoSuite extends Fs2IoSuite { +class IoSuite extends io.Fs2IoSuite { group("readInputStream") { test("non-buffered") { forAllF { (bytes: Array[Byte], chunkSize0: Int) => From 9e561b4e9ba999a3fadde205b93e7ca9ebf6f7ce Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 12 Sep 2022 02:55:24 +0000 Subject: [PATCH 75/83] Exit TLS read-loop when total read == requested --- .../main/scala/fs2/io/net/tls/S2nConnection.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala index 6d3765b6fd..1d79f98f49 100644 --- a/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala +++ b/io/native/src/main/scala/fs2/io/net/tls/S2nConnection.scala @@ -146,9 +146,15 @@ private[tls] object S2nConnection { }.productL(F.delay(readTasks.get).flatten) .flatMap { case (blocked, readed) => val total = i + readed - if (blocked.toInt == S2N_NOT_BLOCKED) { + + def chunk: F[Option[Chunk[Byte]]] = + F.pure(Some(Chunk.byteVector(ByteVector.fromPtr(buf, total)))) + + if (total >= n) + chunk + else if (blocked.toInt == S2N_NOT_BLOCKED) { if (total > 0) - F.pure(Some(Chunk.byteVector(ByteVector.fromPtr(buf, total)))) + chunk else F.pure(None) } else go(total) @@ -172,7 +178,7 @@ private[tls] object S2nConnection { }.productL(F.delay(writeTasks.get).flatten) .flatMap { case (blocked, wrote) => val total = i + wrote - go(total).unlessA(blocked.toInt == S2N_NOT_BLOCKED && total == n) + go(total).unlessA(blocked.toInt == S2N_NOT_BLOCKED && total >= n) } go(0) From d1c22c81a3acbfc623ed36c95cf6662c25a200ea Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 12 Sep 2022 15:06:48 -0700 Subject: [PATCH 76/83] Bump epollcat --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 80fd419b22..f0f44149bc 100644 --- a/build.sbt +++ b/build.sbt @@ -261,7 +261,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .nativeSettings(commonNativeSettings) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat" % "0.0-3c7bdac" % Test + "com.armanbilge" %%% "epollcat" % "0.0-e7dd9a7" % Test ), nativeConfig ~= { c => if (isLinux) { // brew-installed s2n From 38670d9180b5785f836d96a1ed96184149304366 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 13 Sep 2022 20:31:11 +0000 Subject: [PATCH 77/83] bumps --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index f0f44149bc..c95592e2a8 100644 --- a/build.sbt +++ b/build.sbt @@ -209,9 +209,9 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % "2.8.0", "org.typelevel" %%% "cats-laws" % "2.8.0" % Test, - "org.typelevel" %%% "cats-effect" % "3.3.14-1-5d11fe9", - "org.typelevel" %%% "cats-effect-laws" % "3.3.14-1-5d11fe9" % Test, - "org.typelevel" %%% "cats-effect-testkit" % "3.3.14-1-5d11fe9" % Test, + "org.typelevel" %%% "cats-effect" % "3.3.14-5-4591364", + "org.typelevel" %%% "cats-effect-laws" % "3.3.14-5-4591364" % Test, + "org.typelevel" %%% "cats-effect-testkit" % "3.3.14-5-4591364" % Test, "org.scodec" %%% "scodec-bits" % "1.1.34", "org.typelevel" %%% "scalacheck-effect-munit" % "2.0-9366e44" % Test, "org.typelevel" %%% "munit-cats-effect" % "2.0-5e03bfc" % Test, @@ -261,7 +261,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .nativeSettings(commonNativeSettings) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat" % "0.0-e7dd9a7" % Test + "com.armanbilge" %%% "epollcat" % "0.0-ab1026e" % Test ), nativeConfig ~= { c => if (isLinux) { // brew-installed s2n From 83935680376899a003ca3b5cc7793b9477d7d242 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 13 Sep 2022 20:46:33 +0000 Subject: [PATCH 78/83] Increase timeout for tls suite --- io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala index 772af0293b..1936b2cbcd 100644 --- a/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala +++ b/io/native/src/test/scala/fs2/io/net/tls/TLSSocketSuite.scala @@ -31,6 +31,7 @@ import cats.syntax.all._ import com.comcast.ip4s._ class TLSSocketSuite extends TLSSuite { + override def munitIOTimeout = 1.minute group("TLSSocket") { group("google") { From 9ce950a6e5673a37354e7714f9441a0f043407dd Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Tue, 13 Sep 2022 22:31:49 +0000 Subject: [PATCH 79/83] Add clues --- core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala b/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala index 6cffc01ab3..39cd4de450 100644 --- a/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala +++ b/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala @@ -268,7 +268,7 @@ class StreamCombinatorsSuite extends Fs2Suite { .map { result => assertEquals(result.size, 1) val head = result.head - assert(head.toMillis >= (delay.toMillis - 5)) + assert(clue(head.toMillis) >= clue(delay.toMillis - 5)) } } From 75d9d25a4c52a186ef08e8c79f8bdd6951b2d62e Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 14 Sep 2022 02:05:48 +0000 Subject: [PATCH 80/83] bump ce --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index c95592e2a8..1e0a16872b 100644 --- a/build.sbt +++ b/build.sbt @@ -209,9 +209,9 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % "2.8.0", "org.typelevel" %%% "cats-laws" % "2.8.0" % Test, - "org.typelevel" %%% "cats-effect" % "3.3.14-5-4591364", - "org.typelevel" %%% "cats-effect-laws" % "3.3.14-5-4591364" % Test, - "org.typelevel" %%% "cats-effect-testkit" % "3.3.14-5-4591364" % Test, + "org.typelevel" %%% "cats-effect" % "3.3.14-6-d8a0441", + "org.typelevel" %%% "cats-effect-laws" % "3.3.14-6-d8a0441" % Test, + "org.typelevel" %%% "cats-effect-testkit" % "3.3.14-6-d8a0441" % Test, "org.scodec" %%% "scodec-bits" % "1.1.34", "org.typelevel" %%% "scalacheck-effect-munit" % "2.0-9366e44" % Test, "org.typelevel" %%% "munit-cats-effect" % "2.0-5e03bfc" % Test, From 55d4f94559195ade05ac625ff99c083882ebcc01 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 15 Sep 2022 02:54:26 +0000 Subject: [PATCH 81/83] Bumps --- build.sbt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 1e0a16872b..3b580c30c3 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import com.typesafe.tools.mima.core._ Global / onChangedBuildSource := ReloadOnSourceChanges -ThisBuild / tlBaseVersion := "3.2" +ThisBuild / tlBaseVersion := "3.3" ThisBuild / organization := "co.fs2" ThisBuild / organizationName := "Functional Streams for Scala" @@ -209,12 +209,12 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % "2.8.0", "org.typelevel" %%% "cats-laws" % "2.8.0" % Test, - "org.typelevel" %%% "cats-effect" % "3.3.14-6-d8a0441", - "org.typelevel" %%% "cats-effect-laws" % "3.3.14-6-d8a0441" % Test, - "org.typelevel" %%% "cats-effect-testkit" % "3.3.14-6-d8a0441" % Test, + "org.typelevel" %%% "cats-effect" % "3.3.14", + "org.typelevel" %%% "cats-effect-laws" % "3.3.14" % Test, + "org.typelevel" %%% "cats-effect-testkit" % "3.3.14" % Test, "org.scodec" %%% "scodec-bits" % "1.1.34", - "org.typelevel" %%% "scalacheck-effect-munit" % "2.0-9366e44" % Test, - "org.typelevel" %%% "munit-cats-effect" % "2.0-5e03bfc" % Test, + "org.typelevel" %%% "scalacheck-effect-munit" % "2.0.0-M2" % Test, + "org.typelevel" %%% "munit-cats-effect" % "2.0.0-M3" % Test, "org.typelevel" %%% "discipline-munit" % "2.0.0-M3" % Test ), tlJdkRelease := Some(8), @@ -245,7 +245,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( name := "fs2-io", tlVersionIntroduced ~= { _.updated("3", "3.1.0") }, - libraryDependencies += "com.armanbilge" %%% "ip4s-core" % "3.1.3-83-cfa2ec1" + libraryDependencies += "com.comcast" %%% "ip4s-core" % "3.2.0" ) .jvmSettings( Test / fork := true, @@ -261,7 +261,7 @@ lazy val io = crossProject(JVMPlatform, JSPlatform, NativePlatform) .nativeSettings(commonNativeSettings) .nativeSettings( libraryDependencies ++= Seq( - "com.armanbilge" %%% "epollcat" % "0.0-ab1026e" % Test + "com.armanbilge" %%% "epollcat" % "0.1.0" % Test ), nativeConfig ~= { c => if (isLinux) { // brew-installed s2n From 503db1704a8075d994226814d7ae01da2490070f Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 15 Sep 2022 03:06:26 +0000 Subject: [PATCH 82/83] Address deprecation --- io/jvm/src/test/scala/fs2/io/IoPlatformSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/jvm/src/test/scala/fs2/io/IoPlatformSuite.scala b/io/jvm/src/test/scala/fs2/io/IoPlatformSuite.scala index fb15855119..44c2135d26 100644 --- a/io/jvm/src/test/scala/fs2/io/IoPlatformSuite.scala +++ b/io/jvm/src/test/scala/fs2/io/IoPlatformSuite.scala @@ -39,7 +39,7 @@ import java.util.concurrent.Executors class IoPlatformSuite extends Fs2Suite { // This suite runs for a long time, this avoids timeouts in CI. - override def munitTimeout: Duration = 1.minute + override def munitIOTimeout: Duration = 1.minute group("readOutputStream") { test("writes data and terminates when `f` returns") { From 54782c9a6eca392c29f1f3db69d4bd970efb1fc7 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 15 Sep 2022 03:09:15 +0000 Subject: [PATCH 83/83] Doc updates --- site/getstarted/install.md | 10 ++++++++++ site/io.md | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/site/getstarted/install.md b/site/getstarted/install.md index b868cc2dfc..6d42c2f065 100644 --- a/site/getstarted/install.md +++ b/site/getstarted/install.md @@ -31,6 +31,16 @@ libraryDependencies += "co.fs2" %%% "fs2-io" % "@VERSION@" scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ``` +The fs2-core as well as fs2-io and fs2-scodec libraries are also supported on Scala Native: +``` +libraryDependencies += "co.fs2" %%% "fs2-core" % "@VERSION@" +libraryDependencies += "co.fs2" %%% "fs2-scodec" % "@VERSION@" + +// TCP support requires https://github.com/armanbilge/epollcat/ +// TLS support requires https://github.com/aws/s2n-tls +libraryDependencies += "co.fs2" %%% "fs2-io" % "@VERSION@" +``` + Release notes for each release are available on [Github](https://github.com/typelevel/fs2/releases/). If upgrading from the 2.x series, see the [release notes for 3.0.0](https://github.com/typelevel/fs2/releases/tag/v3.0.0) for help with upgrading. diff --git a/site/io.md b/site/io.md index 5f4aabfd58..ef2e6e31d6 100644 --- a/site/io.md +++ b/site/io.md @@ -1,6 +1,6 @@ # I/O -The `fs2-io` library provides support for performing input and output on the JVM and Node.js. This includes: +The `fs2-io` library provides support for performing input and output on the JVM, Node.js, and Scala Native. This includes: - [Networking](#networking) - [TCP](#tcp) - [UDP](#udp)