Skip to content

Commit

Permalink
Merge pull request #94 from http4s/pr/native
Browse files Browse the repository at this point in the history
Cross-build for Scala Native
  • Loading branch information
armanbilge authored Sep 15, 2022
2 parents 049da83 + ed97178 commit b1300b1
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 14 deletions.
48 changes: 45 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
os: [ubuntu-latest]
scala: [3.1.3, 2.12.16, 2.13.8]
java: [temurin@8, temurin@11, temurin@17]
project: [rootJS, rootJVM]
project: [rootJS, rootJVM, rootNative]
jsenv: [NodeJS, Chrome, Firefox]
exclude:
- scala: 3.1.3
Expand All @@ -46,6 +46,10 @@ jobs:
java: temurin@11
- project: rootJS
java: temurin@17
- project: rootNative
java: temurin@11
- project: rootNative
java: temurin@17
- scala: 3.1.3
jsenv: Chrome
- scala: 3.1.3
Expand All @@ -56,8 +60,12 @@ jobs:
jsenv: Firefox
- project: rootJVM
jsenv: Chrome
- project: rootNative
jsenv: Chrome
- project: rootJVM
jsenv: Firefox
- project: rootNative
jsenv: Firefox
runs-on: ${{ matrix.os }}
steps:
- name: Checkout current branch (full)
Expand Down Expand Up @@ -142,6 +150,10 @@ jobs:
if: matrix.project == 'rootJS'
run: 'sbt ''project ${{ matrix.project }}'' ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' Test/scalaJSLinkerResult'

- name: nativeLink
if: matrix.project == 'rootNative'
run: 'sbt ''project ${{ matrix.project }}'' ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' Test/nativeLink'

- name: Test
run: 'sbt ''project ${{ matrix.project }}'' ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' test'

Expand All @@ -163,11 +175,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p crypto/jvm/target target .js/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target project/target
run: mkdir -p crypto/jvm/target target .js/target crypto/native/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target test-runtime/.native/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar crypto/jvm/target target .js/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target project/target
run: tar cf targets.tar crypto/jvm/target target .js/target crypto/native/target .jvm/target .native/target test-runtime/.jvm/target crypto/js/target test-runtime/.js/target test-runtime/.native/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down Expand Up @@ -272,6 +284,16 @@ jobs:
tar xf targets.tar
rm targets.tar
- name: Download target directories (3.1.3, NodeJS, rootNative)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-NodeJS-rootNative

- name: Inflate target directories (3.1.3, NodeJS, rootNative)
run: |
tar xf targets.tar
rm targets.tar
- name: Download target directories (2.12.16, NodeJS, rootJS)
uses: actions/download-artifact@v2
with:
Expand All @@ -292,6 +314,16 @@ jobs:
tar xf targets.tar
rm targets.tar
- name: Download target directories (2.12.16, NodeJS, rootNative)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-NodeJS-rootNative

- name: Inflate target directories (2.12.16, NodeJS, rootNative)
run: |
tar xf targets.tar
rm targets.tar
- name: Download target directories (2.13.8, NodeJS, rootJS)
uses: actions/download-artifact@v2
with:
Expand All @@ -312,6 +344,16 @@ jobs:
tar xf targets.tar
rm targets.tar
- name: Download target directories (2.13.8, NodeJS, rootNative)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-NodeJS-rootNative

