/** Wrapper around nonVolatileWrite() which also encrypts data
  * using xexEncrypt(). Because this uses encryption, it is much slower
  * than nonVolatileWrite(). The parameters and return values are identical
  * to that of nonVolatileWrite().
  * \param data A pointer to the data to be written.
  * \param partition The partition to write to. Must be one of #NVPartitions.
  * \param address Byte offset specifying where in the partition to
  *                start writing to.
  * \param length The number of bytes to write.
  * \return See #NonVolatileReturnEnum for return values.
  * \warning Writes may be buffered; use nonVolatileFlush() to be sure that
  *          data is actually written to non-volatile storage.
  */
NonVolatileReturn encryptedNonVolatileWrite(uint8_t *data, NVPartitions partition, uint32_t address, uint32_t length)
{
	uint32_t block_start;
	uint32_t block_end;
	uint8_t block_offset;
	uint8_t ciphertext[16];
	uint8_t plaintext[16];
	uint8_t n[16];
	NonVolatileReturn r;

	block_start = address & 0xfffffff0;
	block_offset = (uint8_t)(address & 0x0000000f);
	block_end = (address + length - 1) & 0xfffffff0;
	if ((address + length) < address)
	{
		// Overflow occurred.
		return NV_INVALID_ADDRESS;
	}

	memset(n, 0, 16);
	for (; block_start <= block_end; block_start += 16)
	{
		r = nonVolatileRead(ciphertext, partition, block_start, 16);
		if (r != NV_NO_ERROR)
		{
			return r;
		}
		writeU32LittleEndian(n, block_start);
		xexDecrypt(plaintext, ciphertext, n, 1);
		while (length && block_offset < 16)
		{
			plaintext[block_offset++] = *data++;
			length--;
		}
		block_offset = 0;
		xexEncrypt(ciphertext, plaintext, n, 1);
		r = nonVolatileWrite(ciphertext, partition, block_start, 16);
		if (r != NV_NO_ERROR)
		{
			return r;
		}
	}

	return NV_NO_ERROR;
}
/** Wrapper around nonVolatileWrite() which also encrypts data
  * using xexEncrypt(). Because this uses encryption, it is much slower
  * than nonVolatileWrite(). The parameters and return values are identical
  * to that of nonVolatileWrite().
  * \param data A pointer to the data to be written.
  * \param address Byte offset specifying where in non-volatile storage to
  *                start writing to.
  * \param length The number of bytes to write.
  * \return See #NonVolatileReturnEnum for return values.
  * \warning Writes may be buffered; use nonVolatileFlush() to be sure that
  *          data is actually written to non-volatile storage.
  */
NonVolatileReturn encryptedNonVolatileWrite(uint8_t *data, uint32_t address, uint8_t length)
{
	uint32_t block_start;
	uint32_t block_end;
	uint8_t block_offset;
	uint8_t ciphertext[16];
	uint8_t plaintext[16];
	uint8_t n[16];
	NonVolatileReturn r;

	block_start = address & 0xfffffff0;
	block_offset = (uint8_t)(address & 0x0000000f);
	block_end = (address + length - 1) & 0xfffffff0;

	memset(n, 0, 16);
	for (; block_start <= block_end; block_start += 16)
	{
		r = nonVolatileRead(ciphertext, block_start, 16);
		if (r != NV_NO_ERROR)
		{
			return r;
		}
		writeU32LittleEndian(n, block_start);
		xexDecrypt(plaintext, ciphertext, n, 1, nv_storage_tweak_key, nv_storage_encrypt_key);
		while (length && block_offset < 16)
		{
			plaintext[block_offset++] = *data++;
			length--;
		}
		block_offset = 0;
		xexEncrypt(ciphertext, plaintext, n, 1, nv_storage_tweak_key, nv_storage_encrypt_key);
		r = nonVolatileWrite(ciphertext, block_start, 16);
		if (r != NV_NO_ERROR)
		{
			return r;
		}
	}

	return NV_NO_ERROR;
}