<?php

namespace LowEntryUE4PHP\Classes\Internal;

use LowEntryUE4PHP\LowEntry;


class PaddingOaep
{
	/**
	 * @param int[] $input Byte array
	 *
	 * @return int[] Byte array
	 */
	private static function hash($input)
	{
		return LowEntry::hash('sha256', $input);
	}
	
	/**
	 * @return int
	 */
	private static function hashSize()
	{
		return 32;
	}
	
	
	/**
	 * @param int[]|null $bytes Byte array
	 *
	 * @return int
	 */
	private static function random(&$bytes = null)
	{
		if($bytes === null)
		{
			return LowEntry::castToByte(\mt_rand());
		}
		
		$length = \count($bytes);
		for($i = 0; $i < $length; $i++)
		{
			$bytes[$i] = LowEntry::castToByte(\mt_rand());
		}
		return 0;
	}
	
	/**
	 * @param int[] $bytes Byte array
	 */
	private static function randomSecure(&$bytes)
	{
		$length = \count($bytes);
		
		$newBytes = false;
		if(!$newBytes && \function_exists('random_bytes'))
		{
			try
			{
				/** @noinspection UnknownInspectionInspection,RedundantSuppression,PhpElementIsNotAvailableInCurrentPhpVersionInspection */
				$newBytes = \random_bytes($length);
			}
			catch(\Exception $e)
			{
				$newBytes = false;
			}
		}
		if(!$newBytes && \function_exists('openssl_random_pseudo_bytes'))
		{
			try
			{
				/** @noinspection PhpComposerExtensionStubsInspection, CryptographicallySecureRandomnessInspection */
				$newBytes = \openssl_random_pseudo_bytes($length);
			}
			catch(\Exception $e)
			{
				$newBytes = false;
			}
		}
		if($newBytes === false)
		{
			self::random($bytes);
			return;
		}
		$newBytes = LowEntry::stringToBytesUtf8($newBytes);
		
		for($i = 0; $i < $length; $i++)
		{
			$bytes[$i] = $newBytes[$i];
		}
	}
	
	
	/**
	 * @param int[] $seed Byte array
	 * @param int   $seedOffset
	 * @param int   $seedLength
	 * @param int   $desiredLength
	 *
	 * @return int[] Byte array
	 */
	private static function mfg1($seed, $seedOffset, $seedLength, $desiredLength)
	{
		$hLen = 32;
		$offset = 0;
		$i = 0;
		$mask = LowEntry::createArray($desiredLength, 0);
		//Arrays.fill(mask, LowEntry::castToByte0);
		$temp = LowEntry::createArray($seedLength + 4, 0);
		LowEntry::systemArrayCopy($seed, $seedOffset, $temp, 4, $seedLength);
		while($offset < $desiredLength)
		{
			$temp[0] = LowEntry::castToByte($i >> 24);
			$temp[1] = LowEntry::castToByte($i >> 16);
			$temp[2] = LowEntry::castToByte($i >> 8);
			$temp[3] = LowEntry::castToByte($i);
			$remaining = $desiredLength - $offset;
			LowEntry::systemArrayCopy(self::hash($temp), 0, $mask, $offset, (($remaining < $hLen) ? $remaining : $hLen));
			$offset = $offset + $hLen;
			$i = $i + 1;
		}
		return $mask;
	}
	
	
	/**
	 * @param int[] $message Byte array
	 * @param int   $length
	 *
	 * @return int[] Byte array
	 */
	public static function pad($message, $length)
	{
		$length -= 1;
		$mLen = \count($message);
		$hLen = self::hashSize();
		if($mLen > ($length - $hLen - 1))
		{
			$mLen = $length - $hLen - 1;
		}
		if($mLen <= 0)
		{
			return [];
		}
		$dataBlock = LowEntry::createArray($length - $hLen, 0);
		$dataBlockLength = \count($dataBlock);
		self::random($dataBlock);
		$padlength = ($length - $mLen - $hLen - 1);
		if($padlength <= 225)
		{
			$dataBlock[0] = LowEntry::castToByte($padlength);
		}
		elseif($padlength <= 65535)
		{
			$dataBlock[0] = LowEntry::castToByte((((self::random() & 0xff) / 256.0) * 15.0) + 226);
			$dataBlock[1] = LowEntry::castToByte($padlength >> 8);
			$dataBlock[2] = LowEntry::castToByte($padlength);
		}
		else
		{
			$dataBlock[0] = LowEntry::castToByte((((self::random() & 0xff) / 256.0) * 15.0) + 241);
			$dataBlock[1] = LowEntry::castToByte($padlength >> 24);
			$dataBlock[2] = LowEntry::castToByte($padlength >> 16);
			$dataBlock[3] = LowEntry::castToByte($padlength >> 8);
			$dataBlock[4] = LowEntry::castToByte($padlength);
		}
		LowEntry::systemArrayCopy($message, 0, $dataBlock, ($dataBlockLength - $mLen), $mLen);
		$seed = LowEntry::createArray($hLen, 0);
		self::randomSecure($seed);
		$dataBlockMask = self::mfg1($seed, 0, $hLen, $dataBlockLength);
		for($i = 0; $i < $dataBlockLength; $i++)
		{
			$dataBlock[$i] ^= $dataBlockMask[$i];
		}
		$seedMask = self::mfg1($dataBlock, 0, $dataBlockLength, $hLen);
		for($i = 0; $i < $hLen; $i++)
		{
			$seed[$i] ^= $seedMask[$i];
		}
		$padded = LowEntry::createArray($length + 1, 0);
		$paddedbyte = 0x00;
		while($paddedbyte == 0x00)
		{
			$paddedbyte = LowEntry::castToByte(self::random() & 0x3f);
		}
		$padded[0] = $paddedbyte;
		LowEntry::systemArrayCopy($seed, 0, $padded, 1, $hLen);
		LowEntry::systemArrayCopy($dataBlock, 0, $padded, 1 + $hLen, $dataBlockLength);
		return $padded;
	}
	
