zinflate.cpp

00001 // zinflate.cpp - written and placed in the public domain by Wei Dai
00002 
00003 // This is a complete reimplementation of the DEFLATE decompression algorithm.
00004 // It should not be affected by any security vulnerabilities in the zlib 
00005 // compression library. In particular it is not affected by the double free bug
00006 // (http://www.kb.cert.org/vuls/id/368819).
00007 
00008 #include "pch.h"
00009 #include "zinflate.h"
00010 
00011 NAMESPACE_BEGIN(CryptoPP)
00012 
00013 struct CodeLessThan
00014 {
00015         inline bool operator()(CryptoPP::HuffmanDecoder::code_t lhs, const CryptoPP::HuffmanDecoder::CodeInfo &rhs)
00016                 {return lhs < rhs.code;}
00017         // needed for MSVC .NET 2005
00018         inline bool operator()(const CryptoPP::HuffmanDecoder::CodeInfo &lhs, const CryptoPP::HuffmanDecoder::CodeInfo &rhs)
00019                 {return lhs.code < rhs.code;}
00020 };
00021 
00022 inline bool LowFirstBitReader::FillBuffer(unsigned int length)
00023 {
00024         while (m_bitsBuffered < length)
00025         {
00026                 byte b;
00027                 if (!m_store.Get(b))
00028                         return false;
00029                 m_buffer |= (unsigned long)b << m_bitsBuffered;
00030                 m_bitsBuffered += 8;
00031         }
00032         assert(m_bitsBuffered <= sizeof(unsigned long)*8);
00033         return true;
00034 }
00035 
00036 inline unsigned long LowFirstBitReader::PeekBits(unsigned int length)
00037 {
00038         bool result = FillBuffer(length);
00039         assert(result);
00040         return m_buffer & (((unsigned long)1 << length) - 1);
00041 }
00042 
00043 inline void LowFirstBitReader::SkipBits(unsigned int length)
00044 {
00045         assert(m_bitsBuffered >= length);
00046         m_buffer >>= length;
00047         m_bitsBuffered -= length;
00048 }
00049 
00050 inline unsigned long LowFirstBitReader::GetBits(unsigned int length)
00051 {
00052         unsigned long result = PeekBits(length);
00053         SkipBits(length);
00054         return result;
00055 }
00056 
00057 inline HuffmanDecoder::code_t HuffmanDecoder::NormalizeCode(HuffmanDecoder::code_t code, unsigned int codeBits)
00058 {
00059         return code << (MAX_CODE_BITS - codeBits);
00060 }
00061 
00062 void HuffmanDecoder::Initialize(const unsigned int *codeBits, unsigned int nCodes)
00063 {
00064         // the Huffman codes are represented in 3 ways in this code:
00065         //
00066         // 1. most significant code bit (i.e. top of code tree) in the least significant bit position
00067         // 2. most significant code bit (i.e. top of code tree) in the most significant bit position
00068         // 3. most significant code bit (i.e. top of code tree) in n-th least significant bit position,
00069         //    where n is the maximum code length for this code tree
00070         //
00071         // (1) is the way the codes come in from the deflate stream
00072         // (2) is used to sort codes so they can be binary searched
00073         // (3) is used in this function to compute codes from code lengths
00074         //
00075         // a code in representation (2) is called "normalized" here
00076         // The BitReverse() function is used to convert between (1) and (2)
00077         // The NormalizeCode() function is used to convert from (3) to (2)
00078 
00079         if (nCodes == 0)
00080                 throw Err("null code");
00081 
00082         m_maxCodeBits = *std::max_element(codeBits, codeBits+nCodes);
00083 
00084         if (m_maxCodeBits > MAX_CODE_BITS)
00085                 throw Err("code length exceeds maximum");
00086 
00087         if (m_maxCodeBits == 0)
00088                 throw Err("null code");
00089 
00090         // count number of codes of each length
00091         SecBlockWithHint<unsigned int, 15+1> blCount(m_maxCodeBits+1);
00092         std::fill(blCount.begin(), blCount.end(), 0);
00093         unsigned int i;
00094         for (i=0; i<nCodes; i++)
00095                 blCount[codeBits[i]]++;
00096 
00097         // compute the starting code of each length
00098         code_t code = 0;
00099         SecBlockWithHint<code_t, 15+1> nextCode(m_maxCodeBits+1);
00100         nextCode[1] = 0;
00101         for (i=2; i<=m_maxCodeBits; i++)
00102         {
00103                 // compute this while checking for overflow: code = (code + blCount[i-1]) << 1
00104                 if (code > code + blCount[i-1])
00105                         throw Err("codes oversubscribed");
00106                 code += blCount[i-1];
00107                 if (code > (code << 1))
00108                         throw Err("codes oversubscribed");
00109                 code <<= 1;
00110                 nextCode[i] = code;
00111         }
00112 
00113         if (code > (1 << m_maxCodeBits) - blCount[m_maxCodeBits])
00114                 throw Err("codes oversubscribed");
00115         else if (m_maxCodeBits != 1 && code < (1 << m_maxCodeBits) - blCount[m_maxCodeBits])
00116                 throw Err("codes incomplete");
00117 
00118         // compute a vector of <code, length, value> triples sorted by code
00119         m_codeToValue.resize(nCodes - blCount[0]);
00120         unsigned int j=0;
00121         for (i=0; i<nCodes; i++) 
00122         {
00123                 unsigned int len = codeBits[i];
00124                 if (len != 0)
00125                 {
00126                         code = NormalizeCode(nextCode[len]++, len);
00127                         m_codeToValue[j].code = code;
00128                         m_codeToValue[j].len = len;
00129                         m_codeToValue[j].value = i;
00130                         j++;
00131                 }
00132         }
00133         std::sort(m_codeToValue.begin(), m_codeToValue.end());
00134 
00135         // initialize the decoding cache
00136         m_cacheBits = STDMIN(9U, m_maxCodeBits);
00137         m_cacheMask = (1 << m_cacheBits) - 1;
00138         m_normalizedCacheMask = NormalizeCode(m_cacheMask, m_cacheBits);
00139         assert(m_normalizedCacheMask == BitReverse(m_cacheMask));
00140 
00141         if (m_cache.size() != size_t(1) << m_cacheBits)
00142                 m_cache.resize(1 << m_cacheBits);
00143 
00144         for (i=0; i<m_cache.size(); i++)
00145                 m_cache[i].type = 0;
00146 }
00147 
00148 void HuffmanDecoder::FillCacheEntry(LookupEntry &entry, code_t normalizedCode) const
00149 {
00150         normalizedCode &= m_normalizedCacheMask;
00151         const CodeInfo &codeInfo = *(std::upper_bound(m_codeToValue.begin(), m_codeToValue.end(), normalizedCode, CodeLessThan())-1);
00152         if (codeInfo.len <= m_cacheBits)
00153         {
00154                 entry.type = 1;
00155                 entry.value = codeInfo.value;
00156                 entry.len = codeInfo.len;
00157         }
00158         else
00159         {
00160                 entry.begin = &codeInfo;
00161                 const CodeInfo *last = & *(std::upper_bound(m_codeToValue.begin(), m_codeToValue.end(), normalizedCode + ~m_normalizedCacheMask, CodeLessThan())-1);
00162                 if (codeInfo.len == last->len)
00163                 {
00164                         entry.type = 2;
00165                         entry.len = codeInfo.len;
00166                 }
00167                 else
00168                 {
00169                         entry.type = 3;
00170                         entry.end = last+1;
00171                 }
00172         }
00173 }
00174 
00175 inline unsigned int HuffmanDecoder::Decode(code_t code, /* out */ value_t &value) const
00176 {
00177         assert(m_codeToValue.size() > 0);
00178         LookupEntry &entry = m_cache[code & m_cacheMask];
00179 
00180         code_t normalizedCode;
00181         if (entry.type != 1)
00182                 normalizedCode = BitReverse(code);
00183 
00184         if (entry.type == 0)
00185                 FillCacheEntry(entry, normalizedCode);
00186 
00187         if (entry.type == 1)
00188         {
00189                 value = entry.value;
00190                 return entry.len;
00191         }
00192         else
00193         {
00194                 const CodeInfo &codeInfo = (entry.type == 2)
00195                         ? entry.begin[(normalizedCode << m_cacheBits) >> (MAX_CODE_BITS - (entry.len - m_cacheBits))]
00196                         : *(std::upper_bound(entry.begin, entry.end, normalizedCode, CodeLessThan())-1);
00197                 value = codeInfo.value;
00198                 return codeInfo.len;
00199         }
00200 }
00201 
00202 bool HuffmanDecoder::Decode(LowFirstBitReader &reader, value_t &value) const
00203 {
00204         reader.FillBuffer(m_maxCodeBits);
00205         unsigned int codeBits = Decode(reader.PeekBuffer(), value);
00206         if (codeBits > reader.BitsBuffered())
00207                 return false;
00208         reader.SkipBits(codeBits);
00209         return true;
00210 }
00211 
00212 // *************************************************************
00213 
00214 Inflator::Inflator(BufferedTransformation *attachment, bool repeat, int propagation)
00215         : AutoSignaling<Filter>(propagation)
00216         , m_state(PRE_STREAM), m_repeat(repeat), m_reader(m_inQueue)
00217 {
00218         Detach(attachment);
00219 }
00220 
00221 void Inflator::IsolatedInitialize(const NameValuePairs &parameters)
00222 {
00223         m_state = PRE_STREAM;
00224         parameters.GetValue("Repeat", m_repeat);
00225         m_inQueue.Clear();
00226         m_reader.SkipBits(m_reader.BitsBuffered());
00227 }
00228 
00229 void Inflator::OutputByte(byte b)
00230 {
00231         m_window[m_current++] = b;
00232         if (m_current == m_window.size())
00233         {
00234                 ProcessDecompressedData(m_window + m_lastFlush, m_window.size() - m_lastFlush);
00235                 m_lastFlush = 0;
00236                 m_current = 0;
00237                 m_wrappedAround = true;
00238         }
00239 }
00240 
00241 void Inflator::OutputString(const byte *string, size_t length)
00242 {
00243         while (length)
00244         {
00245                 size_t len = UnsignedMin(length, m_window.size() - m_current);
00246                 memcpy(m_window + m_current, string, len);
00247                 m_current += len;
00248                 if (m_current == m_window.size())
00249                 {
00250                         ProcessDecompressedData(m_window + m_lastFlush, m_window.size() - m_lastFlush);
00251                         m_lastFlush = 0;
00252                         m_current = 0;
00253                         m_wrappedAround = true;
00254                 }
00255                 string += len;
00256                 length -= len;
00257         }               
00258 }
00259 
00260 void Inflator::OutputPast(unsigned int length, unsigned int distance)
00261 {
00262         size_t start;
00263         if (distance <= m_current)
00264                 start = m_current - distance;
00265         else if (m_wrappedAround && distance <= m_window.size())
00266                 start = m_current + m_window.size() - distance;
00267         else
00268                 throw BadBlockErr();
00269 
00270         if (start + length > m_window.size())
00271         {
00272                 for (; start < m_window.size(); start++, length--)
00273                         OutputByte(m_window[start]);
00274                 start = 0;
00275         }
00276 
00277         if (start + length > m_current || m_current + length >= m_window.size())
00278         {
00279                 while (length--)
00280                         OutputByte(m_window[start++]);
00281         }
00282         else
00283         {
00284                 memcpy(m_window + m_current, m_window + start, length);
00285                 m_current += length;
00286         }
00287 }
00288 
00289 size_t Inflator::Put2(const byte *inString, size_t length, int messageEnd, bool blocking)
00290 {
00291         if (!blocking)
00292                 throw BlockingInputOnly("Inflator");
00293 
00294         LazyPutter lp(m_inQueue, inString, length);
00295         ProcessInput(messageEnd != 0);
00296 
00297         if (messageEnd)
00298                 if (!(m_state == PRE_STREAM || m_state == AFTER_END))
00299                         throw UnexpectedEndErr();
00300 
00301         Output(0, NULL, 0, messageEnd, blocking);
00302         return 0;
00303 }
00304 
00305 bool Inflator::IsolatedFlush(bool hardFlush, bool blocking)
00306 {
00307         if (!blocking)
00308                 throw BlockingInputOnly("Inflator");
00309 
00310         if (hardFlush)
00311                 ProcessInput(true);
00312         FlushOutput();
00313 
00314         return false;
00315 }
00316 
00317 void Inflator::ProcessInput(bool flush)
00318 {
00319         while (true)
00320         {
00321                 switch (m_state)
00322                 {
00323                 case PRE_STREAM:
00324                         if (!flush && m_inQueue.CurrentSize() < MaxPrestreamHeaderSize())
00325                                 return;
00326                         ProcessPrestreamHeader();
00327                         m_state = WAIT_HEADER;
00328                         m_wrappedAround = false;
00329                         m_current = 0;
00330                         m_lastFlush = 0;
00331                         m_window.New(1 << GetLog2WindowSize());
00332                         break;
00333                 case WAIT_HEADER:
00334                         {
00335                         // maximum number of bytes before actual compressed data starts
00336                         const size_t MAX_HEADER_SIZE = BitsToBytes(3+5+5+4+19*7+286*15+19*15);
00337                         if (m_inQueue.CurrentSize() < (flush ? 1 : MAX_HEADER_SIZE))
00338                                 return;
00339                         DecodeHeader();
00340                         break;
00341                         }
00342                 case DECODING_BODY:
00343                         if (!DecodeBody())
00344                                 return;
00345                         break;
00346                 case POST_STREAM:
00347                         if (!flush && m_inQueue.CurrentSize() < MaxPoststreamTailSize())
00348                                 return;
00349                         ProcessPoststreamTail();
00350                         m_state = m_repeat ? PRE_STREAM : AFTER_END;
00351                         Output(0, NULL, 0, GetAutoSignalPropagation(), true);   // TODO: non-blocking
00352                         if (m_inQueue.IsEmpty())
00353                                 return;
00354                         break;
00355                 case AFTER_END:
00356                         m_inQueue.TransferTo(*AttachedTransformation());
00357                         return;
00358                 }
00359         }
00360 }
00361 
00362 void Inflator::DecodeHeader()
00363 {
00364         if (!m_reader.FillBuffer(3))
00365                 throw UnexpectedEndErr();
00366         m_eof = m_reader.GetBits(1) != 0;
00367         m_blockType = (byte)m_reader.GetBits(2);
00368         switch (m_blockType)
00369         {
00370         case 0: // stored
00371                 {
00372                 m_reader.SkipBits(m_reader.BitsBuffered() % 8);
00373                 if (!m_reader.FillBuffer(32))
00374                         throw UnexpectedEndErr();
00375                 m_storedLen = (word16)m_reader.GetBits(16);
00376                 word16 nlen = (word16)m_reader.GetBits(16);
00377                 if (nlen != (word16)~m_storedLen)
00378                         throw BadBlockErr();
00379                 break;
00380                 }
00381         case 1: // fixed codes
00382                 m_nextDecode = LITERAL;
00383                 break;
00384         case 2: // dynamic codes
00385                 {
00386                 if (!m_reader.FillBuffer(5+5+4))
00387                         throw UnexpectedEndErr();
00388                 unsigned int hlit = m_reader.GetBits(5);
00389                 unsigned int hdist = m_reader.GetBits(5);
00390                 unsigned int hclen = m_reader.GetBits(4);
00391 
00392                 FixedSizeSecBlock<unsigned int, 286+32> codeLengths;
00393                 unsigned int i;
00394                 static const unsigned int border[] = {    // Order of the bit length code lengths
00395                         16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
00396                 std::fill(codeLengths.begin(), codeLengths+19, 0);
00397                 for (i=0; i<hclen+4; i++)
00398                         codeLengths[border[i]] = m_reader.GetBits(3);
00399 
00400                 try
00401                 {
00402                         HuffmanDecoder codeLengthDecoder(codeLengths, 19);
00403                         for (i = 0; i < hlit+257+hdist+1; )
00404                         {
00405                                 unsigned int k, count, repeater;
00406                                 bool result = codeLengthDecoder.Decode(m_reader, k);
00407                                 if (!result)
00408                                         throw UnexpectedEndErr();
00409                                 if (k <= 15)
00410                                 {
00411                                         count = 1;
00412                                         repeater = k;
00413                                 }
00414                                 else switch (k)
00415                                 {
00416                                 case 16:
00417                                         if (!m_reader.FillBuffer(2))
00418                                                 throw UnexpectedEndErr();
00419                                         count = 3 + m_reader.GetBits(2);
00420                                         if (i == 0)
00421                                                 throw BadBlockErr();
00422                                         repeater = codeLengths[i-1];
00423                                         break;
00424                                 case 17:
00425                                         if (!m_reader.FillBuffer(3))
00426                                                 throw UnexpectedEndErr();
00427                                         count = 3 + m_reader.GetBits(3);
00428                                         repeater = 0;
00429                                         break;
00430                                 case 18:
00431                                         if (!m_reader.FillBuffer(7))
00432                                                 throw UnexpectedEndErr();
00433                                         count = 11 + m_reader.GetBits(7);
00434                                         repeater = 0;
00435                                         break;
00436                                 }
00437                                 if (i + count > hlit+257+hdist+1)
00438                                         throw BadBlockErr();
00439                                 std::fill(codeLengths + i, codeLengths + i + count, repeater);
00440                                 i += count;
00441                         }
00442                         m_dynamicLiteralDecoder.Initialize(codeLengths, hlit+257);
00443                         if (hdist == 0 && codeLengths[hlit+257] == 0)
00444                         {
00445                                 if (hlit != 0)  // a single zero distance code length means all literals
00446                                         throw BadBlockErr();
00447                         }
00448                         else
00449                                 m_dynamicDistanceDecoder.Initialize(codeLengths+hlit+257, hdist+1);
00450                         m_nextDecode = LITERAL;
00451                 }
00452                 catch (HuffmanDecoder::Err &)
00453                 {
00454                         throw BadBlockErr();
00455                 }
00456                 break;
00457                 }
00458         default:
00459                 throw BadBlockErr();    // reserved block type
00460         }
00461         m_state = DECODING_BODY;
00462 }
00463 
00464 bool Inflator::DecodeBody()
00465 {
00466         bool blockEnd = false;
00467         switch (m_blockType)
00468         {
00469         case 0: // stored
00470                 assert(m_reader.BitsBuffered() == 0);
00471                 while (!m_inQueue.IsEmpty() && !blockEnd)
00472                 {
00473                         size_t size;
00474                         const byte *block = m_inQueue.Spy(size);
00475                         size = UnsignedMin(m_storedLen, size);
00476                         OutputString(block, size);
00477                         m_inQueue.Skip(size);
00478                         m_storedLen -= (word16)size;
00479                         if (m_storedLen == 0)
00480                                 blockEnd = true;
00481                 }
00482                 break;
00483         case 1: // fixed codes
00484         case 2: // dynamic codes
00485                 static const unsigned int lengthStarts[] = {
00486                         3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
00487                         35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
00488                 static const unsigned int lengthExtraBits[] = {
00489                         0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
00490                         3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
00491                 static const unsigned int distanceStarts[] = {
00492                         1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
00493                         257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
00494                         8193, 12289, 16385, 24577};
00495                 static const unsigned int distanceExtraBits[] = {
00496                         0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
00497                         7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
00498                         12, 12, 13, 13};
00499 
00500                 const HuffmanDecoder& literalDecoder = GetLiteralDecoder();
00501                 const HuffmanDecoder& distanceDecoder = GetDistanceDecoder();
00502 
00503                 switch (m_nextDecode)
00504                 {
00505                 case LITERAL:
00506                         while (true)
00507                         {
00508                                 if (!literalDecoder.Decode(m_reader, m_literal))
00509                                 {
00510                                         m_nextDecode = LITERAL;
00511                                         break;
00512                                 }
00513                                 if (m_literal < 256)
00514                                         OutputByte((byte)m_literal);
00515                                 else if (m_literal == 256)      // end of block
00516                                 {
00517                                         blockEnd = true;
00518                                         break;
00519                                 }
00520                                 else
00521                                 {
00522                                         if (m_literal > 285)
00523                                                 throw BadBlockErr();
00524                                         unsigned int bits;
00525                 case LENGTH_BITS:
00526                                         bits = lengthExtraBits[m_literal-257];
00527                                         if (!m_reader.FillBuffer(bits))
00528                                         {
00529                                                 m_nextDecode = LENGTH_BITS;
00530                                                 break;
00531                                         }
00532                                         m_literal = m_reader.GetBits(bits) + lengthStarts[m_literal-257];
00533                 case DISTANCE:
00534                                         if (!distanceDecoder.Decode(m_reader, m_distance))
00535                                         {
00536                                                 m_nextDecode = DISTANCE;
00537                                                 break;
00538                                         }
00539                 case DISTANCE_BITS:
00540                                         bits = distanceExtraBits[m_distance];
00541                                         if (!m_reader.FillBuffer(bits))
00542                                         {
00543                                                 m_nextDecode = DISTANCE_BITS;
00544                                                 break;
00545                                         }
00546                                         m_distance = m_reader.GetBits(bits) + distanceStarts[m_distance];
00547                                         OutputPast(m_literal, m_distance);
00548                                 }
00549                         }
00550                 }
00551         }
00552         if (blockEnd)
00553         {
00554                 if (m_eof)
00555                 {
00556                         FlushOutput();
00557                         m_reader.SkipBits(m_reader.BitsBuffered()%8);
00558                         if (m_reader.BitsBuffered())
00559                         {
00560                                 // undo too much lookahead
00561                                 SecBlockWithHint<byte, 4> buffer(m_reader.BitsBuffered() / 8);
00562                                 for (unsigned int i=0; i<buffer.size(); i++)
00563                                         buffer[i] = (byte)m_reader.GetBits(8);
00564                                 m_inQueue.Unget(buffer, buffer.size());
00565                         }
00566                         m_state = POST_STREAM;
00567                 }
00568                 else
00569                         m_state = WAIT_HEADER;
00570         }
00571         return blockEnd;
00572 }
00573 
00574 void Inflator::FlushOutput()
00575 {
00576         if (m_state != PRE_STREAM)
00577         {
00578                 assert(m_current >= m_lastFlush);
00579                 ProcessDecompressedData(m_window + m_lastFlush, m_current - m_lastFlush);
00580                 m_lastFlush = m_current;
00581         }
00582 }
00583 
00584 struct NewFixedLiteralDecoder
00585 {
00586         HuffmanDecoder * operator()() const
00587         {
00588                 unsigned int codeLengths[288];
00589                 std::fill(codeLengths + 0, codeLengths + 144, 8);
00590                 std::fill(codeLengths + 144, codeLengths + 256, 9);
00591                 std::fill(codeLengths + 256, codeLengths + 280, 7);
00592                 std::fill(codeLengths + 280, codeLengths + 288, 8);
00593                 std::auto_ptr<HuffmanDecoder> pDecoder(new HuffmanDecoder);
00594                 pDecoder->Initialize(codeLengths, 288);
00595                 return pDecoder.release();
00596         }
00597 };
00598 
00599 struct NewFixedDistanceDecoder
00600 {
00601         HuffmanDecoder * operator()() const
00602         {
00603                 unsigned int codeLengths[32];
00604                 std::fill(codeLengths + 0, codeLengths + 32, 5);
00605                 std::auto_ptr<HuffmanDecoder> pDecoder(new HuffmanDecoder);
00606                 pDecoder->Initialize(codeLengths, 32);
00607                 return pDecoder.release();
00608         }
00609 };
00610 
00611 const HuffmanDecoder& Inflator::GetLiteralDecoder() const
00612 {
00613         return m_blockType == 1 ? Singleton<HuffmanDecoder, NewFixedLiteralDecoder>().Ref() : m_dynamicLiteralDecoder;
00614 }
00615 
00616 const HuffmanDecoder& Inflator::GetDistanceDecoder() const
00617 {
00618         return m_blockType == 1 ? Singleton<HuffmanDecoder, NewFixedDistanceDecoder>().Ref() : m_dynamicDistanceDecoder;
00619 }
00620 
00621 NAMESPACE_END

Generated on Sat Dec 23 02:07:11 2006 for Crypto++ by  doxygen 1.5.1-p1