/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) 2020-2022 Lightbend Inc. <https://www.lightbend.com>
 */

package org.apache.pekko.remote.artery.tcp

import java.io.ByteArrayOutputStream
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.util.zip.GZIPOutputStream

import org.apache.pekko
import pekko.event.NoMarkerLogging
import pekko.testkit.PekkoSpec

class SecureRandomFactorySHA1Spec extends SecureRandomFactorySpec("SHA1PRNG")
class SecureRandomFactoryNativePRNGSpec extends SecureRandomFactorySpec("NativePRNG")
class SecureRandomFactoryJVMChoiceSpec extends SecureRandomFactorySpec("SecureRandom")
class SecureRandomFactoryBlankSpec extends SecureRandomFactorySpec("")
class SecureRandomFactoryInvalidPRNGSpec extends SecureRandomFactorySpec("InvalidPRNG")

abstract class SecureRandomFactorySpec(alg: String) extends PekkoSpec {
  var prng: SecureRandom = null

  def isSupported: Boolean = {
    try {
      prng = SecureRandomFactory.createSecureRandom(alg, NoMarkerLogging)
      prng.nextInt() // Has to work
      val sRng = alg
      if (prng.getAlgorithm != sRng && sRng != "")
        throw new NoSuchAlgorithmException(sRng)
      true
    } catch {
      case e: NoSuchAlgorithmException =>
        info(e.toString)
        false
    }
  }

  s"Artery's Secure Random support ($alg)" must {
    if (isSupported) {
      "generate random" in {
        val bytes = Array.ofDim[Byte](16)
        // Reproducer of the specific issue described at
        // https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html
        // awaitAssert just in case we are very unlucky to get same sequence more than once
        awaitAssert {
          val randomBytes = List
            .fill(10) {
              prng.nextBytes(bytes)
              bytes.toVector
            }
            .toSet
          randomBytes.size should ===(10)
        }
      }

      "have random numbers that are not compressable, because then they are not random" in {
        val randomData = new Array[Byte](1024 * 1024)
        prng.nextBytes(randomData)

        val baos = new ByteArrayOutputStream()
        val gzipped = new GZIPOutputStream(baos)
        try gzipped.write(randomData)
        finally gzipped.close()

        val compressed = baos.toByteArray
        // random data should not be compressible
        // Another reproducer of https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html
        // with the broken implementation the compressed size was <5k
        compressed.size should be > randomData.length
      }

    } else {
      s"not be run when the $alg PRNG provider is not supported by the platform this test is currently being executed on" in {
        pending
      }
    }
  }
}
