--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/persistentstorage/store/HTOOLS/PFSDUMP.CPP Fri Jan 22 11:06:30 2010 +0200
@@ -0,0 +1,861 @@
+// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+//
+
+#include "PFSDUMP.H"
+
+// entry point
+
+int main(int argc,char *argv[])
+ {
+ if (TheProgram.ProcessCommandLine(argc,argv))
+ {
+ TheProgram.ExplainUsage();
+ return 1;
+ }
+ TheProgram.Run();
+ return 0;
+ }
+
+// Class Program
+
+class Program TheProgram;
+
+Program::Program()
+ : iFile(NULL),iOptions(0),iBin(&iBinBuf),iErrors(0)
+ {
+ cout << setfill('0');
+ }
+
+void Program::Information()
+ {
+ if (!Option(quiet))
+ cout << "\nEPOC PermanentFileStore Dump Utility Version " << MajorVersion << '.' \
+ << setw(2) << MinorVersion << "(build " << setw(3) << setfill('0') << Build \
+ << ")\nCopyright (c) Symbian Software Limited 1997-2004. All rights reserved.\n\n" << flush;
+ cout << hex;
+ cerr << hex;
+ }
+
+void Program::ExplainUsage()
+ {
+ Information();
+ cout << "Usage: PFSDUMP [options] storefile\n" \
+ " -v verbose: dump contents\n" \
+ " -f generate a frame dump\n" \
+ " -r<n> use nth previous store revision\n" \
+ " -c format output for comparison (not for frame dumps)\n"
+ " -q quiet: suppress logo\n" << flush;
+ }
+
+int Program::ProcessCommandLine(int argc,char ** argv)
+ {
+ if (argc<2)
+ return 1;
+
+ int options=0;
+ while (--argc>0)
+ {
+ istrstream arg(*++argv);
+ int c=arg.get();
+ if (c=='/' || c=='-')
+ {
+ while ((c=arg.get())!=EOF)
+ {
+ switch (c)
+ {
+ case 'c': case 'C':
+ options|=compare;
+ break;
+ case 'f': case 'F':
+ options|=frame;
+ break;
+ case 'q': case 'Q':
+ options|=quiet;
+ break;
+ case 'r': case 'R':
+ arg >> iTocRevision;
+ if (arg.fail() || iTocRevision<0)
+ return 1;
+ break;
+ case 'v': case 'V':
+ options|=verbose;
+ break;
+ default: // unrecognised option
+ return 1;
+ }
+ }
+ }
+ else if (iFile!=NULL)
+ return 1; // two filenames?
+ else
+ iFile=arg.str();
+ }
+ if (options&frame)
+ options&=~compare;
+ iOptions=options;
+ return iFile==NULL;
+ }
+
+void Program::Run()
+//
+// The main part of the program
+//
+ {
+ Information();
+ StoreFile store(iFile);
+ if (iErrors)
+ cerr << endl;
+ if (store.Empty())
+ {
+ cout << "Store is empty" << endl;
+ return;
+ }
+ if (Option(frame))
+ {
+ store.EmitHeader();
+ store.EmitFrames();
+ }
+ store.EmitToc();
+ if (!Option(frame))
+ store.EmitStreams();
+ }
+
+void Program::Abort(char const* aMessage)
+ {
+ cerr << aMessage << endl;
+ exit(3);
+ }
+
+void Program::Corrupt(char const* aMessage)
+//
+// terminate after detecting a fatal corruption error
+//
+ {
+ cerr << "\nfatal error: " << aMessage << "\ncannot continue\n" << flush;
+ exit(2);
+ }
+
+ostream& Program::Error()
+ {
+ ++TheProgram.iErrors;
+ return cerr << "error: ";
+ }
+
+ostream& Program::Warning()
+ {
+ ++TheProgram.iErrors;
+ return cerr << "warning: ";
+ }
+
+// Bin stream buffer
+
+binbuf::binbuf()
+ : streambuf()
+ {}
+
+int binbuf::overflow(int)
+ {return 0;}
+
+int binbuf::underflow()
+ {return EOF;}
+
+// Class StoreFile
+
+int StoreFile::OutWidth;
+
+StoreFile::StoreFile(char const* aFile)
+ {
+#ifdef __MSVCDOTNET__
+ iFile.open(aFile, ios::binary);
+#else //!__MSVCDOTNET__
+ iFile.open(aFile, ios::binary | ios::nocreate);
+#endif //__MSVCDOTNET__
+ if (!iFile)
+ Program::Abort("Unable to open file or file does not exist");
+//
+ unsigned long uid;
+ iFile.read((char*)&uid,sizeof(uid));
+ iFile.seekg(0,ios::end);
+ int size=iFile.tellg();
+ if (size<FrameDes::First || uid!=PermanentFileStoreUid)
+ Program::Abort("Not a permanent file store");
+//
+ iSize=size;
+ if (TheProgram.Option(Program::compare))
+ OutWidth=7;
+ else
+ {
+ int width=0;
+ while (size)
+ {
+ ++width;
+ size>>=4;
+ }
+ OutWidth=width;
+ }
+//
+ cout << "Dumping " << aFile << "\n\n";
+//
+ iFile.seekg(Header::Offset,ios::beg);
+ iFile.read((char*)&iHeader,Header::Size);
+//
+ if (Empty())
+ return;
+ LoadFrames();
+ LoadToc();
+ }
+
+StoreFile::~StoreFile()
+ {
+ iFile.close();
+ }
+
+void StoreFile::LoadFrames()
+ {
+ FrameDes frame;
+ int offset=FrameDes::First;
+ int full=FrameDes::First+FrameDes::Interval;
+ int diff=FrameDes::First;
+
+ while (offset-FrameDes::Size<iSize)
+ {
+ if (offset==full)
+ {
+ full+=FrameDes::Interval;
+ diff+=FrameDes::Size;
+ }
+ if (offset>iSize)
+ {
+ Program::Warning() << "incomplete link at " << FramePos(offset-diff) << endl;
+ break;
+ }
+ iFile.seekg(offset-FrameDes::Size,ios::beg);
+ iFile >> frame;
+ iFrames.Add(FramePos(offset-diff),frame);
+ int length=frame.Length();
+ if (length==0)
+ {
+ if (full>iSize && offset>iSize)
+ Program::Warning() << "incomplete frame at " << FramePos(offset-diff) << endl;
+ offset=full;
+ }
+ else
+ {
+ int newoffset=offset+length+FrameDes::Size;
+ if (newoffset>=full || newoffset-FrameDes::Size>iSize)
+ {
+ Program::Error() << "bad link at " << FramePos(offset-diff) << ", skipping to next anchor link" << endl;
+ offset=full;
+ }
+ else
+ {
+ offset=newoffset;
+ if (full-offset<=FrameDes::Size)
+ offset=full;
+ }
+ }
+ }
+ iFrames.Complete();
+ }
+
+void StoreFile::LoadToc()
+ {
+ FramePos toc=iHeader.Toc();
+ Stream stream(iFrames,toc);
+ if (!stream.IsGood())
+ {
+ Program::Error() << "invalid toc address " << toc << endl;
+ return;
+ }
+ if (stream.Type()!=FrameDes::Toc)
+ {
+ Program::Error() << "toc address " << toc << ": refers to non-toc frame"<< endl;
+ return;
+ }
+
+// find the requested store revision
+ Frames::Iterator f=stream.Frame();
+ Frames::Iterator const first=iFrames.Begin();
+ for (int rev=TheProgram.TocRevision();rev;--rev)
+ {
+ do {
+ if (--f<first)
+ Program::Abort("Store revision not found");
+ } while (f->iDes.Type()!=FrameDes::Toc);
+ }
+
+ iToc.Load(iFile,iFrames,f,iHeader.GetReloc());
+
+// verify the Toc stream references
+ Toc::Iterator const end=iToc.End();
+ for (Toc::Iterator iter=iToc.Begin();iter<end;++iter)
+ {
+ if (iter->iHandle.IsNull())
+ Program::Error() << "missing entry in toc-delta for index " << (1+iter-iToc.Begin()) << endl;
+ else if (!iter->iHandle.Avail() && iter->Pos().Pos()>=0)
+ {
+ f=iFrames.Find(iter->Pos());
+ if (f==NULL)
+ Program::Error() << "invalid stream reference in toc entry " << iter->iHandle << endl;
+ else if (iter->Pos().Pos()>=toc.Pos())
+ Program::Error() << "virtual stream reference in toc entry " << iter->iHandle << endl;
+ else if (f->iDes.Type()!=FrameDes::Data)
+ Program::Error() << "toc entry " << iter->iHandle << ": refers to non-data frame" << endl;
+ }
+ }
+ }
+
+void StoreFile::EmitHeader()
+ {
+ cout << iHeader;
+ }
+
+void StoreFile::EmitFrames()
+ {
+ int verbose=TheProgram.Option(Program::verbose);
+ Frames::Iterator const end=iFrames.End();
+ for (Frames::Iterator iter=iFrames.Begin();iter<end;++iter)
+ {
+ FramePos pos=iter->iPos;
+ cout << "Frame at " << pos << ": " << iter->iDes << endl;
+ if (!verbose)
+ continue;
+
+ // dump contents
+ int length=iter->iDes.Length();
+ if (length==0)
+ { // full frame
+ if (iter+1==end) // no more
+ continue;
+ length=iter[1].iPos.Pos()-pos.Pos();
+ }
+ HexDump hex;
+ iFile.seekg(FileOffset(pos).Offset(),ios::beg);
+ hex.Dump(iFile,length);
+ hex.Flush();
+ cout << endl;
+ }
+ cout << endl;
+ }
+
+void StoreFile::EmitToc()
+ {
+ cout << iToc;
+ }
+
+void StoreFile::EmitStreams()
+ {
+ int verbose=TheProgram.Option(Program::verbose);
+ cout << endl;
+ Toc::Iterator const end=iToc.End();
+ for (Toc::Iterator iter=iToc.Begin();iter<end;++iter)
+ {
+ if (!iter->iHandle.Avail())
+ {
+ cout << "Stream " << iter->iHandle;
+ if (iter->Pos().Pos()==-1)
+ {
+ cout << " is empty\n";
+ continue;
+ }
+ if (!TheProgram.Option(Program::compare))
+ cout << " at " << iter->Pos();
+ Stream stream(iFrames,iter->Pos());
+ if (!stream.IsGood() || stream.Type()!=FrameDes::Data)
+ {
+ cout << " is invalid\n";
+ continue;
+ }
+ int len=stream.Length();
+ cout << ", " << dec << len << hex << " byte" << (len==1 ? "\n" : "s\n");
+ if (!verbose)
+ continue;
+
+ // dump contents
+ HexDump hex;
+ Frames::Iterator f=stream.Frame();
+ do
+ {
+ FramePos pos=f->iPos;
+ int len=f++->iDes.Length();
+ if (len==0)
+ len=f->iPos.Pos()-pos.Pos();
+ iFile.seekg(FileOffset(pos).Offset(),ios::beg);
+ hex.Dump(iFile,len);
+ } while (f->iDes.Type()==FrameDes::Continuation);
+ hex.Flush();
+ cout << endl;
+ }
+ }
+ }
+
+// Class HexDump
+
+HexDump::HexDump()
+ : iPos(0),iByte(0)
+ {
+ Reset();
+ }
+
+HexDump::~HexDump()
+ {
+ Flush();
+ }
+
+void HexDump::Reset()
+ {
+ memset(iChar,' ',HexInterval+3);
+ iChar[HexInterval+3]=0;
+ iPtr=&iChar[2];
+ }
+
+void HexDump::Dump(istream& aStream,int aBytes)
+ {
+ while (--aBytes>=0 && !aStream.eof())
+ {
+ if (iByte==0)
+ cout << Margin << FileOffset(iPos) << ':';
+ int c=aStream.get();
+ cout << ' ' << setw(2) << c;
+ *iPtr++=char(isprint(c) ? c : '.');
+ if (++iByte==HexInterval/2)
+ {
+ cout << ' ';
+ ++iPtr;
+ }
+ else if (iByte==HexInterval)
+ Flush();
+ }
+ }
+
+void HexDump::Flush()
+ {
+ if (iByte)
+ {
+ for (int ii=iByte;ii<HexInterval;++ii)
+ cout << " ";
+ if (iByte<HexInterval/2)
+ cout << ' ';
+ cout << iChar << endl;
+ iPos+=iByte;
+ iByte=0;
+ Reset();
+ }
+ }
+
+// Toc
+
+Toc::Toc()
+ : iPos(0), iRep(NULL), iAvail(0)
+ {
+ memset(&iHeader,0,sizeof(iHeader));
+ }
+
+Toc::~Toc()
+ {
+ free(iRep);
+ }
+
+
+void Toc::Base(const char* aPtr,int aCount)
+ {
+ Entry* e=iRep;
+ for (int i=1;i<=aCount;++e,++i)
+ {
+ e->iHandle=i; // set the index part
+ memcpy((char*)e+Entry::BaseRedundant,aPtr,Entry::BaseSize);
+ aPtr+=Entry::BaseSize;
+ }
+ }
+
+void Toc::Load(istream& aStream,Frames const& aFrames,Frames::Iterator aFrame,Header::Reloc const* aReloc)
+ {
+ iPos = aFrame->iPos;
+
+ Stream toc1(aFrame);
+ void* toc = toc1.Load(aStream);
+ const char* p = reinterpret_cast<char*>(toc);
+ memcpy(&iHeader,p,Head::Size);
+ p+=Head::Size;
+ int n = iHeader.iCount;
+ if (n < 0)
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "corrupt toc" << endl;
+ return;
+ }
+ iRep=static_cast<Entry*>(malloc(n*sizeof(Entry)));
+ if (iRep==NULL)
+ Program::Abort("Out of memory");
+
+ if (iHeader.IsDelta())
+ {
+ // verify the delta header
+ memcpy(&iDelta,p,DeltaHead::Size);
+ p+=DeltaHead::Size;
+ int dn = iDelta.iCount;
+ if (toc1.Length() != Head::Size + DeltaHead::Size + dn * Entry::DeltaSize)
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "incomplete toc" << endl;
+ return;
+ }
+
+ // find the toc-base
+ FramePos tocbase(iDelta.iBase + Header::tocoffset);
+ if (aReloc && aReloc->iHandle.IsTocBase())
+ tocbase = aReloc->iPos;
+ Stream toc2(aFrames,tocbase);
+ if (!toc2.IsGood())
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "invalid toc-base address " << tocbase << endl;
+ return;
+ }
+ if (toc2.Type()!=FrameDes::Toc)
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "toc-base address " << tocbase << ": refers to non-toc frame"<< endl;
+ return;
+ }
+
+ // validate and load the toc-base
+ void* tocb = toc2.Load(aStream);
+ const char* p2 = reinterpret_cast<char*>(tocb);
+ Head headbase;
+ memcpy(&headbase,p2,Head::Size);
+ p2+=Head::Size;
+ if (headbase.IsDelta())
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "toc-base is a toc-delta"<< endl;
+ return;
+ }
+ int bn = headbase.iCount;
+ if (bn > n)
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "toc-base is larger than toc"<< endl;
+ return;
+ }
+ Base(p2,bn);
+ free(tocb);
+
+ // validate and update with the toc-delta
+ int last = 0;
+ while (--dn>=0)
+ {
+ Entry e;
+ memcpy(&e,p,Entry::DeltaSize);
+ p+=Entry::DeltaSize;
+ int ix = e.iHandle.Index();
+ if (ix<=0 || ix > n)
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "toc-delta entry " << e.iHandle << " is outside toc"<< endl;
+ return;
+ }
+ if (ix <= last)
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "toc-delta entry " << e.iHandle << " is out of order"<< endl;
+ return;
+ }
+ iRep[ix-1] = e;
+ last = ix;
+ }
+ }
+ else
+ {
+ if (toc1.Length() != Head::Size + n * Entry::BaseSize)
+ {
+ memset(&iHeader,0,Head::Size);
+ Program::Error() << "incomplete toc" << endl;
+ return;
+ }
+ Base(p,n);
+ }
+ free(toc);
+
+ // apply the relocation
+ if (aReloc && !aReloc->iHandle.IsTocBase())
+ {
+ int ix=aReloc->iHandle.Index();
+ if (ix<=0 || ix>n)
+ Program::Corrupt("invalid index in relocation patch");
+
+ Entry& e=iRep[ix-1];
+ if (e.iHandle.Generation()!=aReloc->iHandle.Generation())
+ Program::Corrupt("incorrect generation in relocation patch");
+ e.iPos=aReloc->iPos.Pos();
+ }
+
+ // count the available entries
+ int avail=0;
+ for (int i=0;i<n;++i)
+ {
+ if (iRep[i].iHandle.Avail())
+ ++avail;
+ }
+ iAvail=avail;
+
+// verify the available list
+ Handle link=iHeader.iAvail;
+ if (!link.IsNull())
+ {
+ int ix=link.Index();
+ if (!link.Avail() || ix<=0 || ix >iHeader.iCount)
+ {
+eAvail: Program::Error() << "corrupt available link in toc header " << link << endl;
+ return;
+ }
+ Entry const* en=&(*this)[ix];
+ if (en->iHandle!=link)
+ goto eAvail;
+ for (;;)
+ {
+ if (--avail<0)
+ {
+ Program::Error() << "corrupt available list, possible circular reference" << endl;
+ return;
+ }
+ Handle next=en->Link();
+ if (next.IsNull())
+ break;
+ ix=next.Index();
+ if (!next.Avail() || ix<=0 || ix >iHeader.iCount)
+ {
+eLink: Program::Error() << "corrupt link in toc entry " << link << endl;
+ return;
+ }
+ en=&(*this)[ix];
+ if (en->iHandle!=next)
+ goto eLink;
+ link=next;
+ }
+ }
+ if (avail!=0)
+ Program::Error() << "corrupt available list: free index leakage" << endl;
+ }
+
+ostream& operator<<(ostream& aStream,Toc const& aToc)
+ {
+ int all=TheProgram.Option(Program::frame);
+
+ Toc::Head const& head=aToc.iHeader;
+ if (TheProgram.Option(Program::compare))
+ aStream << "Toc: ";
+ else
+ aStream << "Toc at " << aToc.iPos << " with ";
+ aStream << dec << head.iCount << (head.iCount==1 ? " entry: " : " entries: ") \
+ << head.iCount-aToc.iAvail << " allocated, " << aToc.iAvail << " free\n" << hex;
+ if (!all)
+ aStream << "root is " << head.Root() << '\n';
+ else
+ {
+ aStream << "first available is " << head.iAvail << '\n';
+ Toc::Iterator const end=aToc.End();
+ for (Toc::Iterator iter=aToc.Begin();iter<end;++iter)
+ {
+ aStream << (iter->iHandle==head.Root() ? "* " : Margin) << iter->iHandle;
+ if (iter->iHandle.Avail())
+ aStream << " free -> " << iter->Link() << '\n';
+ else if (iter->Pos().Pos()==-1)
+ aStream << " empty\n";
+ else
+ aStream << " alloc at " << iter->Pos() << '\n';
+ }
+ }
+ return aStream << flush;
+ }
+
+// Class Stream
+
+int Stream::Length() const
+ {
+ int total=0;
+ Frames::Iterator f=iFrame;
+ do {
+ int len=f->iDes.Length();
+ if (len==0)
+ len=f[1].iPos.Pos()-f[0].iPos.Pos();
+ total+=len;
+ } while ((++f)->iDes.Type()==FrameDes::Continuation);
+ return total;
+ }
+
+void* Stream::Load(istream& aStream) const
+ {
+ int size = Length();
+ void* data = malloc(size);
+ if (data==NULL)
+ Program::Abort("Out of memory");
+
+ char* read=reinterpret_cast<char*>(data);
+ Frames::Iterator f = iFrame;
+ do
+ {
+ FramePos pos=f->iPos;
+ int len=f++->iDes.Length();
+ if (len==0)
+ len=f->iPos.Pos()-pos.Pos();
+ aStream.seekg(FileOffset(pos).Offset(),ios::beg);
+ aStream.read(read,len);
+ read+=len;
+ } while (f->iDes.Type()==FrameDes::Continuation);
+
+ return data;
+ }
+
+// Class Frames
+
+Frames::Frames()
+ : iSize(0),iElements(0),iRep(NULL)
+ {}
+
+Frames::~Frames()
+ {
+ free(iRep);
+ }
+
+void Frames::Add(FramePos aPos,FrameDes aDes)
+ {
+ if (iElements==iSize)
+ {
+ iSize=iSize==0 ? 128 : iSize+iSize;
+ void* rep=realloc(iRep,iSize*sizeof(*iRep));
+ if (rep==NULL)
+ Program::Abort("Out of memory");
+ iRep=(Element*)rep;
+ }
+ Element& element=iRep[iElements++];
+ element.iPos=aPos;
+ element.iDes=aDes;
+ }
+
+void Frames::Complete()
+//
+// add a terminating entry
+//
+ {
+ Add(0,0);
+ --iElements;
+ }
+
+Frames::Iterator Frames::Find(FramePos aPos) const
+ {
+ return (Element const*)bsearch(&aPos,iRep,iElements,sizeof(*iRep),Compare);
+ }
+
+int Frames::Compare(void const* aLeft,void const* aRight)
+ {
+ int left=static_cast<FramePos const*>(aLeft)->Pos();
+ int right=static_cast<Element const*>(aRight)->iPos.Pos();
+ if (left<right)
+ return -1;
+ if (left>right)
+ return 1;
+ return 0;
+ }
+
+// Header
+
+FramePos Header::Toc() const
+ {
+ return tocoffset+(!Dirty() && iToc.iZero==0 ? iToc.iPos : iBackupToc>>backupshift);
+ }
+
+Header::Reloc const* Header::GetReloc() const
+ {
+ return (Dirty() || iToc.iZero==0) ? NULL : reinterpret_cast<Reloc const*>(&iReloc);
+ }
+
+ostream& operator<<(ostream& aStream,Header const& aHeader)
+ {
+ aStream << "Header is " << (aHeader.Dirty() ? "dirty" : "clean");
+ Header::Reloc const* reloc=aHeader.GetReloc();
+ if (reloc!=NULL)
+ {
+ aStream << "\npending relocation of ";
+ if (reloc->iHandle.IsTocBase())
+ aStream << "toc-base";
+ else
+ aStream << "stream " << StreamId(reloc->iHandle);
+ aStream << " to " << reloc->iPos;
+ }
+ return aStream << "\n\n" << flush;
+ }
+
+// FileOffset
+
+FileOffset::FileOffset(FramePos aPos)
+// calculate the file offset for a streampos
+ {
+ int pos=aPos.Pos();
+ int n=pos>>FrameDes::FullShift;
+ pos+=FrameDes::Size*n+FrameDes::First;
+ iValue=pos;
+ }
+
+FileOffset::operator FramePos() const
+ {
+ int pos=iValue-FrameDes::First;
+ int n=pos/FrameDes::Interval;
+ pos-=n*FrameDes::Size;
+ return FramePos(pos);
+ }
+
+ostream& operator<<(ostream& aStream,FileOffset anOffset)
+ {
+ return aStream << setw(StoreFile::OutWidth) << anOffset.iValue;
+ }
+
+// Handle
+
+ostream& operator<<(ostream& aStream,Handle aHandle)
+ {
+ if (aHandle.IsNull())
+ aStream << "Null";
+ else
+ aStream << setw(6) << aHandle.Index() << ':' << aHandle.Generation();
+ return aStream;
+ }
+
+// FramePos
+
+ostream& operator<<(ostream& aStream,FramePos aPos)
+ {
+ return aStream << setw(StoreFile::OutWidth) << aPos.iValue << '[' << FileOffset(aPos) << ']';
+ }
+
+// FrameDes
+
+istream& operator>>(istream& aStream,FrameDes& aFrame)
+ {
+ return aStream.read((char*)&aFrame,FrameDes::Size);
+ }
+
+ostream& operator<<(ostream& aStream,FrameDes aFrame)
+ {
+ static char const* FrameType[]={"free","data","toc","continuation"};
+ aStream << FrameType[aFrame.Type()] << " (";
+ int length=aFrame.Length();
+ if (length==0)
+ aStream << "full";
+ else
+ aStream << dec << length << hex;
+ return aStream << ')';
+ }
+