- name: Inflate target directories (2.13.8, NodeJS, 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
Expand Down
25 changes: 18 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ ThisBuild / githubWorkflowBuildMatrixExclusions ++= {
ThisBuild / githubWorkflowBuildMatrixExclusions ++= {
for {
jsenv <- jsenvs.tail
} yield MatrixExclude(Map("project" -> "rootJVM", "jsenv" -> jsenv))
project <- List("rootJVM", "rootNative")
} yield MatrixExclude(Map("project" -> project, "jsenv" -> jsenv))
}

lazy val useJSEnv =
Expand All @@ -80,13 +81,13 @@ ThisBuild / Test / jsEnv := {
val catsVersion = "2.8.0"
val catsEffectVersion = "3.3.14"
val scodecBitsVersion = "1.1.34"
val munitVersion = "0.7.29"
val munitCEVersion = "1.0.7"
val disciplineMUnitVersion = "1.0.9"
val munitVersion = "1.0.0-M6"
val munitCEVersion = "2.0.0-M3"
val disciplineMUnitVersion = "2.0.0-M3"

lazy val root = tlCrossRootProject.aggregate(crypto, testRuntime)

lazy val crypto = crossProject(JSPlatform, JVMPlatform)
lazy val crypto = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.in(file("crypto"))
.settings(
name := "http4s-crypto",
Expand All @@ -98,12 +99,16 @@ lazy val crypto = crossProject(JSPlatform, JVMPlatform)
"org.typelevel" %%% "cats-laws" % catsVersion % Test,
"org.typelevel" %%% "cats-effect" % catsEffectVersion % Test,
"org.typelevel" %%% "discipline-munit" % disciplineMUnitVersion % Test,
"org.typelevel" %%% "munit-cats-effect-3" % munitCEVersion % Test
"org.typelevel" %%% "munit-cats-effect" % munitCEVersion % Test
)
)
.nativeSettings(
tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.2.4").toMap,
unusedCompileDependenciesTest := {}
)
.dependsOn(testRuntime % Test)

lazy val testRuntime = crossProject(JSPlatform, JVMPlatform)
lazy val testRuntime = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("test-runtime"))
.enablePlugins(BuildInfoPlugin, NoPublishPlugin)
Expand All @@ -120,3 +125,9 @@ lazy val testRuntime = crossProject(JSPlatform, JVMPlatform)
BuildInfoKey("runtime" -> useJSEnv.value.toString)
)
)
.nativeSettings(
buildInfoKeys := Seq(
BuildInfoKey("runtime" -> "Native")
),
unusedCompileDependenciesTest := {}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2021 http4s.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.http4s.crypto

import cats.effect.kernel.Sync

private[crypto] trait CryptoCompanionPlatform {
implicit def forSync[F[_]: Sync]: Crypto[F] =
new UnsealedCrypto[F] {
override def hash: Hash[F] = Hash[F]
override def hmac: Hmac[F] = Hmac[F]
override def hmacKeyGen: HmacKeyGen[F] = HmacKeyGen[F]
}
}
56 changes: 56 additions & 0 deletions crypto/native/src/main/scala/org/http4s/crypto/HashPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2021 http4s.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.http4s.crypto

import cats.ApplicativeThrow
import scodec.bits.ByteVector

import scala.scalanative.unsafe._
import scala.scalanative.unsigned._

private[crypto] trait HashCompanionPlatform {
implicit def forApplicativeThrow[F[_]](implicit F: ApplicativeThrow[F]): Hash[F] =
new UnsealedHash[F] {
def digest(algorithm: HashAlgorithm, data: ByteVector): F[ByteVector] =
Zone { implicit z =>
import HashAlgorithm._

val name = algorithm match {
case MD5 => c"MD5"
case SHA1 => c"SHA1"
case SHA256 => c"SHA256"
case SHA512 => c"SHA512"
}

val `type` = openssl.evp.EVP_get_digestbyname(name)
if (`type` == null)
F.raiseError(new GeneralSecurityException("EVP_get_digestbyname"))
else {
val md = stackalloc[CUnsignedChar](openssl.evp.EVP_MAX_MD_SIZE)
val size = stackalloc[CUnsignedInt]()

if (openssl
.evp
.EVP_Digest(data.toPtr, data.size.toULong, md, size, `type`, null) == 1)
F.pure(ByteVector.fromPtr(md.asInstanceOf[Ptr[Byte]], (!size).toLong))
else
F.raiseError(new GeneralSecurityException("EVP_DIGEST"))
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2021 http4s.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.http4s.crypto

import cats.effect.kernel.Sync
import scodec.bits.ByteVector

import scala.scalanative.unsafe._

private[crypto] trait HmacKeyGenCompanionPlatform {
implicit def forSync[F[_]](implicit F: Sync[F]): HmacKeyGen[F] =
new UnsealedHmacKeyGen[F] {
def generateKey[A <: HmacAlgorithm](algorithm: A): F[SecretKey[A]] =
F.delay {
val len = algorithm.minimumKeyLength
val buf = stackalloc[Byte](len.toLong)
if (openssl.rand.RAND_bytes(buf, len) != 1)
throw new GeneralSecurityException("RAND_bytes")
SecretKeySpec(ByteVector.fromPtr(buf, len.toLong), algorithm)
}
}
}
69 changes: 69 additions & 0 deletions crypto/native/src/main/scala/org/http4s/crypto/HmacPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2021 http4s.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.http4s.crypto

import cats.ApplicativeThrow
import scodec.bits.ByteVector

import scala.scalanative.unsafe._
import scala.scalanative.unsigned._

private[crypto] trait HmacPlatform[F[_]]

private[crypto] trait HmacCompanionPlatform {
implicit def forApplicativeThrow[F[_]](implicit F: ApplicativeThrow[F]): Hmac[F] =
new UnsealedHmac[F] {

def digest(key: SecretKey[HmacAlgorithm], data: ByteVector): F[ByteVector] =
Zone { implicit z =>
import HmacAlgorithm._

val SecretKeySpec(keyBytes, algorithm) = key

val name = algorithm match {
case SHA1 => c"SHA1"
case SHA256 => c"SHA256"
case SHA512 => c"SHA512"
}

val evpMd = openssl.evp.EVP_get_digestbyname(name)
if (evpMd == null)
F.raiseError(new GeneralSecurityException("EVP_get_digestbyname"))
else {
val md = stackalloc[CUnsignedChar](openssl.evp.EVP_MAX_MD_SIZE)
val mdLen = stackalloc[CUnsignedInt]()

if (openssl
.hmac
.HMAC(
evpMd,
keyBytes.toPtr,
keyBytes.size.toInt,
data.toPtr.asInstanceOf[Ptr[CUnsignedChar]],
data.size.toULong,
md,
mdLen) != null)
F.pure(ByteVector.fromPtr(md.asInstanceOf[Ptr[Byte]], (!mdLen).toLong))
else
F.raiseError(new GeneralSecurityException("HMAC"))
}
}

def importKey[A <: HmacAlgorithm](key: ByteVector, algorithm: A): F[SecretKey[A]] =
F.pure(SecretKeySpec(key, algorithm))
}
}
23 changes: 23 additions & 0 deletions crypto/native/src/main/scala/org/http4s/crypto/KeyPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2021 http4s.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.http4s.crypto

private[crypto] trait KeyPlatform
private[crypto] trait PublicKeyPlatform
private[crypto] trait PrivateKeyPlatform
private[crypto] trait SecretKeyPlatform
private[crypto] trait SecretKeySpecPlatform[+A <: Algorithm]
Loading

0 comments on commit b1300b1

Please sign in to comment.