public DATHistoryStream(Stream dataStream)
 {
     if (dataStream is ICQDataStream)
         innerDataStream = (ICQDataStream)dataStream;
     else
         innerDataStream = new ICQDataStream(dataStream);
 }
        protected override void parseInnerData(byte[] innerData)
        {
            ICQDataStream strmContent = new ICQDataStream(new MemoryStream(innerData, false));
            byte[] baPacketHead = strmContent.readFixedBinary(0x1a);
            isOutgoing = ((baPacketHead[0x16] & 0x01) == 0x01);

            while (strmContent.Position < strmContent.Length - 1)
            {
                string nextLabel = strmContent.readString();
                handleTag(nextLabel, strmContent);
            }
            //string answerFlagsLabel = readString(ms);   // "AnswerFlags"
            //handleTag(answerFlagsLabel, ms);

            //string bodyLabel = readString(ms);
            //handleTag(bodyLabel, ms);

            //string clsidLabel = readString(ms);
            //handleTag(clsidLabel, ms);

            //string extidLabel = readString(ms);
            //handleTag(extidLabel, ms);
        }
        private void parseICQ2003bBody(ICQDataStream strmBody)
        {
            byte[] baRTFText = strmBody.readBinary();
            if (null != baRTFText && baRTFText.Length > 0)
                TextRTF = Encoding.Default.GetString(baRTFText);

            byte[] baText = strmBody.readBinary();
            if (null != baText && baText.Length > 0)
                Text = Encoding.Default.GetString(baText);
        }
 private void parseICQ2003aBody(ICQDataStream strmBody)
 {
     byte[] baText = strmBody.readBinary();
     if (null != baText && baText.Length > 0)
     {
         Text = Encoding.Default.GetString(baText);
         string textUTF8Temp;
         string textRTFTemp;
         strmBody.parsePossiblyRemainingRTFandUTF8(out textRTFTemp, out textUTF8Temp);   // sometimes, textUTF8Temp is plaintext with spaces in between :-(
         TextRTF = textRTFTemp;  // TextRTF will be null before that operation anyway
         if (null != textUTF8Temp)
             Text = textUTF8Temp;
     }
 }
        private void handleTag(string tagName, ICQDataStream streamContent)
        {
            switch(tagName)
            {
                case "AnswerFlags":
                    byte[] baAnswerFlags = streamContent.readFixedBinary(5);
                    break;
                case "SendFlags":
                case "Status":
                case "Read":
                    byte[] baSendFlags = streamContent.readFixedBinary(5); // TODO: this is a special Uint format: First 0x69 then four bytes of uint
                    break;
                case "Body":
                    byte[] baBodyHeader = new byte[0x13];
                    streamContent.Read(baBodyHeader, 0, 0x3);

                    if (baBodyHeader[1] == 0xEB)    // as opposed to 0xEA
                    {
                        streamContent.Read(baBodyHeader, 3, 0x10);
                        //if (isOutgoing != ((baBodyHeader[4] & 0x01) == 0x01))
                        //    throw new InvalidDataException("Outgoing flag is different in packet head and body head");
                    }

                    byte[] bodyContent = streamContent.readBinary();
                    if (null != bodyContent)
                    {
                        // bodyContent contains 0x58 as start tag for only ANSI text (ICQ Pro 2003a) or RTF and ANSI (ICQ Pro 2003b)
                        ICQDataStream strmBody = new ICQDataStream(bodyContent);
                        if (0x58 == bodyContent[0])     // maybe bodyContent[0] and bodyContent[1] together are a package type number?
                        {
                            strmBody.Seek(2, SeekOrigin.Begin);
                            UInt32 iICQ2003bIndicator = strmBody.readUInt32();
                            if (0 != iICQ2003bIndicator)    // this means the RTF starts immediately. ICQ Pro 2003b style.
                            {
                                strmBody.Seek(-4, SeekOrigin.Current);
                                parseICQ2003bBody(strmBody);
                            }
                        }
                        else if (0x23 == bodyContent[0])    // this means:  ANSI + RTF + UTF-8
                            strmBody.Seek(2, SeekOrigin.Begin);
                        else if (0x03 == bodyContent[0])    // observed cases are only "The user has added you to his/her Contact list"
                                                            // + "You added him/her to your Contact List"
                        {
                            MessageType = "Added2ContactList";
                            return;
                        }
                        else if (0x01 == bodyContent[0])
                        {
                            MessageType = "AuthorizationRequestAccepted";
                            return;
                        }
                        else if (0x00 == bodyContent[0])
                        {
                            MessageType = "Contacts";
                            return;
                        }
                        else if (0x11 == bodyContent[0])
                        {
                            MessageType = "BirthdayReminder";
                            return;
                        }
                        else
                            throw new NotImplementedException("Message start tag " + bodyContent[0].ToString() + " not implemented");

                        parseICQ2003aBody(strmBody);
                    }

                    break;
                case "CLSID":
                    byte clsidStartTag = Convert.ToByte(streamContent.ReadByte());
                    byte[] clsidData = streamContent.readBinary();
                    break;
                case "ExtId":
                    byte[] extidContent = streamContent.readFixedBinary(3);
                    break;
                case "ExtName":
                    byte extName = Convert.ToByte(streamContent.ReadByte());
                    MessageType = streamContent.readString();
                    break;
                case "ExtVers":
                    byte[] extVers = streamContent.readFixedBinary( 5);
                    break;
                case "Folder":
                    byte[] folder = streamContent.readFixedBinary(3);
                    break;
                case "MsgUserName":
                    byte startTag = (byte)streamContent.ReadByte();
                    OtherPartyName = streamContent.readString();
                    break;
                case "Time":
                    byte timeStartTag = (byte)streamContent.ReadByte();
                    if (timeStartTag != 0x69)
                        throw new ArgumentException("Time tag expected to be 0x69, but it was " + timeStartTag.ToString());
                    TimeOfMessage = streamContent.readUnixTime();
                    break;
                default:
                    throw new NotImplementedException("Unknown tag \"" + tagName + "\"");
            }
        }
        private void parseStream(ICQDataStream streamContent)
        {
            UInt32 iMessageLength = streamContent.readUInt32();
            if (0 == iMessageLength || iMessageLength > 65536)
                throw new InvalidDataException("This is not a message packet. Unusal packet length: " + iMessageLength.ToString());

            byte[] strangeHeading = streamContent.readFixedBinary(0x1E);
            // Byte strangeHeading[0x1a], lowest bit seems to indicate incoming messages
            // strangeHeading is either 00 00 00 00 xx xx xx xx 50 3b c1 5c 5c 95 d3 11 8d d7 00 10 4b 06 46 2e xx 02 xx 00 00 00
            //                   or                             e0 23 a3 db df b8 d1 11 8a 65 00 60 08 71 a3 91
            byte[] comparisonBits = new byte[] {
                0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
                0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF
            };

            if (!byteArraysAreEqual(strangeHeading,
                new byte[]
                {
                    0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, // the latter four bytes are disregared because of comparisonBits
                    0x50, 0x3b, 0xc1, 0x5c, 0x5c, 0x95, 0xd3, 0x11,
                    0x8d, 0xd7, 0x00, 0x10, 0x4b, 0x06, 0x46, 0x2e,
                    0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00
                },
                comparisonBits)
                &&
                !byteArraysAreEqual(strangeHeading,
                new byte[]
                {
                    0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, // the latter four bytes are disregared because of comparisonBits
                    0xe0, 0x23, 0xa3, 0xdb, 0xdf, 0xb8, 0xd1, 0x11,
                    0x8a, 0x65, 0x00, 0x60, 0x08, 0x71, 0xa3, 0x91,
                    0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00
                },
                comparisonBits)
               )
                throw new InvalidDataException("This is not a message packet. The identifying bits do not match!");

            MessageNumber = BitConverter.ToUInt32(strangeHeading, 4);   // this number increases, although not linearly
            UInt16 SessionNumber = BitConverter.ToUInt16(strangeHeading, 24);   // this number also increases, but more slowly

            iMessageType = streamContent.readUInt16();
            UIN = streamContent.readUInt32();

            if (1 == iMessageType)
            {
                parseMessagePacket(streamContent);
                return;
            }

            if (0x0d == iMessageType)
            {
                // it's an internet message. The UIN will not be correct (I've seen 0x0a as UIN)
                parseMessagePacket(streamContent);
                return;
            }

            if (0x13 == iMessageType)
            {
                UInt16 contactListLength = streamContent.readUInt16();
                //byte[] contacts = streamContent.readFixedBinary(contactListLength);

                // this is a list of contacts. First comes the number of contacts in decimal.
                // Then, for each contact, the UIN and then the display name is stored.
                // Different contacts as well as UIN and display name and the number of contacts are separated with 0xFE.
                // Contact names are encoded in the current code page
                return;
            }

            if (0x04 == iMessageType)   // URL
            {
                parseMessagePacket(streamContent);  // Looks almost like a message, except that a 0xFE separates a custom message from the URL
                return;
            }

            if (0x0c == iMessageType)   // only seen once and then it was mostly empty
            {
                parseMessagePacket(streamContent);
                return;
            }

            if (0x0e == iMessageType)   // Email
            {
                parseMessagePacket(streamContent);  // contains some specials: 0xFEs separate Sender Displayname, ?, ?, Sender mail address, ?, Message with HTML tags
                return;
            }

            Debugger.Break();   // TODO: this is an interesting special case. Let's have a look at it!
        }
        private void parseMessagePacket(ICQDataStream streamContent)
        {
            Text = streamContent.readString();

            byte[] messageFlags = streamContent.readFixedBinary(0x0a);    // Mostly 0x00, but last two bytes are 0xC8 0x01(?), 0x23 0x02, or 0x19 0x02, or 0x03 0x02
            // the first four bytes seem to be ff ff ff ff in case of an SMS
            isOutgoing = ((messageFlags[4] & 0x01) == 0x01);

            TimeOfMessage = streamContent.readUnixTime();

            byte[] zeroes = streamContent.readFixedBinary(0x13);
            if (streamContent.Position > streamContent.Length - 8)
                return; // not enough to read anymore
            if (zeroes.Any(zeroByte => zeroByte != 0x00))
                return;     // The RTF messages are always zero in these bytes
            UInt16 possibleRTFLength = streamContent.readUInt16();
            byte[] baPossibleStrangeHeader = streamContent.readFixedBinary(6);      // in later versions of ICQ, these prepend the RTF
            AfterTextFooterType footerType = parseFooterTypeFromStrangeHeader(possibleRTFLength, baPossibleStrangeHeader);

            if (footerType == AfterTextFooterType.NoFooterBeforeRTF || footerType == AfterTextFooterType.SMSText)
                streamContent.Seek(-8, SeekOrigin.Current); // seek back the 8 bytes for the header
            //else if ()
            //    streamContent.Seek(-6, SeekOrigin.Current); // Only an UTF-8 text will be provided
            else if (footerType == AfterTextFooterType.NoFooterAtAll)
                return;
            //else
            //{
            //    UInt32 nextStrangeNumber = streamContent.readUInt32();
            //    if (0x00c0c0c0 != (0xFFc0c0c0 & nextStrangeNumber))
            //        return;
            //    // if the two strange numbers are there... just go on and parse the RTF :-)
            //}

            try
            {
                string textUTF8Temp;
                string textRTFTemp;
                streamContent.parsePossiblyRemainingRTFandUTF8(out textRTFTemp, out textUTF8Temp);
                TextRTF = textRTFTemp;  // TextRTF will be null before that operation anyway
                if (null != textUTF8Temp)
                    Text = textUTF8Temp;
            }
            catch (Exception ex)
            {
                throw new InvalidDataException("Parsed message text \"" + Text + "\" after which a footer of type "
                    + footerType.ToString() + " was detected, but there was a problem parsing the RTF/UTF-8 footer.", ex);
            }

            //            byte[] tail = streamContent.readFixedBinary(0x08);  // zeroes for incoming messages, E4 04 00 00 00 80 80 00 for outgoing
        }