// =================================================================================================
// Copyright 2004 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
//
// Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of
// one format in a file with a different format', inventors: Sean Parent, Greg Gilley.
// =================================================================================================
#if WIN32
#pragma warning ( disable : 4127 ) // conditional expression is constant
#pragma warning ( disable : 4510 ) // default constructor could not be generated
#pragma warning ( disable : 4610 ) // user defined constructor required
#pragma warning ( disable : 4786 ) // debugger can't handle long symbol names
#endif
#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
#include "public/include/XMP_Const.h"
#if TestRunnerBuild
#define EnablePacketScanning 1
#else
#include "XMPFiles/source/XMPFiles_Impl.hpp"
#endif
#include "XMPFiles/source/FormatSupport/XMPScanner.hpp"
#include <cassert>
#include <string>
#include <cstdlib>
#if DEBUG
#include <iostream>
#include <iomanip>
#include <fstream>
#endif
#ifndef UseStringPushBack // VC++ 6.x does not provide push_back for strings!
#define UseStringPushBack 0
#endif
using namespace std;
#if EnablePacketScanning
// =================================================================================================
// =================================================================================================
// class PacketMachine
// ===================
//
// This is the packet recognizer state machine. The top of the machine is FindNextPacket, this
// calls the specific state components and handles transitions. The states are described by an
// array of RecognizerInfo records, indexed by the RecognizerKind enumeration. Each RecognizerInfo
// record has a function that does that state's work, the success and failure transition states,
// and a string literal that is passed to the state function. The literal lets a common MatchChar
// or MatchString function be used in several places.
//
// The state functions are responsible for consuming input to recognize their particular state.
// This includes intervening nulls for 16 and 32 bit character forms. For the simplicity, things
// are treated as essentially little endian and the nulls are not actually checked. The opening
// '<' is found with a byte-by-byte search, then the number of bytes per character is determined
// by counting the following nulls. From then on, consuming a character means incrementing the
// buffer pointer by the number of bytes per character. Thus the buffer pointer only points to
// the "real" bytes. This also means that the pointer can go off the end of the buffer by a
// variable amount. The amount of overrun is saved so that the pointer can be positioned at the
// right byte to start the next buffer.
//
// The state functions return a TriState value, eTriYes means the pattern was found, eTriNo means
// the pattern was definitely not found, eTriMaybe means that the end of the buffer was reached
// while working through the pattern.
//
// When eTriYes is returned, the fBufferPtr data member is left pointing to the "real" byte
// following the last actual byte. Which might not be addressable memory! This also means that
// a state function can be entered with nothing available in the buffer. When eTriNo is returned,
// the fBufferPtr data member is left pointing to the byte that caused the failure. The state
// machine starts over from the failure byte.
//
// The state functions must preserve their internal micro-state before returning eTriMaybe, and
// resume processing when called with the next buffer. The fPosition data member is used to denote
// how many actual characters have been consumed. The fNullCount data member is used to denote how
// many nulls are left before the next actual character.
// =================================================================================================
// PacketMachine
// =============
XMPScanner::PacketMachine::PacketMachine ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength ) :
// Public members
fPacketStart ( 0 ),
fPacketLength ( 0 ),
fBytesAttr ( -1 ),
fCharForm ( eChar8Bit ),
fAccess ( ' ' ),
fBogusPacket ( false ),
// Private members
fBufferOffset ( bufferOffset ),
fBufferOrigin ( (const char *) bufferOrigin ),
fBufferPtr ( fBufferOrigin ),
fBufferLimit ( fBufferOrigin + bufferLength ),
fRecognizer ( eLeadInRecognizer ),
fPosition ( 0 ),
fBytesPerChar ( 1 ),
fBufferOverrun ( 0 ),
fQuoteChar ( ' ' )
{
/*
REVIEW NOTES : Should the buffer stuff be in a class?
*/
assert ( bufferOrigin != NULL );
assert ( bufferLength != 0 );
} // PacketMachine
// =================================================================================================
// ~PacketMachine
// ==============
XMPScanner::PacketMachine::~PacketMachine ()
{
// An empty placeholder.
} // ~PacketMachine
// =================================================================================================
// AssociateBuffer
// ===============
void
XMPScanner::PacketMachine::AssociateBuffer ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength )
{
fBufferOffset = bufferOffset;
fBufferOrigin = (const char *) bufferOrigin;
fBufferPtr = fBufferOrigin + fBufferOverrun;
fBufferLimit = fBufferOrigin + bufferLength;
} // AssociateBuffer
// =================================================================================================
// ResetMachine
// ============
void
XMPScanner::PacketMachine::ResetMachine ()
{
fRecognizer = eLeadInRecognizer;
fPosition = 0;
fBufferOverrun = 0;
fCharForm = eChar8Bit;
fBytesPerChar = 1;
fAccess = ' ';
fBytesAttr = -1;
fBogusPacket = false;
fAttrName.erase ( fAttrName.begin(), fAttrName.end() );
fAttrValue.erase ( fAttrValue.begin(), fAttrValue.end() );
fEncodingAttr.erase ( fEncodingAttr.begin(), fEncodingAttr.end() );
} // ResetMachine
// =================================================================================================
// FindLessThan
// ============
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::FindLessThan ( PacketMachine * ths, const char * which )
{
if ( *which == 'H' ) {
// --------------------------------------------------------------------------------
// We're looking for the '<' of the header. If we fail there is no packet in this
// part of the input, so return eTriNo.
ths->fCharForm = eChar8Bit; // We might have just failed from a bogus 16 or 32 bit case.
ths->fBytesPerChar = 1;
while ( ths->fBufferPtr < ths->fBufferLimit ) { // Don't skip nulls for the header's '<'!
if ( *ths->fBufferPtr == '<' ) break;
ths->fBufferPtr++;
}
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriNo;
ths->fBufferPtr++;
return eTriYes;
} else {
// --------------------------------------------------------------------------------
// We're looking for the '<' of the trailer. We're already inside the packet body,
// looking for the trailer. So here if we fail we must return eTriMaybe so that we
// keep looking for the trailer in the next buffer.
const int bytesPerChar = ths->fBytesPerChar;
while ( ths->fBufferPtr < ths->fBufferLimit ) {
if ( *ths->fBufferPtr == '<' ) break;
ths->fBufferPtr += bytesPerChar;
}
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
ths->fBufferPtr += bytesPerChar;
return eTriYes;
}
} // FindLessThan
// =================================================================================================
// MatchString
// ===========
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::MatchString ( PacketMachine * ths, const char * literal )
{
const int bytesPerChar = ths->fBytesPerChar;
const char * litPtr = literal + ths->fPosition;
const XMP_Int32 charsToGo = (XMP_Int32) strlen ( literal ) - ths->fPosition;
int charsDone = 0;
while ( (charsDone < charsToGo) && (ths->fBufferPtr < ths->fBufferLimit) ) {
if ( *litPtr != *ths->fBufferPtr ) return eTriNo;
charsDone++;
litPtr++;
ths->fBufferPtr += bytesPerChar;
}
if ( charsDone == charsToGo ) return eTriYes;
ths->fPosition += charsDone;
return eTriMaybe;
} // MatchString
// =================================================================================================
// MatchChar
// =========
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::MatchChar ( PacketMachine * ths, const char * literal )
{
const int bytesPerChar = ths->fBytesPerChar;
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
const char currChar = *ths->fBufferPtr;
if ( currChar != *literal ) return eTriNo;
ths->fBufferPtr += bytesPerChar;
return eTriYes;
} // MatchChar
// =================================================================================================
// MatchOpenQuote
// ==============
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::MatchOpenQuote ( PacketMachine * ths, const char * /* unused */ )
{
const int bytesPerChar = ths->fBytesPerChar;
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
const char currChar = *ths->fBufferPtr;
if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
ths->fQuoteChar = currChar;
ths->fBufferPtr += bytesPerChar;
return eTriYes;
} // MatchOpenQuote
// =================================================================================================
// MatchCloseQuote
// ===============
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::MatchCloseQuote ( PacketMachine * ths, const char * /* unused */ )
{
return MatchChar ( ths, &ths->fQuoteChar );
} // MatchCloseQuote
// =================================================================================================
// CaptureAttrName
// ===============
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::CaptureAttrName ( PacketMachine * ths, const char * /* unused */ )
{
const int bytesPerChar = ths->fBytesPerChar;
char currChar;
if ( ths->fPosition == 0 ) { // Get the first character in the name.
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
currChar = *ths->fBufferPtr;
if ( ths->fAttrName.size() == 0 ) {
if ( ! ( ( ('a' <= currChar) && (currChar <= 'z') ) ||
( ('A' <= currChar) && (currChar <= 'Z') ) ||
(currChar == '_') || (currChar == ':') ) ) {
return eTriNo;
}
}
ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
#if UseStringPushBack
ths->fAttrName.push_back ( currChar );
#else
ths->fAttrName.insert ( ths->fAttrName.end(), currChar );
#endif
ths->fBufferPtr += bytesPerChar;
}
while ( ths->fBufferPtr < ths->fBufferLimit ) { // Get the remainder of the name.
currChar = *ths->fBufferPtr;
if ( ! ( ( ('a' <= currChar) && (currChar <= 'z') ) ||
( ('A' <= currChar) && (currChar <= 'Z') ) ||
( ('0' <= currChar) && (currChar <= '9') ) ||
(currChar == '-') || (currChar == '.') || (currChar == '_') || (currChar == ':') ) ) {
break;
}
#if UseStringPushBack
ths->fAttrName.push_back ( currChar );
#else
ths->fAttrName.insert ( ths->fAttrName.end(), currChar );
#endif
ths->fBufferPtr += bytesPerChar;
}
if ( ths->fBufferPtr < ths->fBufferLimit ) return eTriYes;
ths->fPosition = (long) ths->fAttrName.size(); // The name might span into the next buffer.
return eTriMaybe;
} // CaptureAttrName
// =================================================================================================
// CaptureAttrValue
// ================
//
// Recognize the equal sign and the quoted string value, capture the value along the way.
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::CaptureAttrValue ( PacketMachine * ths, const char * /* unused */ )
{
const int bytesPerChar = ths->fBytesPerChar;
char currChar = 0;
TriState result = eTriMaybe;
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
switch ( ths->fPosition ) {
case 0 : // The name should haved ended at the '=', nulls already skipped.
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
if ( *ths->fBufferPtr != '=' ) return eTriNo;
ths->fBufferPtr += bytesPerChar;
ths->fPosition = 1;
// fall through OK because MatchOpenQuote will check the buffer limit and nulls ...
case 1 : // Look for the open quote.
result = MatchOpenQuote ( ths, NULL );
if ( result != eTriYes ) return result;
ths->fPosition = 2;
// fall through OK because the buffer limit and nulls are checked below ...
default : // Look for the close quote, capturing the value along the way.
assert ( ths->fPosition == 2 );
const char quoteChar = ths->fQuoteChar;
while ( ths->fBufferPtr < ths->fBufferLimit ) {
currChar = *ths->fBufferPtr;
if ( currChar == quoteChar ) break;
#if UseStringPushBack
ths->fAttrValue.push_back ( currChar );
#else
ths->fAttrValue.insert ( ths->fAttrValue.end(), currChar );
#endif
ths->fBufferPtr += bytesPerChar;
}
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
assert ( currChar == quoteChar );
ths->fBufferPtr += bytesPerChar; // Advance past the closing quote.
return eTriYes;
}
} // CaptureAttrValue
// =================================================================================================
// RecordStart
// ===========
//
// Note that this routine looks at bytes, not logical characters. It has to figure out how many
// bytes per character there are so that the other recognizers can skip intervening nulls.
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::RecordStart ( PacketMachine * ths, const char * /* unused */ )
{
while ( true ) {
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
const char currByte = *ths->fBufferPtr;
switch ( ths->fPosition ) {
case 0 : // Record the length.
assert ( ths->fCharForm == eChar8Bit );
assert ( ths->fBytesPerChar == 1 );
ths->fPacketStart = ths->fBufferOffset + ((ths->fBufferPtr - 1) - ths->fBufferOrigin);
ths->fPacketLength = 0;
ths->fPosition = 1;
// ! OK to fall through here, we didn't consume a byte in this step.
case 1 : // Look for the first null byte.
if ( currByte != 0 ) return eTriYes; // No nulls found.
ths->fCharForm = eChar16BitBig; // Assume 16 bit big endian for now.
ths->fBytesPerChar = 2;
ths->fBufferPtr++;
ths->fPosition = 2;
break; // ! Don't fall through, have to check for the end of the buffer between each byte.
case 2 : // One null was found, look for a second.
if ( currByte != 0 ) return eTriYes; // Just one null found.
ths->fBufferPtr++;
ths->fPosition = 3;
break;
case 3 : // Two nulls were found, look for a third.
if ( currByte != 0 ) return eTriNo; // Just two nulls is not valid.
ths->fCharForm = eChar32BitBig; // Assume 32 bit big endian for now.
ths->fBytesPerChar = 4;
ths->fBufferPtr++;
return eTriYes;
break;
}
}
} // RecordStart
// =================================================================================================
// RecognizeBOM
// ============
//
// Recognizing the byte order marker is a surprisingly messy thing to do. It can't be done by the
// normal string matcher, there are no intervening nulls. There are 4 transitions after the opening
// quote, the closing quote or one of the three encodings. For the actual BOM there are then 1 or 2
// following bytes that depend on which of the encodings we're in. Not to mention that the buffer
// might end at any point.
//
// The intervening null count done earlier determined 8, 16, or 32 bits per character, but not the
// big or little endian nature for the 16/32 bit cases. The BOM must be present for the 16 and 32
// bit cases in order to determine the endian mode. There are six possible byte sequences for the
// quoted BOM string, ignoring the differences for quoting with ''' versus '"'.
//
// Keep in mind that for the 16 and 32 bit cases there will be nulls for the quote. In the table
// below the symbol <quote> means just the one byte containing the ''' or '"'. The nulls for the
// quote character are explicitly shown.
//
// <quote> <quote> - 1: No BOM, this must be an 8 bit case.
// <quote> \xEF \xBB \xBF <quote> - 1.12-13: The 8 bit form.
//
// <quote> \xFE \xFF \x00 <quote> - 1.22-23: The 16 bit, big endian form
// <quote> \x00 \xFF \xFE <quote> - 1.32-33: The 16 bit, little endian form.
//
// <quote> \x00 \x00 \xFE \xFF \x00 \x00 \x00 <quote> - 1.32.43-45.56-57: The 32 bit, big endian form.
// <quote> \x00 \x00 \x00 \xFF \xFE \x00 \x00 <quote> - 1.32.43.54-57: The 32 bit, little endian form.
enum {
eBOM_8_1 = 0xEF,
eBOM_8_2 = 0xBB,
eBOM_8_3 = 0xBF,
eBOM_Big_1 = 0xFE,
eBOM_Big_2 = 0xFF,
eBOM_Little_1 = eBOM_Big_2,
eBOM_Little_2 = eBOM_Big_1
};
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::RecognizeBOM ( PacketMachine * ths, const char * /* unused */ )
{
const int bytesPerChar = ths->fBytesPerChar;
while ( true ) { // Handle one character at a time, the micro-state (fPosition) changes for each.
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
const unsigned char currChar = *ths->fBufferPtr; // ! The BOM bytes look like integers bigger than 127.
switch ( ths->fPosition ) {
case 0 : // Look for the opening quote.
if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
ths->fQuoteChar = currChar;
ths->fBufferPtr++;
ths->fPosition = 1;
break; // ! Don't fall through, have to check for the end of the buffer between each byte.
case 1 : // Look at the byte immediately following the opening quote.
if ( currChar == ths->fQuoteChar ) { // Closing quote, no BOM character, must be 8 bit.
if ( ths->fCharForm != eChar8Bit ) return eTriNo;
ths->fBufferPtr += bytesPerChar; // Skip the nulls after the closing quote.
return eTriYes;
} else if ( currChar == eBOM_8_1 ) { // Start of the 8 bit form.
if ( ths->fCharForm != eChar8Bit ) return eTriNo;
ths->fBufferPtr++;
ths->fPosition = 12;
} else if ( currChar == eBOM_Big_1 ) { // Start of the 16 bit big endian form.
if ( ths->fCharForm != eChar16BitBig ) return eTriNo;
ths->fBufferPtr++;
ths->fPosition = 22;
} else if ( currChar == 0 ) { // Start of the 16 bit little endian or either 32 bit form.
if ( ths->fCharForm == eChar8Bit ) return eTriNo;
ths->fBufferPtr++;
ths->fPosition = 32;
} else {
return eTriNo;
}
break;
case 12 : // Look for the second byte of the 8 bit form.
if ( currChar != eBOM_8_2 ) return eTriNo;
ths->fPosition = 13;
ths->fBufferPtr++;
break;
case 13 : // Look for the third byte of the 8 bit form.
if ( currChar != eBOM_8_3 ) return eTriNo;
ths->fPosition = 99;
ths->fBufferPtr++;
break;
case 22 : // Look for the second byte of the 16 bit big endian form.
if ( currChar != eBOM_Big_2 ) return eTriNo;
ths->fPosition = 23;
ths->fBufferPtr++;
break;
case 23 : // Look for the null before the closing quote of the 16 bit big endian form.
if ( currChar != 0 ) return eTriNo;
ths->fBufferPtr++;
ths->fPosition = 99;
break;
case 32 : // Look at the second byte of the 16 bit little endian or either 32 bit form.
if ( currChar == eBOM_Little_1 ) {
ths->fPosition = 33;
} else if ( currChar == 0 ) {
ths->fPosition = 43;
} else {
return eTriNo;
}
ths->fBufferPtr++;
break;
case 33 : // Look for the third byte of the 16 bit little endian form.
if ( ths->fCharForm != eChar16BitBig ) return eTriNo; // Null count before assumed big endian.
if ( currChar != eBOM_Little_2 ) return eTriNo;
ths->fCharForm = eChar16BitLittle;
ths->fPosition = 99;
ths->fBufferPtr++;
break;
case 43 : // Look at the third byte of either 32 bit form.
if ( ths->fCharForm != eChar32BitBig ) return eTriNo; // Null count before assumed big endian.
if ( currChar == eBOM_Big_1 ) {
ths->fPosition = 44;
} else if ( currChar == 0 ) {
ths->fPosition = 54;
} else {
return eTriNo;
}
ths->fBufferPtr++;
break;
case 44 : // Look for the fourth byte of the 32 bit big endian form.
if ( currChar != eBOM_Big_2 ) return eTriNo;
ths->fPosition = 45;
ths->fBufferPtr++;
break;
case 45 : // Look for the first null before the closing quote of the 32 bit big endian form.
if ( currChar != 0 ) return eTriNo;
ths->fPosition = 56;
ths->fBufferPtr++;
break;
case 54 : // Look for the fourth byte of the 32 bit little endian form.
ths->fCharForm = eChar32BitLittle;
if ( currChar != eBOM_Little_1 ) return eTriNo;
ths->fPosition = 55;
ths->fBufferPtr++;
break;
case 55 : // Look for the fifth byte of the 32 bit little endian form.
if ( currChar != eBOM_Little_2 ) return eTriNo;
ths->fPosition = 56;
ths->fBufferPtr++;
break;
case 56 : // Look for the next to last null before the closing quote of the 32 bit forms.
if ( currChar != 0 ) return eTriNo;
ths->fPosition = 57;
ths->fBufferPtr++;
break;
case 57 : // Look for the last null before the closing quote of the 32 bit forms.
if ( currChar != 0 ) return eTriNo;
ths->fPosition = 99;
ths->fBufferPtr++;
break;
default : // Look for the closing quote.
assert ( ths->fPosition == 99 );
if ( currChar != ths->fQuoteChar ) return eTriNo;
ths->fBufferPtr += bytesPerChar; // Skip the nulls after the closing quote.
return eTriYes;
break;
}
}
} // RecognizeBOM
// =================================================================================================
// RecordHeadAttr
// ==============
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::RecordHeadAttr ( PacketMachine * ths, const char * /* unused */ )
{
if ( ths->fAttrName == "encoding" ) {
assert ( ths->fEncodingAttr.empty() );
ths->fEncodingAttr = ths->fAttrValue;
} else if ( ths->fAttrName == "bytes" ) {
long value = 0;
int count = (int) ths->fAttrValue.size();
int i;
assert ( ths->fBytesAttr == -1 );
if ( count > 0 ) { // Allow bytes='' to be the same as no bytes attribute.
for ( i = 0; i < count; i++ ) {
const char currChar = ths->fAttrValue[i];
if ( ('0' <= currChar) && (currChar <= '9') ) {
value = (value * 10) + (currChar - '0');
} else {
ths->fBogusPacket = true;
value = -1;
break;
}
}
ths->fBytesAttr = value;
if ( CharFormIs16Bit ( ths->fCharForm ) ) {
if ( (ths->fBytesAttr & 1) != 0 ) ths->fBogusPacket = true;
} else if ( CharFormIs32Bit ( ths->fCharForm ) ) {
if ( (ths->fBytesAttr & 3) != 0 ) ths->fBogusPacket = true;
}
}
}
ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
ths->fAttrValue.erase ( ths->fAttrValue.begin(), ths->fAttrValue.end() );
return eTriYes;
} // RecordHeadAttr
// =================================================================================================
// CaptureAccess
// =============
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::CaptureAccess ( PacketMachine * ths, const char * /* unused */ )
{
const int bytesPerChar = ths->fBytesPerChar;
while ( true ) {
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
const char currChar = *ths->fBufferPtr;
switch ( ths->fPosition ) {
case 0 : // Look for the opening quote.
if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
ths->fQuoteChar = currChar;
ths->fBufferPtr += bytesPerChar;
ths->fPosition = 1;
break; // ! Don't fall through, have to check for the end of the buffer between each byte.
case 1 : // Look for the 'r' or 'w'.
if ( (currChar != 'r') && (currChar != 'w') ) return eTriNo;
ths->fAccess = currChar;
ths->fBufferPtr += bytesPerChar;
ths->fPosition = 2;
break;
default : // Look for the closing quote.
assert ( ths->fPosition == 2 );
if ( currChar != ths->fQuoteChar ) return eTriNo;
ths->fBufferPtr += bytesPerChar;
return eTriYes;
break;
}
}
} // CaptureAccess
// =================================================================================================
// RecordTailAttr
// ==============
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::RecordTailAttr ( PacketMachine * ths, const char * /* unused */ )
{
// There are no known "general" attributes for the packet trailer.
ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
ths->fAttrValue.erase ( ths->fAttrValue.begin(), ths->fAttrValue.end() );
return eTriYes;
} // RecordTailAttr
// =================================================================================================
// CheckPacketEnd
// ==============
//
// Check for trailing padding and record the packet length. We have trailing padding if the bytes
// attribute is present and has a value greater than the current length.
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::CheckPacketEnd ( PacketMachine * ths, const char * /* unused */ )
{
const int bytesPerChar = ths->fBytesPerChar;
if ( ths->fPosition == 0 ) { // First call, decide if there is trailing padding.
const XMP_Int64 currLen64 = (ths->fBufferOffset + (ths->fBufferPtr - ths->fBufferOrigin)) - ths->fPacketStart;
if ( currLen64 > 0x7FFFFFFF ) throw std::runtime_error ( "Packet length exceeds 2GB-1" );
const XMP_Int32 currLength = (XMP_Int32)currLen64;
if ( (ths->fBytesAttr != -1) && (ths->fBytesAttr != currLength) ) {
if ( ths->fBytesAttr < currLength ) {
ths->fBogusPacket = true; // The bytes attribute value is too small.
} else {
ths->fPosition = ths->fBytesAttr - currLength;
if ( (ths->fPosition % ths->fBytesPerChar) != 0 ) {
ths->fBogusPacket = true; // The padding is not a multiple of the character size.
ths->fPosition = (ths->fPosition / ths->fBytesPerChar) * ths->fBytesPerChar;
}
}
}
}
while ( ths->fPosition > 0 ) {
if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
const char currChar = *ths->fBufferPtr;
if ( (currChar != ' ') && (currChar != '\t') && (currChar != '\n') && (currChar != '\r') ) {
ths->fBogusPacket = true; // The padding is not whitespace.
break; // Stop the packet here.
}
ths->fPosition -= bytesPerChar;
ths->fBufferPtr += bytesPerChar;
}
const XMP_Int64 currLen64 = (ths->fBufferOffset + (ths->fBufferPtr - ths->fBufferOrigin)) - ths->fPacketStart;
if ( currLen64 > 0x7FFFFFFF ) throw std::runtime_error ( "Packet length exceeds 2GB-1" );
ths->fPacketLength = (XMP_Int32)currLen64;
return eTriYes;
} // CheckPacketEnd
// =================================================================================================
// CheckFinalNulls
// ===============
//
// Do some special case processing for little endian characters. We have to make sure the presumed
// nulls after the last character actually exist, i.e. that the stream does not end too soon. Note
// that the prior character scanning has moved the buffer pointer to the address following the last
// byte of the last character. I.e. we're already past the presumed nulls, so we can't check their
// content. All we can do is verify that the stream does not end too soon.
//
// Doing this check is simple yet subtle. If we're still in the current buffer then the trailing
// bytes obviously exist. If we're exactly at the end of the buffer then the bytes also exist.
// The only question is when we're actually past this buffer, partly into the next buffer. This is
// when "ths->fBufferPtr > ths->fBufferLimit" on entry. For that case we have to wait until we've
// actually seen enough extra bytes of input.
//
// Since the normal buffer processing is already adjusting for this partial character overrun, all
// that needs to be done here is wait until "ths->fBufferPtr <= ths->fBufferLimit" on entry. In
// other words, if we're presently too far, ths->fBufferPtr will be adjusted by the amount of the
// overflow the next time XMPScanner::Scan is called. This might still be too far, so just keep
// waiting for enough data to pass by.
//
// Note that there is a corresponding special case for big endian characters, we must decrement the
// starting offset by the number of leading nulls. But we don't do that here, we leave it to the
// outer code. This is because the leading nulls might have been at the exact end of a previous
// buffer, in which case we have to also decrement the length of that raw data snip.
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::CheckFinalNulls ( PacketMachine * ths, const char * /* unused */ )
{
if ( (ths->fCharForm != eChar8Bit) && CharFormIsLittleEndian ( ths->fCharForm ) ) {
if ( ths->fBufferPtr > ths->fBufferLimit ) return eTriMaybe;
}
return eTriYes;
} // CheckFinalNulls
// =================================================================================================
// SetNextRecognizer
// =================
void
XMPScanner::PacketMachine::SetNextRecognizer ( RecognizerKind nextRecognizer )
{
fRecognizer = nextRecognizer;
fPosition = 0;
} // SetNextRecognizer
// =================================================================================================
// FindNextPacket
// ==============
// *** When we start validating intervening nulls for 2 and 4 bytes characters, throw an exception
// *** for errors. Don't return eTriNo, that might skip at an optional point.
XMPScanner::PacketMachine::TriState
XMPScanner::PacketMachine::FindNextPacket ()
{
TriState status;
#define kPacketHead "?xpacket begin="
#define kPacketID "W5M0MpCehiHzreSzNTczkc9d"
#define kPacketTail "?xpacket end="
static const RecognizerInfo recognizerTable [eRecognizerCount] = { // ! Would be safer to assign these explicitly.
// proc successNext failureNext literal
{ NULL, eFailureRecognizer, eFailureRecognizer, NULL}, // eFailureRecognizer
{ NULL, eSuccessRecognizer, eSuccessRecognizer, NULL}, // eSuccessRecognizer
{ FindLessThan, eHeadStartRecorder, eFailureRecognizer, "H" }, // eLeadInRecognizer
{ RecordStart, eHeadStartRecognizer, eLeadInRecognizer, NULL }, // eHeadStartRecorder
{ MatchString, eBOMRecognizer, eLeadInRecognizer, kPacketHead }, // eHeadStartRecognizer
{ RecognizeBOM, eIDTagRecognizer, eLeadInRecognizer, NULL }, // eBOMRecognizer
{ MatchString, eIDOpenRecognizer, eLeadInRecognizer, " id=" }, // eIDTagRecognizer
{ MatchOpenQuote, eIDValueRecognizer, eLeadInRecognizer, NULL }, // eIDOpenRecognizer
{ MatchString, eIDCloseRecognizer, eLeadInRecognizer, kPacketID }, // eIDValueRecognizer
{ MatchCloseQuote, eAttrSpaceRecognizer_1, eLeadInRecognizer, NULL }, // eIDCloseRecognizer
{ MatchChar, eAttrNameRecognizer_1, eHeadEndRecognizer, " " }, // eAttrSpaceRecognizer_1
{ CaptureAttrName, eAttrValueRecognizer_1, eLeadInRecognizer, NULL }, // eAttrNameRecognizer_1
{ CaptureAttrValue, eAttrValueRecorder_1, eLeadInRecognizer, NULL }, // eAttrValueRecognizer_1
{ RecordHeadAttr, eAttrSpaceRecognizer_1, eLeadInRecognizer, NULL }, // eAttrValueRecorder_1
{ MatchString, eBodyRecognizer, eLeadInRecognizer, "?>" }, // eHeadEndRecognizer
{ FindLessThan, eTailStartRecognizer, eBodyRecognizer, "T"}, // eBodyRecognizer
{ MatchString, eAccessValueRecognizer, eBodyRecognizer, kPacketTail }, // eTailStartRecognizer
{ CaptureAccess, eAttrSpaceRecognizer_2, eBodyRecognizer, NULL }, // eAccessValueRecognizer
{ MatchChar, eAttrNameRecognizer_2, eTailEndRecognizer, " " }, // eAttrSpaceRecognizer_2
{ CaptureAttrName, eAttrValueRecognizer_2, eBodyRecognizer, NULL }, // eAttrNameRecognizer_2
{ CaptureAttrValue, eAttrValueRecorder_2, eBodyRecognizer, NULL }, // eAttrValueRecognizer_2
{ RecordTailAttr, eAttrSpaceRecognizer_2, eBodyRecognizer, NULL }, // eAttrValueRecorder_2
{ MatchString, ePacketEndRecognizer, eBodyRecognizer, "?>" }, // eTailEndRecognizer
{ CheckPacketEnd, eCloseOutRecognizer, eBodyRecognizer, "" }, // ePacketEndRecognizer
{ CheckFinalNulls, eSuccessRecognizer, eBodyRecognizer, "" } // eCloseOutRecognizer
};
while ( true ) {
switch ( fRecognizer ) {
case eFailureRecognizer :
return eTriNo;
case eSuccessRecognizer :
return eTriYes;
default :
// -------------------------------------------------------------------
// For everything else, the normal cases, use the state machine table.
const RecognizerInfo * thisState = &recognizerTable [fRecognizer];
status = thisState->proc ( this, thisState->literal );
switch ( status ) {
case eTriNo :
SetNextRecognizer ( thisState->failureNext );
continue;
case eTriYes :
SetNextRecognizer ( thisState->successNext );
continue;
case eTriMaybe :
fBufferOverrun = (unsigned char)(fBufferPtr - fBufferLimit);
return eTriMaybe; // Keep this recognizer intact, to be resumed later.
}
} // switch ( fRecognizer ) { ...
} // while ( true ) { ...
} // FindNextPacket
// =================================================================================================
// =================================================================================================
// class InternalSnip
// ==================
// =================================================================================================
// InternalSnip
// ============
XMPScanner::InternalSnip::InternalSnip ( XMP_Int64 offset, XMP_Int64 length )
{
fInfo.fOffset = offset;
fInfo.fLength = length;
} // InternalSnip
// =================================================================================================
// InternalSnip
// ============
XMPScanner::InternalSnip::InternalSnip ( const InternalSnip & rhs ) :
fInfo ( rhs.fInfo ),
fMachine ()
{
assert ( rhs.fMachine.get() == NULL ); // Don't copy a snip with a machine.
assert ( (rhs.fInfo.fEncodingAttr == 0) || (*rhs.fInfo.fEncodingAttr == 0) ); // Don't copy a snip with an encoding.
} // InternalSnip
// =================================================================================================
// ~InternalSnip
// =============
XMPScanner::InternalSnip::~InternalSnip ()
{
} // ~InternalSnip
// =================================================================================================
// =================================================================================================
// class XMPScanner
// ================
// =================================================================================================
// DumpSnipList
// ============
#if DEBUG
static const char * snipStateName [6] = { "not-seen", "pending", "raw-data", "good-packet", "partial", "bad-packet" };
void
XMPScanner::DumpSnipList ( const char * title )
{
InternalSnipIterator currPos = fInternalSnips.begin();
InternalSnipIterator endPos = fInternalSnips.end();
cout << endl << title << " snip list: " << fInternalSnips.size() << endl;
for ( ; currPos != endPos; ++currPos ) {
SnipInfo * currSnip = &currPos->fInfo;
cout << '\t' << currSnip << ' ' << snipStateName[currSnip->fState] << ' '
<< currSnip->fOffset << ".." << (currSnip->fOffset + currSnip->fLength - 1)
<< ' ' << currSnip->fLength << ' ' << endl;
}
} // DumpSnipList
#endif
// =================================================================================================
// PrevSnip and NextSnip
// =====================
XMPScanner::InternalSnipIterator
XMPScanner::PrevSnip ( InternalSnipIterator snipPos )
{
InternalSnipIterator prev = snipPos;
return --prev;
} // PrevSnip
XMPScanner::InternalSnipIterator
XMPScanner::NextSnip ( InternalSnipIterator snipPos )
{
InternalSnipIterator next = snipPos;
return ++next;
} // NextSnip
// =================================================================================================
// XMPScanner
// ==========
//
// Initialize the scanner object with one "not seen" snip covering the whole stream.
XMPScanner::XMPScanner ( XMP_Int64 streamLength ) :
fStreamLength ( streamLength )
{
InternalSnip rootSnip ( 0, streamLength );
if ( streamLength > 0 ) fInternalSnips.push_front ( rootSnip ); // Be nice for empty files.
// DumpSnipList ( "New XMPScanner" );
} // XMPScanner
// =================================================================================================
// ~XMPScanner
// ===========
XMPScanner::~XMPScanner()
{
} // ~XMPScanner
// =================================================================================================
// GetSnipCount
// ============
long
XMPScanner::GetSnipCount ()
{
return (long)fInternalSnips.size();
} // GetSnipCount
// =================================================================================================
// StreamAllScanned
// ================
bool
XMPScanner::StreamAllScanned ()
{
InternalSnipIterator currPos = fInternalSnips.begin();
InternalSnipIterator endPos = fInternalSnips.end();
for ( ; currPos != endPos; ++currPos ) {
if ( currPos->fInfo.fState == eNotSeenSnip ) return false;
}
return true;
} // StreamAllScanned
// =================================================================================================
// SplitInternalSnip
// =================
//
// Split the given snip into up to 3 pieces. The new pieces are inserted before and after this one
// in the snip list. The relOffset is the first byte to be kept, it is relative to this snip. If
// the preceeding or following snips have the same state as this one, just shift the boundaries.
// I.e. move the contents from one snip to the other, don't create a new snip.
// *** To be thread safe we ought to lock the entire list during manipulation. Let data scanning
// *** happen in parallel, serialize all mucking with the list.
void
XMPScanner::SplitInternalSnip ( InternalSnipIterator snipPos, XMP_Int64 relOffset, XMP_Int64 newLength )
{
assert ( (relOffset + newLength) > relOffset ); // Check for overflow.
assert ( (relOffset + newLength) <= snipPos->fInfo.fLength );
// -----------------------------------
// First deal with the low offset end.
if ( relOffset > 0 ) {
InternalSnipIterator prevPos;
if ( snipPos != fInternalSnips.begin() ) prevPos = PrevSnip ( snipPos );
if ( (snipPos != fInternalSnips.begin()) && (snipPos->fInfo.fState == prevPos->fInfo.fState) ) {
prevPos->fInfo.fLength += relOffset; // Adjust the preceeding snip.
} else {
InternalSnip headExcess ( snipPos->fInfo.fOffset, relOffset );
headExcess.fInfo.fState = snipPos->fInfo.fState;
headExcess.fInfo.fOutOfOrder = snipPos->fInfo.fOutOfOrder;
fInternalSnips.insert ( snipPos, headExcess ); // Insert the head piece before the middle piece.
}
snipPos->fInfo.fOffset += relOffset; // Adjust the remainder of this snip.
snipPos->fInfo.fLength -= relOffset;
}
// ----------------------------------
// Now deal with the high offset end.
if ( newLength < snipPos->fInfo.fLength ) {
InternalSnipIterator nextPos = NextSnip ( snipPos );
const XMP_Int64 tailLength = snipPos->fInfo.fLength - newLength;
if ( (nextPos != fInternalSnips.end()) && (snipPos->fInfo.fState == nextPos->fInfo.fState) ) {
nextPos->fInfo.fOffset -= tailLength; // Adjust the following snip.
nextPos->fInfo.fLength += tailLength;
} else {
InternalSnip tailExcess ( (snipPos->fInfo.fOffset + newLength), tailLength );
tailExcess.fInfo.fState = snipPos->fInfo.fState;
tailExcess.fInfo.fOutOfOrder = snipPos->fInfo.fOutOfOrder;
fInternalSnips.insert ( nextPos, tailExcess ); // Insert the tail piece after the middle piece.
}
snipPos->fInfo.fLength = newLength;
}
} // SplitInternalSnip
// =================================================================================================
// MergeInternalSnips
// ==================
XMPScanner::InternalSnipIterator
XMPScanner::MergeInternalSnips ( InternalSnipIterator firstPos, InternalSnipIterator secondPos )
{
firstPos->fInfo.fLength += secondPos->fInfo.fLength;
fInternalSnips.erase ( secondPos );
return firstPos;
} // MergeInternalSnips
// =================================================================================================
// Scan
// ====
void
XMPScanner::Scan ( const void * bufferOrigin, XMP_Int64 bufferOffset, XMP_Int64 bufferLength )
{
XMP_Int64 relOffset;
#if 0
cout << "Scan: @ " << bufferOrigin << ", " << bufferOffset << ", " << bufferLength << endl;
#endif
if ( bufferLength == 0 ) return;
// ----------------------------------------------------------------
// These comparisons are carefully done to avoid overflow problems.
if ( (bufferOffset >= fStreamLength) ||
(bufferLength > (fStreamLength - bufferOffset)) ||
(bufferOrigin == 0) ) {
throw ScanError ( "Bad origin, offset, or length" );
}
// ----------------------------------------------------------------------------------------------
// This buffer must be within a not-seen snip. Find it and split it. The first snip whose whose
// end is beyond the buffer must be the enclosing one.
// *** It would be friendly for rescans for out of order problems to accept any buffer postion.
const XMP_Int64 endOffset = bufferOffset + bufferLength - 1;
InternalSnipIterator snipPos = fInternalSnips.begin();
while ( endOffset > (snipPos->fInfo.fOffset + snipPos->fInfo.fLength - 1) ) ++ snipPos;
if ( snipPos->fInfo.fState != eNotSeenSnip ) throw ScanError ( "Already seen" );
relOffset = bufferOffset - snipPos->fInfo.fOffset;
if ( (relOffset + bufferLength) > snipPos->fInfo.fLength ) throw ScanError ( "Not within existing snip" );
SplitInternalSnip ( snipPos, relOffset, bufferLength ); // *** If sequential & prev is partial, just tack on,
// --------------------------------------------------------
// Merge this snip with the preceeding snip if appropriate.
// *** When out of order I/O is supported we have to do something about buffers who's predecessor is not seen.
if ( snipPos->fInfo.fOffset > 0 ) {
InternalSnipIterator prevPos = PrevSnip ( snipPos );
if ( prevPos->fInfo.fState == ePartialPacketSnip ) snipPos = MergeInternalSnips ( prevPos, snipPos );
}
// ----------------------------------
// Look for packets within this snip.
snipPos->fInfo.fState = ePendingSnip;
PacketMachine* thisMachine = snipPos->fMachine.get();
// DumpSnipList ( "Before scan" );
if ( thisMachine != 0 ) {
thisMachine->AssociateBuffer ( bufferOffset, bufferOrigin, bufferLength );
} else {
// *** snipPos->fMachine.reset ( new PacketMachine ( bufferOffset, bufferOrigin, bufferLength ) ); VC++ lacks reset
#if 0
snipPos->fMachine = unique_ptr<PacketMachine> ( new PacketMachine ( bufferOffset, bufferOrigin, bufferLength ) );
#else
{
// Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
PacketMachine * pm = new PacketMachine ( bufferOffset, bufferOrigin, bufferLength );
unique_ptr<PacketMachine> ap ( pm );
snipPos->fMachine = std::move(ap);
}
#endif
thisMachine = snipPos->fMachine.get();
}
bool bufferDone = false;
while ( ! bufferDone ) {
PacketMachine::TriState foundPacket = thisMachine->FindNextPacket();
if ( foundPacket == PacketMachine::eTriNo ) {
// -----------------------------------------------------------------------
// No packet, mark the snip as raw data and get rid of the packet machine.
// We're done with this buffer.
snipPos->fInfo.fState = eRawInputSnip;
#if 0
snipPos->fMachine = unique_ptr<PacketMachine>(); // *** snipPos->fMachine.reset(); VC++ lacks reset
#else
{
// Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
unique_ptr<PacketMachine> ap;
snipPos->fMachine = std::move(ap);
}
#endif
bufferDone = true;
} else {
// ---------------------------------------------------------------------------------------------
// Either a full or partial packet. First trim any excess off of the front as a raw input snip.
// If this is a partial packet mark the snip and keep the packet machine to be resumed later.
// We're done with this buffer, the partial packet by definition extends to the end. If this is
// a complete packet first extract the additional information from the packet machine. If there
// is leftover data split the snip and transfer the packet machine to the new trailing snip.
if ( thisMachine->fPacketStart > snipPos->fInfo.fOffset ) {
// There is data at the front of the current snip that must be trimmed.
SnipState savedState = snipPos->fInfo.fState;
snipPos->fInfo.fState = eRawInputSnip; // ! So it gets propagated to the trimmed front part.
relOffset = thisMachine->fPacketStart - snipPos->fInfo.fOffset;
SplitInternalSnip ( snipPos, relOffset, (snipPos->fInfo.fLength - relOffset) );
snipPos->fInfo.fState = savedState;
}
if ( foundPacket == PacketMachine::eTriMaybe ) {
// We have only found a partial packet.
snipPos->fInfo.fState = ePartialPacketSnip;
bufferDone = true;
} else {
// We have found a complete packet. Extract all the info for it and split any trailing data.
InternalSnipIterator packetSnip = snipPos;
SnipState packetState = eValidPacketSnip;
if ( thisMachine->fBogusPacket ) packetState = eBadPacketSnip;
packetSnip->fInfo.fAccess = thisMachine->fAccess;
packetSnip->fInfo.fCharForm = thisMachine->fCharForm;
packetSnip->fInfo.fBytesAttr = thisMachine->fBytesAttr;
packetSnip->fInfo.fEncodingAttr = thisMachine->fEncodingAttr.c_str();
thisMachine->fEncodingAttr.erase ( thisMachine->fEncodingAttr.begin(), thisMachine->fEncodingAttr.end() );
if ( (thisMachine->fCharForm != eChar8Bit) && CharFormIsBigEndian ( thisMachine->fCharForm ) ) {
// ------------------------------------------------------------------------------
// Handle a special case for big endian characters. The packet machine works as
// though things were little endian. The packet starting offset points to the
// byte containing the opening '<', and the length includes presumed nulls that
// follow the last "real" byte. If the characters are big endian we now have to
// decrement the starting offset of the packet, and also decrement the length of
// the previous snip.
//
// Note that we can't do this before the head trimming above in general. The
// nulls might have been exactly at the end of a buffer and already in the
// previous snip. We are doing this before trimming the tail from the raw snip
// containing the packet. We adjust the raw snip's size because it ends with
// the input buffer. We don't adjust the packet's size, it is already correct.
//
// The raw snip (the one before the packet) might entirely disappear. A simple
// example of this is when the packet is at the start of the file.
assert ( packetSnip != fInternalSnips.begin() ); // Leading nulls were trimmed!
if ( packetSnip != fInternalSnips.begin() ) { // ... but let's program defensibly.
InternalSnipIterator prevSnip = PrevSnip ( packetSnip );
const unsigned int nullsToAdd = ( CharFormIs16Bit ( thisMachine->fCharForm ) ? 1 : 3 );
assert ( nullsToAdd <= prevSnip->fInfo.fLength );
prevSnip->fInfo.fLength -= nullsToAdd;
if ( prevSnip->fInfo.fLength == 0 ) (void) fInternalSnips.erase ( prevSnip );
packetSnip->fInfo.fOffset -= nullsToAdd;
packetSnip->fInfo.fLength += nullsToAdd;
thisMachine->fPacketStart -= nullsToAdd;
}
}
if ( thisMachine->fPacketLength == snipPos->fInfo.fLength ) {
// This packet ends exactly at the end of the current snip.
#if 0
snipPos->fMachine = unique_ptr<PacketMachine>(); // *** snipPos->fMachine.reset(); VC++ lacks reset
#else
{
// Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
unique_ptr<PacketMachine> ap;
snipPos->fMachine = std::move(ap);
}
#endif
bufferDone = true;
} else {
// There is trailing data to split from the just found packet.
SplitInternalSnip ( snipPos, 0, thisMachine->fPacketLength );
InternalSnipIterator tailPos = NextSnip ( snipPos );
tailPos->fMachine = std::move(snipPos->fMachine); // unique_ptr assignment - taking ownership
thisMachine->ResetMachine ();
snipPos = tailPos;
}
packetSnip->fInfo.fState = packetState; // Do this last to avoid messing up the tail split.
// DumpSnipList ( "Found a packet" );
}
}
}
// --------------------------------------------------------
// Merge this snip with the preceeding snip if appropriate.
// *** When out of order I/O is supported we have to check the following snip too.
if ( (snipPos->fInfo.fOffset > 0) && (snipPos->fInfo.fState == eRawInputSnip) ) {
InternalSnipIterator prevPos = PrevSnip ( snipPos );
if ( prevPos->fInfo.fState == eRawInputSnip ) snipPos = MergeInternalSnips ( prevPos, snipPos );
}
// DumpSnipList ( "After scan" );
} // Scan
// =================================================================================================
// Report
// ======
void
XMPScanner::Report ( SnipInfoVector& snips )
{
const int count = (int)fInternalSnips.size();
InternalSnipIterator snipPos = fInternalSnips.begin();
int s;
// DumpSnipList ( "Report" );
snips.erase ( snips.begin(), snips.end() ); // ! Should use snips.clear, but VC++ doesn't have it.
snips.reserve ( count );
for ( s = 0; s < count; s += 1 ) {
snips.push_back ( SnipInfo ( snipPos->fInfo.fState, snipPos->fInfo.fOffset, snipPos->fInfo.fLength ) );
snips[s] = snipPos->fInfo; // Pick up all of the fields.
++ snipPos;
}
} // Report
// =================================================================================================
#endif // EnablePacketScanning