using System; using System.Collections; using System.ComponentModel; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; namespace Huffman { /// /// Handles attempt to extract archive that is protected with password, /// by using wrong password. /// public delegate void WrongPasswordEventHandler(); /// /// Invoked from all xxxxWithProgress functions whenever another 1 percent /// of the function is done. /// public delegate void PercentCompletedEventHandler(); /// /// Implementing the Huffman shrinking algorithm. /// This algorithm was ment to be highly fast efficient, it's supports /// Data streams with size of up to 2^32 - 1 bytes. /// public class HuffmanAlgorithm: IDisposable { #region Internal classes //------------------------------------------------------------------------------- /// /// FrequencyTable build from bytes and their repeatition in the stream. /// this is achieved by using 2 arrays with the of same size. /// [Serializable] internal class FrequencyTable { /// /// Saves all the varies types of bytes (up to 256 ) found in a stream. /// public Byte[] FoundBytes; /// /// Saves the amount of times each byte in the stream apears. /// public uint[] Frequency; }//end of FrequencyTable class //------------------------------------------------------------------------------- /// /// This is a node that the HuffmanTree made of. /// It's used to translate bytes to bits sequence when archiving, /// and bits sequence to bytes when extracting. /// internal class TreeNode { #region Members public TreeNode /// Pointer to the left son. Lson=null, /// Pointer to the right son. Rson=null, /// Pointer to the parent of the node. Parent=null; /// /// The Byte value of a leaf, it is relevant only when the node is actualy a leaf. /// public Byte ByteValue; /// /// This is the frequency value of the node /// public ulong Value; #endregion }//End of TreeNode class //------------------------------------------------------------------------------- /// /// HuffmanTree is the iplementation of a Huffman algorithm tree. /// It's used to translate bytes to bits sequence when archiving, /// and bits sequence to bytes when extracting. /// internal class HuffmanTree { #region Members /// /// This array hold the value of a byte and it is as long as a frequency table. /// public readonly TreeNode[] Leafs; /// The frequency table to build the Huffman tree with. public readonly FrequencyTable FT; /// /// This holds nodes without parents; /// private ArrayList OrphanNodes= new ArrayList(); /// /// The root node in the tree to be build; /// public readonly TreeNode RootNode; #endregion /// Build a Huffman tree out of a frequency table. internal HuffmanTree(FrequencyTable FT) { ushort Length = (ushort)FT.FoundBytes.Length; this.FT= FT; Leafs = new TreeNode[Length]; if( Length > 1 ) { for(ushort i=0; i< Length; ++i) { Leafs[i]=new TreeNode(); Leafs[i].ByteValue = FT.FoundBytes[i]; Leafs[i].Value = FT.Frequency[i]; } OrphanNodes.AddRange(Leafs); RootNode = BuildTree(); } else {//No need to create a tree (only one node below rootnode) TreeNode TempNode = new TreeNode(); TempNode.ByteValue =FT.FoundBytes[0]; TempNode.Value = FT.Frequency[0]; RootNode = new TreeNode(); RootNode.Lson = RootNode.Rson =TempNode; } OrphanNodes.Clear(); OrphanNodes=null; } //------------------------------------------------------------------------------- /// /// This function build a tree from the frequency table /// /// The root of the tree. private TreeNode BuildTree() { TreeNode small, smaller, NewParentNode=null; /*stop when the tree is fully build( only one root )*/ while( OrphanNodes.Count > 1 ) { /*This will return the parent less nodes that thier value togather will *be the smallest one and remove them from the ArrayList*/ FindSmallestOrphanNodes(out smaller,out small); NewParentNode = new TreeNode(); NewParentNode.Value = small.Value + smaller.Value; NewParentNode.Lson = smaller; NewParentNode.Rson = small; smaller.Parent = small.Parent = NewParentNode; OrphanNodes.Add(NewParentNode); } //returning the root of the tree (always the last new parent) return NewParentNode; } //------------------------------------------------------------------------------- /// /// Finds the smallest and the 2nd smallest value orphan nodes /// and removes them them from the arraylist. /// /// The smallest node in the OrphanNodes list. /// The 2nd smallest node in the OrphanNodes list. private void FindSmallestOrphanNodes(out TreeNode Smallest, out TreeNode Small) { Smallest = Small = null; //Scanning backward ulong Tempvalue=18446744073709551614; TreeNode TempNode=null; int i, j=0; int ArrSize = OrphanNodes.Count-1; //scanning for the smallest value orphan node for(i= ArrSize ; i!=-1 ; --i ) { TempNode = (TreeNode)OrphanNodes[i]; if( TempNode.Value < Tempvalue ) { Tempvalue = TempNode.Value; Smallest = TempNode; j=i; } } OrphanNodes.RemoveAt(j); --ArrSize; Tempvalue=18446744073709551614; //scanning for the second smallest value orphan node for(i= ArrSize; i>-1 ; --i ) { TempNode = (TreeNode)OrphanNodes[i]; if( TempNode.Value < Tempvalue ) { Tempvalue = TempNode.Value; Small = TempNode; j=i; } } OrphanNodes.RemoveAt(j); } //------------------------------------------------------------------------------- }//end of HuffmanTree class //------------------------------------------------------------------------------- /// /// This is a stack of 8 bits (1 byte) /// uses to manipulate the bits of a stream(when been extracted or archived). /// It's pushing and poping acts more like a queue then a stack. /// internal struct BitsStack { /// /// The unit to write and read from a stream. /// public Byte Container; private byte Amount; /// /// Indicated if the stack unit is full with 8 bits. /// /// true if full, false if not. public bool IsFull() { return Amount==8; } /// /// Indicated if the stack unit is empty(0 bits). /// /// true if empty, false if not. public bool IsEmpty() { return Amount == 0; } /// /// Get the number of bits, currently located in the stack. /// /// Number of bits located in the stack. public Byte NumOfBits() { return Amount; } /// /// This function removes all the bits from the stack. /// public void Empty(){Amount=Container=0;} /// /// Push a bit to the left of the stack (Most significant bit). /// /// The stack must have at least 1 free bit slot. /// The bit to add the stack(true = 1, false = 0) /// /// When attempting to push a bit from a full stack. /// public void PushFlag(bool Flag) { if( Amount== 8)throw new Exception("Stack is full"); Container>>=1; if( Flag )Container|=128; ++Amount; } /// /// Pops a bit from the right of the stack (Least significant bit). /// /// /// The stack must'nt be empty this function called. /// /// When attempting to pop a bit from an empty stack. /// public bool PopFlag() { if( Amount == 0)throw new Exception("Stack is empty"); bool t= (Container & 1)!=0; --Amount; Container>>=1; return t; } /// /// Fill the stack with 8 bits. If the stack is full, the given byte will /// override the old bits. /// /// Byte(8 bits) to put in the current stack. public void FillStack(Byte Data) { Container = Data; Amount=8; } } //------------------------------------------------------------------------------- /// /// This is the file/stream header that attached to each archived file or stream at the begining. /// [Serializable] internal class FileHeader { /// The version of the archiving code. public readonly byte version; /// The frequency table of the archived data. public readonly FrequencyTable FT; /// The size of the data before archiving it. public readonly long OriginalSize; /// Number of extra bits added to the last byte of the data. public readonly byte ComplementsBits; /// Security key to the archived stream\file. public readonly ushort Key; /// /// Builds a new header that holds info about an archived file\stream. /// /// The version of the archiving program. /// The frequency table to rebuild the file from. /// The size of the file\stream before archiving it. /// /// Number of extra bits added to the last byte in the archived file. /// /// Key to gain access to the file\stream later. public FileHeader(Byte ver, FrequencyTable T, ref long OrgSize, byte BitsToFill, ushort PasswordKey) { version=ver; FT=T; OriginalSize=OrgSize; ComplementsBits=BitsToFill; Key = PasswordKey; } } #endregion //------------------------------------------------------------------------------- #region Members //------------------------------------------------------------------------------- /// /// This is a temporary array to sign where it's location in the /// BuildFrequencyTable function (the value is the location. /// private Byte[] ByteLocation = new Byte[256]; /// /// This array indicated if the byte with the value that correspond /// to the index of the array (0-255) was found or not in the stream. /// private bool[] IsByteExist; /// Holds the bytes that where found. private ArrayList BytesList = new ArrayList(); /// Holds the amount of repetitions of byte. private ArrayList AmountList = new ArrayList(); /// I use this list to write the reverse path to a Byte. private ArrayList BitsList = new ArrayList(); /// Uses to write and read the Headers to and from a stream. private BinaryFormatter BinFormat = new BinaryFormatter(); /// This stack is used to write extracted and shrinked bytes. private BitsStack Stack = new BitsStack(); /// /// Invoked whenever attempt to extract password protected file\stream, by /// using the wrong password. In case this event isn't handaled by the users /// an exeption will be thrown(in password error case). /// private WrongPasswordEventHandler OnWrongPassword; /// /// Invoked from all xxxxWithProgress functions whenever another 1 percent /// of the function is done. /// private PercentCompletedEventHandler OnPercentCompleted; #endregion //------------------------------------------------------------------------------- #region Public Functions //------------------------------------------------------------------------------- /// /// Build a frequency table and Huffman tree and shrinking the stream data. /// /// /// The data streem to shrink, it will start shrinking from the position of the given /// stream as it was given and in the end of the function it's position /// won't be at the end of the stream and it won't be closed. /// /// /// A password to add to the archive, to mark as "password less" assign null instead. /// /// The archived stream, positioned at start. /// /// The given stream must be readable, seekable and it's length /// must exceed zero. /// public Stream Shrink(Stream Data, char[] Password) { //Tempdirectory String TempDir=Environment.GetEnvironmentVariable("temp"); //Generating the header data from the stream and creating a HuffmanTree HuffmanTree HT = new HuffmanTree( BuildFrequencyTable(Data) ); //Creating temporary file FileStream tempFS= new FileStream( TempDir + @"\TempArch.tmp", FileMode.Create); //Writing header WriteHeader(tempFS, HT.FT, Data.Length, 11, GetComplementsBits(HT), Password ); long DataSize= Data.Length; TreeNode TempNode=null; Byte Original; //the byte we read from the original stream short j; int k; for(long i=0;i< DataSize; ++i) { Original = (Byte)Data.ReadByte(); TempNode = HT.Leafs[ ByteLocation[Original] ]; while( TempNode.Parent!=null ) { //If I'm left sone of my parent push 1 else push 0 BitsList.Add(TempNode.Parent.Lson == TempNode); TempNode = TempNode.Parent;//Climb up the tree. } BitsList.Reverse(); k = BitsList.Count; for(j=0; j /// Build a frequency table and Huffman tree and shrinking the stream data. /// This function version, calls the PercentComplete event handler /// When anothe 1 percent compleated. /// /// /// The data streem to shrink, shrinking starts from the position of the given stream /// as it was given and in the end of the function it's position won't be at the end /// of the stream and it won't be closed. /// /// /// A password to add to the archive, to mark as "password less" assign null instead. /// /// The archived stream, positioned at start. /// /// The given stream must be readable, seekable and it's length /// must exceed zero. /// public Stream ShrinkWithProgress(Stream Data, char[] Password) { //Tempdirectory String TempDir=Environment.GetEnvironmentVariable("temp"); //Generating the header data from the stream and creating a HuffmanTree HuffmanTree HT = new HuffmanTree( BuildFrequencyTable(Data) ); //Creating temporary file FileStream tempFS= new FileStream( TempDir + @"\TempArch.tmp", FileMode.Create); //Writing header WriteHeader(tempFS, HT.FT, Data.Length, 11, GetComplementsBits(HT), Password ); long DataSize= Data.Length; TreeNode TempNode=null; Byte Original; //the byte we read from the original stream short j; int k; float ProgressRatio = 0; for(long i=0;i< DataSize; ++i) { Original = (Byte)Data.ReadByte(); TempNode = HT.Leafs[ ByteLocation[Original] ]; while( TempNode.Parent!=null ) { //If I'm left sone of my parent push 1 else push 0 BitsList.Add(TempNode.Parent.Lson == TempNode); TempNode = TempNode.Parent;//Climb up the tree. } BitsList.Reverse(); k = BitsList.Count; for(j=0; j 0.01 ) { ProgressRatio+=0.01f; if( OnPercentCompleted!=null ) OnPercentCompleted(); } } //Writing the last byte if the stack wasn't compleatly full. if( !Stack.IsEmpty() ) { Byte BitsToComplete = (Byte)(8 - Stack.NumOfBits()); for(byte Count=0; Count< BitsToComplete; ++Count)//complete to full 8 bits Stack.PushFlag(false); tempFS.WriteByte(Stack.Container); Stack.Empty(); } tempFS.Seek( 0, SeekOrigin.Begin ); return tempFS; } //------------------------------------------------------------------------------- /// /// Build a frequency table and Huffman tree and shrinking the stream data to a new file. /// into a file. /// /// /// The data streem to shrink, it will start shrinking from the position of the given /// stream as it was given and in the end of the function it's position /// won't be at the end of the stream and it won't be closed. /// /// Path to a file to same the shrinked data in. /// /// A passward to add to the archive, to mark as "passward less" assign null instead. /// /// The expanded stream, positioned at start. /// /// The given stream must be readable, seekable and it's length /// must exceed zero. /// public void Shrink(Stream Data, string OutputFile, char [] Password) { //Generating the header data from the stream and creating a HuffmanTree HuffmanTree HT = new HuffmanTree( BuildFrequencyTable(Data) ); //Creating temporary file FileStream tempFS= new FileStream( OutputFile , FileMode.Create); //Writing header WriteHeader(tempFS, HT.FT, Data.Length, 11, GetComplementsBits(HT), Password); long DataSize= Data.Length; TreeNode TempNode=null; Byte Original; //the byte we read from the original stream short j; int k; for(long i=0;i< DataSize; ++i) { Original = (Byte)Data.ReadByte(); TempNode = HT.Leafs[ ByteLocation[Original] ]; while( TempNode.Parent!=null ) { //If I'm left sone of my parent push 1 else push 0 BitsList.Add(TempNode.Parent.Lson == TempNode); TempNode = TempNode.Parent;//Climb up the tree. } BitsList.Reverse(); k = BitsList.Count; for(j=0; j /// Build a frequency table and Huffman tree and shrinking the stream data to a new file. /// into a file. /// This function version, calls the PercentComplete event handler /// When anothe 1 percent compleated. /// /// /// The data streem to shrink, shrinking starts from the position of the given stream /// as it was given and in the end of the function it's position won't be at the end /// of the stream and it won't be closed. /// /// Path to a file to same the shrinked data in. /// /// A passward to add to the archive, to mark as "passward less" assign null instead. /// /// The expanded stream, positioned at the start. /// /// The given stream must be readable, seekable and it's length /// must exceed zero. /// public void ShrinkWithProgress(Stream Data, string OutputFile, char [] Password) { //Generating the header data from the stream and creating a HuffmanTree HuffmanTree HT = new HuffmanTree( BuildFrequencyTable(Data) ); //Creating temporary file FileStream tempFS= new FileStream( OutputFile , FileMode.Create); //Writing header WriteHeader(tempFS, HT.FT, Data.Length, 11, GetComplementsBits(HT), Password); long DataSize= Data.Length; TreeNode TempNode=null; Byte Original; //the byte we read from the original stream short j; int k; float ProgressRatio = 0; for(long i=0;i< DataSize; ++i) { Original = (Byte)Data.ReadByte(); TempNode = HT.Leafs[ ByteLocation[Original] ]; while( TempNode.Parent!=null ) { //If I'm left sone of my parent push 1 else push 0 BitsList.Add(TempNode.Parent.Lson == TempNode); TempNode = TempNode.Parent;//Climb up the tree. } BitsList.Reverse(); k = BitsList.Count; for(j=0; j 0.01 ) { ProgressRatio+=0.01f; if( OnPercentCompleted!=null ) OnPercentCompleted(); } } //Writing the last byte if the stack wasn't compleatly full. if( !Stack.IsEmpty() ) { Byte BitsToComplete = (Byte)(8 - Stack.NumOfBits()); for(byte Count=0; Count< BitsToComplete; ++Count)//complete to full 8 bits Stack.PushFlag(false); tempFS.WriteByte(Stack.Container); Stack.Empty(); } tempFS.Seek( 0, SeekOrigin.Begin ); tempFS.Close(); } //------------------------------------------------------------------------------- /// /// Build a frequency table and Huffman tree and extract the archive. /// /// The data streem to shrink /// /// A Key to open the archive with, to mark as "passward less" assign null instead. /// /// /// The data stream to extract, but if wrong password error occur /// and the WrongPassword event is handeled, it's returns null. /// /// /// On attempt to extract data that wasn't coded with the HuffmanAlgorithm class, /// Or on attempt to extract a password protected stream\file with the wrong password. /// (If the WrongPassword event is been handeled by the user the 2nd exception is'nt /// relevant). /// /// /// The given stream must be readable, seekable and it's length /// must exceed zero. The given stream must be archived stream of the right type. /// public Stream Extract(Stream Data, char[] Password) { Data.Seek(0, SeekOrigin.Begin); //Tempdirectory String TempDir=Environment.GetEnvironmentVariable("temp"); FileHeader Header; //Reading the header data from the stream if( !IsArchivedStream(Data) ) throw new Exception("The given stream is't Huffmans algorithm archive type."); Header = (FileHeader)BinFormat.Deserialize(Data); //If the stream\file protected and password doesn't fit if(Header.Key !=0 && Header.Key != PasswordGen( Password )) { //Invoke if user handles it if(OnWrongPassword!=null) { new Thread( new ThreadStart(OnWrongPassword)).Start(); return null; } else throw new Exception("Wrong password error, on attempt to extract protected data."); } //Gernerating Huffman tree out of the frequency table in the header HuffmanTree HT = new HuffmanTree( Header.FT ); //Creating temporary file FileStream tempFS= new FileStream( TempDir + @"\TempArch.tmp", FileMode.Create); BitsStack Stack = new BitsStack(); long DataSize= Data.Length - Data.Position; if( Header.ComplementsBits==0 )DataSize+=1; TreeNode TempNode=null; while( true ) { TempNode = HT.RootNode; //As long it's not a leaf, go down the tree while(TempNode.Lson!=null && TempNode.Rson!=null) { //If the stack is empty refill it. if( Stack.IsEmpty() ) { Stack.FillStack( (Byte)Data.ReadByte() ); if( (--DataSize) == 0 ) { goto AlmostDone; } } //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); }//end of while //To this lable u can jump only from the while loop (only one byte left). AlmostDone: short BitsLeft = (Byte)( Stack.NumOfBits() - Header.ComplementsBits); //Writing the rest of the last byte. if(BitsLeft != 8 ) { bool Test = TempNode.Lson==null && TempNode.Rson==null; while( BitsLeft > 0) { //If at itteration, TempNode not done going down the huffman tree. if( Test ) TempNode = HT.RootNode; while(TempNode.Lson!=null && TempNode.Rson!=null) { //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; --BitsLeft; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); Test=true; } } tempFS.Seek( 0, SeekOrigin.Begin ); return tempFS; } //------------------------------------------------------------------------------- /// /// Build a frequency table and Huffman tree and extract the archive. /// This function version, calls the PercentComplete event handler /// When anothe 1 percent compleated. /// /// The data streem to shrink /// /// A Key to open the archive with, to mark as "passward less" assign null instead. /// /// /// The data stream to extract, but if wrong password error occur /// and the WrongPassword event is handeled, it's returns null. /// /// /// On attempt to extract data that wasn't coded with the HuffmanAlgorithm class, /// Or on attempt to extract a password protected stream\file with the wrong password. /// (If the WrongPassword event is been handeled by the user the 2nd exception is'nt /// relevant). /// /// /// The given stream must be readable, seekable and it's length /// must exceed zero. The given stream must be archived stream of the right type. /// public Stream ExtractWithProgress(Stream Data, char[] Password) { Data.Seek(0, SeekOrigin.Begin); //Tempdirectory String TempDir=Environment.GetEnvironmentVariable("temp"); FileHeader Header; //Reading the header data from the stream if( !IsArchivedStream(Data) ) throw new Exception("The given stream is't Huffmans algorithm archive type."); Header = (FileHeader)BinFormat.Deserialize(Data); //If the stream\file protected and password doesn't fit if(Header.Key !=0 && Header.Key != PasswordGen( Password )) { //Invoke if user handles it if(OnWrongPassword!=null) { new Thread( new ThreadStart(OnWrongPassword)).Start(); return null; } else throw new Exception("Wrong password error, on attempt to extract protected data."); } //Gernerating Huffman tree out of the frequency table in the header HuffmanTree HT = new HuffmanTree( Header.FT ); //Creating temporary file FileStream tempFS= new FileStream( TempDir + @"\TempArch.tmp", FileMode.Create); BitsStack Stack = new BitsStack(); long DataSize= Data.Length - Data.Position; if( Header.ComplementsBits==0 )DataSize+=1; TreeNode TempNode=null; long DataSize2 = DataSize; float ProgressRatio=0;//Needed to calculate progress. while( true ) { TempNode = HT.RootNode; //As long it's not a leaf, go down the tree while(TempNode.Lson!=null && TempNode.Rson!=null) { //If the stack is empty refill it. if( Stack.IsEmpty() ) { Stack.FillStack( (Byte)Data.ReadByte() ); if( (--DataSize) == 0 ) { goto AlmostDone; } } //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); if( ((float)(DataSize2-DataSize))/DataSize2 - ProgressRatio > 0.01 ) { ProgressRatio+=0.01f; if( OnPercentCompleted!=null ) OnPercentCompleted(); } }//end of while //To this lable u can jump only from the while loop (only one byte left). AlmostDone: short BitsLeft = (Byte)( Stack.NumOfBits() - Header.ComplementsBits); //Writing the rest of the last byte. if(BitsLeft != 8 ) { bool Test = TempNode.Lson==null && TempNode.Rson==null; while( BitsLeft > 0) { //If at itteration, TempNode not done going down the huffman tree. if( Test ) TempNode = HT.RootNode; while(TempNode.Lson!=null && TempNode.Rson!=null) { //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; --BitsLeft; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); Test=true; } } tempFS.Seek( 0, SeekOrigin.Begin ); return tempFS; } //------------------------------------------------------------------------------- /// /// Build a frequency table and Huffman tree and extract the archive to a new file. /// /// The data streem to shrink /// Path to to save the extracted data. /// /// A Key to open the archive with, to mark as "passward less" assign null instead. /// /// /// flag that indicates if extraction went well or not: true = successful, /// false = wrong password error occured (the WrongPassword event will take place). /// /// /// On attempt to extract data that wasn't coded with the HuffmanAlgorithm class, /// Or on attempt to extract a password protected stream\file with the wrong password. /// (If the WrongPassword event is been handeled by the user the 2nd exception is'nt /// relevant). /// /// /// The given stream must be readable, seekable and it's length /// must exceed zero. The given stream must be archived stream of the right type. /// public bool Extract(Stream Data, string OutputFile, char[] Password) { Data.Seek(0, SeekOrigin.Begin); FileHeader Header; //Reading the header data from the stream if( !IsArchivedStream(Data) ) throw new Exception("The given stream is't my Huffman algorithm type."); Header = (FileHeader)BinFormat.Deserialize(Data); //If the stream\file protected and password doesn't fit if(Header.Key !=0 && Header.Key != PasswordGen( Password )) { //Invoke if user handles it if(OnWrongPassword!=null) { new Thread( new ThreadStart(OnWrongPassword)).Start(); return false; } else throw new Exception("Wrong password error, on attempt to extract protected data."); } //Gernerating Huffman tree out of the frequency table in the header HuffmanTree HT = new HuffmanTree( Header.FT ); //Creating temporary file FileStream tempFS= new FileStream(OutputFile, FileMode.Create); BitsStack Stack = new BitsStack(); long DataSize= Data.Length - Data.Position; if( Header.ComplementsBits==0 )DataSize+=1; TreeNode TempNode=null; while( true ) { TempNode = HT.RootNode; //As long it's not a leaf, go down the tree while(TempNode.Lson!=null && TempNode.Rson!=null) { //If the stack is empty refill it. if( Stack.IsEmpty() ) { Stack.FillStack( (Byte)Data.ReadByte() ); if( (--DataSize) == 0 ) { goto AlmostDone; } } //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); }//end of while //To this lable u can jump only from the while loop (only one byte left). AlmostDone: short BitsLeft = (Byte)( Stack.NumOfBits() - Header.ComplementsBits); //Writing the rest of the last byte. if(BitsLeft != 8 ) { bool Test = TempNode.Lson==null && TempNode.Rson==null; while( BitsLeft > 0) { //If at itteration, TempNode not done going down the huffman tree. if( Test ) TempNode = HT.RootNode; while(TempNode.Lson!=null && TempNode.Rson!=null) { //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; --BitsLeft; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); Test=true; } } tempFS.Close(); return true; } //------------------------------------------------------------------------------- /// /// Build a frequency table and Huffman tree and extract the archive to a new file. /// This function version, calls the PercentComplete event handler /// When anothe 1 percent compleated. /// /// The data streem to shrink /// Path to to save the extracted data. /// /// A Key to open the archive with, to mark as "passward less" assign null instead. /// /// /// flag that indicates if extraction went well or not: true = successful, /// false = wrong password error occured (the WrongPassword event will take place). /// /// /// On attempt to extract data that wasn't coded with the HuffmanAlgorithm class, /// Or on attempt to extract a password protected stream\file with the wrong password. /// (If the WrongPassword event is been handeled by the user the 2nd exception is'nt /// relevant). /// /// /// The given stream must be readable, seekable and it's length /// must exceed zero. The given stream must be archived stream of the right type. /// public bool ExtractWithProgress(Stream Data, string OutputFile, char[] Password) { Data.Seek(0, SeekOrigin.Begin); FileHeader Header; //Reading the header data from the stream if( !IsArchivedStream(Data) ) throw new Exception("The given stream is't my Huffman algorithm type."); Header = (FileHeader)BinFormat.Deserialize(Data); //If the stream\file protected and password doesn't fit if(Header.Key !=0 && Header.Key != PasswordGen( Password )) { //Invoke if user handles it if(OnWrongPassword!=null) { new Thread( new ThreadStart(OnWrongPassword)).Start(); return false; } else throw new Exception("Wrong password error, on attempt to extract protected data."); } //Gernerating Huffman tree out of the frequency table in the header HuffmanTree HT = new HuffmanTree( Header.FT ); //Creating temporary file FileStream tempFS= new FileStream(OutputFile, FileMode.Create); BitsStack Stack = new BitsStack(); long DataSize= Data.Length - Data.Position; if( Header.ComplementsBits==0 )DataSize+=1; TreeNode TempNode=null; long DataSize2 = DataSize; float ProgressRatio=0;//Needed to calculate progress. while( true ) { TempNode = HT.RootNode; //As long it's not a leaf, go down the tree while(TempNode.Lson!=null && TempNode.Rson!=null) { //If the stack is empty refill it. if( Stack.IsEmpty() ) { Stack.FillStack( (Byte)Data.ReadByte() ); if( (--DataSize) == 0 ) goto AlmostDone; } //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); if( ((float)(DataSize2-DataSize))/DataSize2 - ProgressRatio > 0.01 ) { ProgressRatio+=0.01f; if( OnPercentCompleted!=null ) OnPercentCompleted(); } }//end of while //To this lable u can jump only from the while loop (only one byte left). AlmostDone: short BitsLeft = (Byte)( Stack.NumOfBits() - Header.ComplementsBits); //Writing the rest of the last byte. if(BitsLeft != 8 ) { bool Test = TempNode.Lson==null && TempNode.Rson==null; while( BitsLeft > 0) { //If at itteration, TempNode not done going down the huffman tree. if( Test ) TempNode = HT.RootNode; while(TempNode.Lson!=null && TempNode.Rson!=null) { //Going left or right according to the bit TempNode = Stack.PopFlag() ? TempNode.Lson : TempNode.Rson; --BitsLeft; } //By now reached for a leaf and writes it's data. tempFS.WriteByte( TempNode.ByteValue ); Test=true; } } tempFS.Close(); return true; } //------------------------------------------------------------------------------- /// /// Checks if a data stream is archived. /// /// The stream to test. /// true if the stream is archive, false if not. public bool IsArchivedStream(Stream Data) { Data.Seek(0, SeekOrigin.Begin); bool test = true; try { FileHeader Header = (FileHeader)BinFormat.Deserialize(Data); Header=null; } catch(Exception) { //if header wasn't found test = false; } finally { Data.Seek(0, SeekOrigin.Begin); } return test; } //------------------------------------------------------------------------------- /// /// Checks if a given archived data stream is password protected. /// /// Archived stream to test. /// true if the stream is password protected, false if not. /// /// When the given stream isn't correct Huffman archived stream or has been corrupted. /// public bool IsPasswardProtectedStream(Stream Data) { Data.Seek(0, SeekOrigin.Begin); bool test = true; try { FileHeader Header = (FileHeader)BinFormat.Deserialize(Data); test = (Header.Key != 0); Header=null; } catch(Exception) { //if header wasn't found throw new Exception("Error, the given stream isn't Huffman archived or corrupted."); } finally { Data.Seek(0, SeekOrigin.Begin); } return test; } //------------------------------------------------------------------------------- /// /// This function calculates the the archiving ratio of a given archived stream. /// /// Archived stream to calculate archiving ratio to. /// The archiving ratio. /// /// When the given stream isn't correct Huffman archived stream or has been corrupted. /// public float GetArchivingRatio(Stream Data) { Data.Seek(0, SeekOrigin.Begin); float Result; try { FileHeader Header = (FileHeader)BinFormat.Deserialize(Data); Result = (100f*Data.Length)/Header.OriginalSize; Header=null; } catch(Exception) { //if header wasn't found throw new Exception("Error, the given stream isn't Huffman archived or corrupted."); } finally { Data.Seek(0, SeekOrigin.Begin); } return Result; } #endregion //------------------------------------------------------------------------------- #region Public events /// /// This is Asynchronic event and accures when the Extract function returns /// on wrong password error. /// Invoked whenever attempt to extract password protected file\stream, by /// using the wrong password(Fatal error). In case this event isn't handaled by the users /// an exeption will be thrown(in password error case). /// [Description("Invoked whenever attempt to extract password protected file\\stream " + "by using the wrong password. In case this event isn't handaled by the users "+ "an exeption will be thrown(in password error case).") ] [Category("Behavior")] public event WrongPasswordEventHandler WrongPassword { add{OnWrongPassword+=value;} remove{OnWrongPassword-=value;} } //------------------------------------------------------------------------------- /// /// This is Asynchronic event and invoked only from xxxxWithProgress functions. /// Invoked whenever another 1 percent of the function is done. /// [Description("This is Asynchronic event and invoked only from xxxxWithProgress "+ "functions. Invoked whenever another 1 percent of the function is done.") ] [Category("Action")] public event PercentCompletedEventHandler PercentCompleted { add{OnPercentCompleted+=value;} remove{OnPercentCompleted-=value;} } #endregion //------------------------------------------------------------------------------- #region Private Functions //------------------------------------------------------------------------------- /// /// Scanning for repeated bytes and according to them build frequency table. /// /// The stream to build FrequencyTable for. /// The generated frequency table. /// /// DataSource must be readable and seekable stream. /// Don't try to extract somthing smaller then 415 bytes(it's not worth it) /// private FrequencyTable BuildFrequencyTable(Stream DataSource) { long OriginalPosition = DataSource.Position; FrequencyTable FT = new FrequencyTable(); IsByteExist = new bool[256]; //false by default Byte bTemp; //Counting bytes and saving them for(long i=0; i /// This function takes a password cstring and converts it to a ushort number /// that's fit the header of a shrinked file. /// /// /// Password to a shrinked file (8 chars tops), is it's null, no password will /// take place in the file (zero value). /// /// A numeric representation of the given password. /// /// When a given password is longer then 8 characters. /// private ushort PasswordGen(char[] Password) { if(Password==null )return 0; if(Password.Length > 8) throw new Exception("Password's is 8 chars length tops, you've entered " + Password.Length + " bytes."); Byte Size = (byte)Password.Length; ushort Result=0; for(Byte i=0; i /// Bouble sort FrequencyTable( according frequency level ) /// and making the same changes on the corresponding array. /// /// The array to sort by. /// /// The array that will take the same swaping as the target array. /// private void SortArrays(uint[] SortTarget, Byte[] TweenArray, short size) { --size; bool TestSwitch=false; Byte BTemp; uint uiTemp; short i,j; for(i=0; i < size ; ++i ) { for(j=0; j < size ; ++j ) { if( SortTarget[j] < SortTarget[j+1] ) { TestSwitch = true;//Making switch action uiTemp = SortTarget[j]; SortTarget[j] = SortTarget[j+1]; SortTarget[j+1]=uiTemp; //Doing same to corresponding array BTemp = TweenArray[j]; TweenArray[j] = TweenArray[j+1]; TweenArray[j+1]=BTemp; } }//end of for if(!TestSwitch)break;//if no switch action in this round, no need for more. TestSwitch = false; }//end of for for(i=0;i /// Write a header to the stream. This header is vital when extracting the data. /// /// The output stream. /// The frequency table of the data. /// The original of the data before shrinking. /// The version of the shrinking code. /// Number of extra bits added to the last byte of the data. /// A key to gain access to the archived file\stream. private void WriteHeader(Stream St,FrequencyTable FT , long OriginalSize, Byte version, Byte ComplementsBits, char[] Password) { FileHeader Header = new FileHeader(version, FT, ref OriginalSize, ComplementsBits, PasswordGen(Password )); BinFormat.Serialize(St, Header); } //------------------------------------------------------------------------------- /// /// Calculates the amount of complements bits, needed for the last byte writing. /// /// The huffman tree of the stream to be archived. /// Amount of complements bits private Byte GetComplementsBits(HuffmanTree HT) { //Getting the deapth of each leaf in the huffman tree short i = (short)HT.Leafs.Length; ushort[] NodesDeapth=new ushort[i]; long SizeInOfBits=0; while( --i != -1 ) { TreeNode TN = HT.Leafs[i]; while( TN.Parent!=null ) { TN = TN.Parent; ++NodesDeapth[i]; } SizeInOfBits+=NodesDeapth[i]*HT.FT.Frequency[i]; } return (byte)( 8 - SizeInOfBits%8 ); } #endregion //------------------------------------------------------------------------------- #region IDisposable Members public void Dispose() { BytesList = null; IsByteExist=null; AmountList = null; BinFormat = null; BitsList=null; ByteLocation = null; OnWrongPassword = null; OnPercentCompleted=null; } #endregion //----------------------------------------------------------------------------- } }