text: #include "CypherBase.h"
  
  NAMESPACE_UPP
  
  ////////////////////////////////////////////////////////////////////////////////////////////
  //                                      CypherFifo class                                  //
  ////////////////////////////////////////////////////////////////////////////////////////////
  
  // inserts data
  void CypherFifo::Put(byte const *buf, int len)
  {
  	int l = data.GetCount();
  	data.AddN(len);
  	byte *b = &data.At(l);
  	memcpy(b, buf, len);
  }
  
  void CypherFifo::Put(String const &s)
  {
  	Put((byte const *)~s, s.GetCount());
  }
  
  void CypherFifo::Put(byte b)
  {
  	data.Add(b);
  }
  
  // extract data
  bool CypherFifo::Get(byte *buf, int len)
  {
  	if(data.GetCount() < len)
  		return false;
  	memcpy(buf, &data.At(0), len);
  	data.Remove(0, len);
  	return true;
  }
  
  bool CypherFifo::Get(String &s)
  {
  	if(!data.GetCount())
  		return false;
  	StringBuffer sb;
  	sb.Cat((const char *)&data.At(0), data.GetCount());
  	data.Clear();
  	s = sb;
  	return true;
  }
  
  bool CypherFifo::Get(byte &b)
  {
  	if(!data.GetCount())
  		return false;
  	b = data.At(0);
  	data.Remove(0);
  	return true;
  }
  
  
  ////////////////////////////////////////////////////////////////////////////////////////
  //                                      Cypher class                                  //
  ////////////////////////////////////////////////////////////////////////////////////////
  
  Cypher::Cypher(size_t _blockSize)
  {
  	// still no key present
  	keyOk = false;
  	
  	// Cypher in IDLE mode at startup
  	mode = IDLE;
  	
  	// clears FIFO
  	FIFO.Clear();
  	
  	// sets the encryption block size
  	blockSize = _blockSize;
  	
  	// resets total stream size
  	// should be set BEFORE streaming (decoding)
  	// if we want the resulting stream truncated on right length
  	// otherwise we get also the pad bytes
  	streamSize = 0;
  
  }
  
  void Cypher::Reset()
  {
  	keyOk = false;
  	mode = IDLE;
  	FIFO.Clear();
  	streamSize = 0;
  }
  
  // set mode (either STREAM or BLOCK
  // throws an exception if another mode was already active
  void Cypher::SetMode(Mode m)
  {
  	// checks if another mode already in progress, if so throws an exception
  	if(mode != m && mode != IDLE)
  		throw "A different encoding mode was already in progress";
  	
  	// if starting stream mode, allocate block buffer if
  	// working with a Block-Cypher
  	if(m == STREAM_MODE && mode == IDLE && blockSize > 1)
  	{
  		// allocates block buffer and set pointer to its start
  		blockBuffer.Alloc(blockSize);
  		blockBytes = 0;
  		
  		// resets streamedIn and streamedOut values
  		streamedIn = streamedOut = 0;
  	}
  	mode = m;
  }
  
  // checks if key was set, otherwise throws an exception
  void Cypher::CheckKey(void)
  {
  	if(!keyOk)
  		throw "Missing key";
  }
  		
  // checks buffer size if it must be multiple of block size
  // for Block-Cyphers. Throws an exception if not
  void Cypher::CheckBufSize(size_t bufSize)
  {
  	if(blockSize == 1)
  		return;
  	if(bufSize % blockSize)
  		throw "Buffer size mismatch - not a multiple of block size";
  }
  
  // rounds buffer size to nearest bigger multiple of block size
  size_t Cypher::RoundBufSize(size_t bufSize)
  {
  	size_t rem = bufSize % blockSize;
  	if(rem)
  		bufSize += blockSize - rem;
  	return bufSize;
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // key setting
  void Cypher::SetKey(byte const *keyBuf, size_t keyLen, byte const *_nonce, size_t nonceLen)
  {
  	nonce.Clear();
  	if(!_nonce || !nonceLen)
  	{
  		for(size_t i = 0; i < 8; i++)
  			nonce.Cat(Random() & 0xff);
  	}
  	else
  	{
  		for(size_t i = 0; i < nonceLen; i++)
  			nonce.Cat(_nonce[i]);
  	}
  	if(!CypherKey(keyBuf, keyLen, (const byte *)~nonce, nonce.GetCount()))
  		throw "Invalid key";
  	keyOk = true;
  }
  
  void Cypher::SetKey(String const &key, String const &nonce)
  {
  	if(IsNull(nonce))
  		SetKey((byte const *)~key, key.GetCount(), NULL, 0);
  	else
  		SetKey((byte const *)~key, key.GetCount(), (byte const *)~nonce, nonce.GetCount());
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // encrypt/decrypt functions
  String Cypher::operator()(String const &s)
  {
  	// checks if KEY is ok, exception if not
  	CheckKey();
  
  	// sets buffer mode, exception if already in stream mode
  	SetMode(BUFFER_MODE);
  	
  	// checks buffer size, exception if not multiple of block size
  	CheckBufSize(s.GetCount());
  	
  	StringBuffer sb(s.GetCount());
  	CypherCypher((const byte *)~s, (byte *)~sb, s.GetCount());
  	return sb;
  }
  
  void Cypher::operator()(byte const *sourceBuf,  byte *destBuf, size_t bufLen)
  {
  	// checks if KEY is ok, exception if not
  	CheckKey();
  
  	// sets buffer mode, exception if already in stream mode
  	SetMode(BUFFER_MODE);
  	
  	// checks buffer size, exception if not multiple of block size
  	CheckBufSize(bufLen);
  	
  	CypherCypher(sourceBuf, destBuf, bufLen);
  }
  
  void Cypher::operator()(byte *buf, size_t bufLen)
  {
  	// checks if KEY is ok, exception if not
  	CheckKey();
  
  	// sets buffer mode, exception if already in stream mode
  	SetMode(BUFFER_MODE);
  	
  	// checks buffer size, exception if not multiple of block size
  	CheckBufSize(bufLen);
  	
  	CypherCypher(buf, buf, bufLen);
  }
  		
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // streaming support
  String Cypher::StreamOut(void)
  {
  	// checks if KEY is ok, exception if not
  	CheckKey();
  
  	// sets stream mode, exception if already in buffer mode
  	SetMode(STREAM_MODE);
  	
  	String res;
  	FIFO.Get(res);
  
  	// if blockSize == 1 OR user didn't set stream size, simple behaviour
  	if(blockSize != 1 && streamSize != 0)
  	{
  		if(streamedOut >= streamSize)
  			return "";
  		if(streamedOut + res.GetCount() > streamSize)
  			res = res.Left(streamSize - streamedOut);
  	}
  
  	// returns encoded buffer from FIFO
  	streamedOut += res.GetCount();
  	return res;
  }
  
  void Cypher::StreamIn(String const &s)
  {
  	// checks if KEY is ok, exception if not
  	CheckKey();
  
  	// sets stream mode, exception if already in buffer mode
  	SetMode(STREAM_MODE);
  	
  	// simpler streaming if blockSize == 1
  	if(blockSize == 1)
  	{
  		// en/decode string and put into FIFO
  		StringBuffer sb(s.GetCount());
  		CypherCypher((const byte *)~s, (byte *)~sb, s.GetCount());
  		FIFO.Put(sb);
  	}
  	else
  	{
  		size_t len = s.GetCount();
  		size_t idx = 0;
  		
  		// if block buffer is partially filled, try to finish it before
  		if(blockBytes)
  		{
  			size_t count = min(len, blockSize - blockBytes);
  			idx += count;
  			len -= count;
  			memcpy(blockBuffer + blockBytes, ~s, count);
  			blockBytes += count;
  			if(blockBytes >= blockSize)
  			{
  				CypherCypher(blockBuffer, blockBuffer, blockSize);
  				FIFO.Put(blockBuffer, blockSize);
  				blockBytes = 0;
  			}
  		}
  		
  		// stream in full block buffers
  		if(len)
  		{
  			// get number of bytes composing FULL blocks
  			size_t count = len - (len % blockSize);
  			
  			if(count)
  			{
  				Buffer<byte> buf(count);
  				CypherCypher((const byte *)~s + idx, buf, count);
  				FIFO.Put(buf, count);
  				len -= count;
  				idx += count;
  			}
  		}
  		
  		// fills block buffer with remaining data, if any
  		if(len)
  		{
  			memcpy(blockBuffer, ~s + idx, len);
  			blockBytes = len;
  		}
  	}
  	
  	// updates number of streamed-in bytes
  	streamedIn += s.GetCount();
  }
  
  size_t Cypher::Flush(void)
  {
  	// checks if KEY is ok, exception if not
  	CheckKey();
  
  	// sets stream mode, exception if already in buffer mode
  	SetMode(STREAM_MODE);
  	
  	// nothing to do on Stream-Cyphers, just return stream total size
  	if(blockSize == 1)
  		return streamedIn;
  	
  	// on block stream, we should pad partial data (if any)
  	// and encrypt them; we pad them with plain random data
  	if(blockBytes)
  	{
  		for(size_t i = blockBytes; i < blockSize; i++)
  			blockBuffer[i] = (byte)(Random() & 0xff);
  
  		CypherCypher(blockBuffer, blockBuffer, blockSize);
  		FIFO.Put(blockBuffer, blockSize);
  		blockBytes = 0;
  	}
  	
  	// return the true streamed-in byte count, not the padded one
  	return streamedIn; 
  	
  };
  
  
  END_UPP_NAMESPACE
  