# Copyright (c) (2020) Apple Inc. All rights reserved.
#
# corecrypto is licensed under Apple Inc.’s Internal Use License Agreement (which
# is contained in the License.txt file distributed with corecrypto) and only to
# people who accept that license. IMPORTANT:  Any license rights granted to you by
# Apple Inc. (if any) are limited to internal use within your organization only on
# devices and computers you own or control, for the sole purpose of verifying the
# security characteristics and correct functioning of the Apple Software.  You may
# not, directly or indirectly, redistribute the Apple Software or any portions thereof.

from hmac_drbg import HMAC_DRBG
import binascii
import hashlib

class RNG:
    ENTROPY_SIZE = 64
    def __init__(self, drbg, seed, nonce, ps):
        self.drbg = drbg # HMAC_DRBG(ihash, seed, nonce, ps, fips = True)

        self.cache_size = 256
        self.cache_pos = 256
        self.cache = bytes(self.cache_size)
        
        self.predictionbreak = False

    def reseed(self, seed, nonce = b""):
        self.drbg.reseed(seed, ai = nonce)
        self.cache_pos = 256

    def force_reseed_with_getentropy(self, nonce):
        entropy = self.getentropy(self.ENTROPY_SIZE)
        self.reseed(entropy, nonce = nonce)

    def generate(self, n):
        random = b""
        while n > 0:
            status = True
            if self.needreseed():
                status = self.getentropy_and_reseed()
            if not status:
                raise Exception("Uh oh...")

            if n <= self.cache_size:
                take_n = min(n, len(self.cache[self.cache_pos:]))
                random += self.cache[self.cache_pos:self.cache_pos + take_n]

                self.cache_pos += take_n
                n -= take_n

                if n > 0:
                    self.cache = self.drbg.generate(self.cache_size)
                    random += self.cache[:n]
                    self.cache_pos = n
                    n = 0
                    
            else:
                req_size = min(n, 4096)
                random += self.drbg.generate(req_size)
                n -= req_size
        return random

    # Internal Functions

    def getentropy_and_reseed(self):
        got_entropy = False
        for retry in range(0, 100):
            entropy = self.getentropy(self.ENTROPY_SIZE)
            if entropy is None:
                continue
            self.reseed(entropy)
            got_entropy = True
            break
        return got_entropy
    

    def needreseed(self, *args, **kwargs):
        if self.predictionbreak:
            return True
        return False

    # Plugable functions
    def getentropy(self, entropy_len):
        return b"\x01" * entropy_len

if __name__ == "__main__":
    seed = bytes(64)
    ps = bytes(32)
    nonce = bytes(8)
    drbg = HMAC_DRBG(hashlib.sha256, seed, nonce, ps, fips = True)
    rng = RNG(drbg, seed, nonce, ps)

    print(f"seed = {binascii.hexlify(seed)}")
    print(f"ps = {binascii.hexlify(ps)}")
    for x in range(0, 8):
        gen = rng.generate(64)
        print(f"gen{x} = {binascii.hexlify(gen)}")

    rng.force_reseed_with_getentropy(bytes(8))
    print("----------------------------------")

    for x in range(0, 8):
        gen = rng.generate(64)
        print(f"gen{x} = {binascii.hexlify(gen)}")