	/**
	 * @param int[] $message Byte array
	 *
	 * @return int[] Byte array
	 */
	public static function unpad($message)
	{
		$mLen = \count($message) - 1;
		$hLen = self::hashSize();
		if($mLen <= ($hLen + 1))
		{
			return [];
		}
		if(($message[0] == 0x00) || (($message[0] & ~0x3f) != 0x00))
		{
			return [];
		}
		$copy = LowEntry::createArray($mLen, 0);
		LowEntry::systemArrayCopy($message, 1, $copy, 0, $mLen);
		$seedMask = self::mfg1($copy, $hLen, $mLen - $hLen, $hLen);
		for($i = 0; $i < $hLen; $i++)
		{
			$copy[$i] ^= $seedMask[$i];
		}
		$dataBlockMask = self::mfg1($copy, 0, $hLen, $mLen - $hLen);
		for($i = $hLen; $i < $mLen; $i++)
		{
			$copy[$i] ^= $dataBlockMask[$i - $hLen];
		}
		$padlength = $copy[$hLen] & 0xff;
		if($padlength > 225)
		{
			if($padlength <= 240)
			{
				if(($hLen + 2) >= \count($copy))
				{
					return [];
				}
				$padlength = (($copy[$hLen + 1] & 0xFF) << 8) | ($copy[$hLen + 2] & 0xFF);
				if($padlength <= 225)
				{
					return [];
				}
			}
			else
			{
				if(($hLen + 4) >= \count($copy))
				{
					return [];
				}
				$padlength = (($copy[$hLen + 1] & 0xFF) << 24) | (($copy[$hLen + 2] & 0xFF) << 16) | (($copy[$hLen + 3] & 0xFF) << 8) | ($copy[$hLen + 4] & 0xFF);
				if($padlength <= 65535)
				{
					return [];
				}
			}
		}
		if($mLen <= ($hLen + 1 + $padlength))
		{
			return [];
		}
		$unpadded = LowEntry::createArray($mLen - $hLen - 1 - $padlength, 0);
		LowEntry::systemArrayCopy($copy, ($hLen + 1 + $padlength), $unpadded, 0, ($mLen - $hLen - 1 - $padlength));
		return $unpadded;
	}
}
