/// <summary>Decodes an SMS-DELIVER message</summary> internal SmsDeliverMessage(string pdu, MessageLocation messageLocation) { this.Pdu = pdu; this.MessageLocation.Add(messageLocation); int pduOffset = 0; #region Short Message Service Center // length of the smsc information byte smscLength = byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; if (smscLength > 0) { // type of address Convert.FromTypeOfAddress(byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber), out SMSCAddress.TypeOfAddress, out SMSCAddress.NumberingPlan); pduOffset += 2; // message center address SMSCAddress.PhoneNumber += Convert.FromDecimalSemi(pdu.Substring(pduOffset, smscLength * 2 - 2)); pduOffset += smscLength * 2 - 2; } #endregion #region First octet (message type & header info bit) // first octet (TP-MTI must be set to SMS-DELIVER) byte firstOctet = byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); if (Convert.TestBit(firstOctet, 1) || Convert.TestBit(firstOctet, 0)) { throw new DecodeException("Only SMS-DELIVER messages can be decoded!"); } // TP-UDH bool hasMessageHeader = false; if (Convert.TestBit(firstOctet, 6)) { hasMessageHeader = true; } pduOffset += 2; #endregion #region Sender Address // length of the sender address byte senderLength = byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; // type of address Convert.FromTypeOfAddress(byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber), out SenderAddress.TypeOfAddress, out SenderAddress.NumberingPlan); pduOffset += 2; // sender address if (SenderAddress.TypeOfAddress == TypeOfAddress.Alphanumeric) { SenderAddress.PhoneNumber += Convert.FromSeptets(pdu.Substring(pduOffset, senderLength + (senderLength % 2 == 1 ? 1 : 0))); } else { SenderAddress.PhoneNumber += Convert.FromDecimalSemi(pdu.Substring(pduOffset, senderLength + (senderLength % 2 == 1 ? 1 : 0))); } pduOffset += senderLength + (senderLength % 2 == 1 ? 1 : 0); #endregion #region Metadata (compression, alphabet, class, encoding) // TP-PID: protocol id byte protocolId = byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; // TP-DCS: data coding scheme bool messageCompressed = false; int messageAlphabet = 0; int messageClass = -1; byte messageEncoding = byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; if (!Convert.TestBit(messageEncoding, 7) && !Convert.TestBit(messageEncoding, 6)) // general data { messageCompressed = (Convert.TestBit(messageEncoding, 5)); if (Convert.TestBit(messageEncoding, 4)) { if (!Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 0; // class 0 } else if (!Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 1; // class 1 (me specific) } else if (Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 2; // class 2 (sim specific) } else if (Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 3; // class 3 (te specific) } } if (!Convert.TestBit(messageEncoding, 3) && !Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 0; // default alphabet } else if (!Convert.TestBit(messageEncoding, 3) && Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 1; // 8 bit data } else if (Convert.TestBit(messageEncoding, 3) && !Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 2; // ucs2 (16 bit) } else if (Convert.TestBit(messageEncoding, 3) && Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 0; // reserved (default alphabet) } } else if (Convert.TestBit(messageEncoding, 7) && Convert.TestBit(messageEncoding, 6)) { if (Convert.TestBit(messageEncoding, 5) && Convert.TestBit(messageEncoding, 4)) // data coding/message class { if (!Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 0; // default alphabet } else { messageAlphabet = 1; // 8 bit data } if (!Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 0; } else if (!Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 1; } else if (Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 2; } else if (Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 3; } } else // message waiting indication group { if (Convert.TestBit(messageEncoding, 5) && !Convert.TestBit(messageEncoding, 4)) { messageAlphabet = 2; } } } #endregion #region Timestamp (adjusted to computers local time) // TP-SCTS: service center time stamp string messageTimestamp = Convert.FromDecimalSemi(pdu.Substring(pduOffset, 14)); pduOffset += 14; int year = byte.Parse(messageTimestamp.Substring(0, 2)); if ((year >= 79) && (year <= 99)) { year += 1900; } else { year += 2000; } DateReceived = new DateTime(year, byte.Parse(messageTimestamp.Substring(2, 2)), byte.Parse(messageTimestamp.Substring(4, 2)), byte.Parse(messageTimestamp.Substring(6, 2)), byte.Parse(messageTimestamp.Substring(8, 2)), byte.Parse(messageTimestamp.Substring(10, 2))); // calculate offset adjustments we need to do to get localtime int computerGmtOffset = (int)TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes; // local GMT offset string messageGmtOffset = messageTimestamp.Substring(12, 2); int messageGmtOffsetNum = 0; byte b = byte.Parse("" + messageGmtOffset[1]); // already swapped if ((b & 0xFF) == 0xFF) { // negative b &= 0xFE; messageGmtOffset = "" + messageGmtOffset[0] + (char)b; messageGmtOffsetNum = -byte.Parse(messageGmtOffset); } else { // positive messageGmtOffsetNum = byte.Parse(messageGmtOffset); } messageGmtOffsetNum *= 15; // message GMT offset this.DateReceived = this.DateReceived.AddMinutes(computerGmtOffset - messageGmtOffsetNum); #endregion #region Message length, encoded message, and header info // TP-UDL: user data length int messageLength = byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; string messageTPUD; if (messageAlphabet == 0) { messageTPUD = pdu.Substring(pduOffset, (int)Math.Ceiling(messageLength * (7.0 / 8.0)) * 2); } else { messageTPUD = pdu.Substring(pduOffset, messageLength * 2); } // TP-UD: user data pduOffset = 0; int headerLength = 0; if (hasMessageHeader) { // TP-UDHL (header length) headerLength = byte.Parse(messageTPUD.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; // for each information element int headerLengthRemaining = headerLength * 2; int headerOffset = pduOffset; while (headerLengthRemaining > 0) { byte ieElement = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; int ieLength = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; if ((ieElement == 0) && (ieLength == 3)) { // concatenated short message HasMoreParts = true; PartGroupId = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; PartCount = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; PartIndex = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; } else { headerOffset += ieLength * 2; headerLengthRemaining -= ieLength * 2; } } pduOffset = headerLength * 2 + 2; } #endregion #region Message decoding if (messageAlphabet == 0) { Text = Convert.FromSeptets(messageTPUD); // cut sms if (Text.Length > messageLength) { Text = Text.Substring(0, messageLength); } // remove header if (hasMessageHeader) { headerLength++; Text = Text.Substring((int)Math.Ceiling(headerLength / 7.0) * 7); } } else { Text = Convert.FromOctets(messageTPUD.Substring(pduOffset, messageTPUD.Length - pduOffset)); } #endregion }
/// <summary>Decodes an SMS-DELIVER message</summary> internal SmsDeliverMessage(string pdu, MessageLocation messageLocation) { this.Pdu = pdu; this.MessageLocation.Add(messageLocation); int pduOffset = 0; #region Short Message Service Center // length of the smsc information byte smscLength = byte.Parse(pdu.Substring(pduOffset,2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; if (smscLength > 0) { // type of address Convert.FromTypeOfAddress(byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber), out SMSCAddress.TypeOfAddress, out SMSCAddress.NumberingPlan); pduOffset += 2; // message center address SMSCAddress.PhoneNumber += Convert.FromDecimalSemi(pdu.Substring(pduOffset, smscLength * 2 - 2)); pduOffset += smscLength*2-2; } #endregion #region First octet (message type & header info bit) // first octet (TP-MTI must be set to SMS-DELIVER) byte firstOctet = byte.Parse(pdu.Substring(pduOffset,2), System.Globalization.NumberStyles.HexNumber); if (Convert.TestBit(firstOctet, 1) || Convert.TestBit(firstOctet, 0)) { throw new DecodeException("Only SMS-DELIVER messages can be decoded!"); } // TP-UDH bool hasMessageHeader = false; if (Convert.TestBit(firstOctet, 6)) { hasMessageHeader = true; } pduOffset += 2; #endregion #region Sender Address // length of the sender address byte senderLength = byte.Parse(pdu.Substring(pduOffset,2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; // type of address Convert.FromTypeOfAddress(byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber), out SenderAddress.TypeOfAddress, out SenderAddress.NumberingPlan); pduOffset += 2; // sender address if (SenderAddress.TypeOfAddress == TypeOfAddress.Alphanumeric) { SenderAddress.PhoneNumber += Convert.FromSeptets(pdu.Substring(pduOffset, senderLength + (senderLength % 2 == 1 ? 1 : 0))); } else { SenderAddress.PhoneNumber += Convert.FromDecimalSemi(pdu.Substring(pduOffset, senderLength + (senderLength % 2 == 1 ? 1 : 0))); } pduOffset += senderLength+(senderLength % 2 == 1 ? 1 : 0); #endregion #region Metadata (compression, alphabet, class, encoding) // TP-PID: protocol id byte protocolId = byte.Parse(pdu.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; // TP-DCS: data coding scheme bool messageCompressed = false; int messageAlphabet = 0; int messageClass = -1; byte messageEncoding = byte.Parse(pdu.Substring(pduOffset, 2),System.Globalization.NumberStyles.HexNumber); pduOffset += 2; if (!Convert.TestBit(messageEncoding, 7) && !Convert.TestBit(messageEncoding, 6)) { // general data messageCompressed = (Convert.TestBit(messageEncoding, 5)); if (Convert.TestBit(messageEncoding, 4)) { if (!Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 0; // class 0 } else if (!Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 1; // class 1 (me specific) } else if (Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 2; // class 2 (sim specific) } else if (Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 3; // class 3 (te specific) } } if (!Convert.TestBit(messageEncoding, 3) && !Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 0; // default alphabet } else if (!Convert.TestBit(messageEncoding, 3) && Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 1; // 8 bit data } else if (Convert.TestBit(messageEncoding, 3) && !Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 2; // ucs2 (16 bit) } else if (Convert.TestBit(messageEncoding, 3) && Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 0; // reserved (default alphabet) } } else if (Convert.TestBit(messageEncoding, 7) && Convert.TestBit(messageEncoding, 6)) { if (Convert.TestBit(messageEncoding, 5) && Convert.TestBit(messageEncoding, 4)) { // data coding/message class if (!Convert.TestBit(messageEncoding, 2)) { messageAlphabet = 0; // default alphabet } else { messageAlphabet = 1; // 8 bit data } if (!Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 0; } else if (!Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 1; } else if (Convert.TestBit(messageEncoding, 1) && !Convert.TestBit(messageEncoding, 0)) { messageClass = 2; } else if (Convert.TestBit(messageEncoding, 1) && Convert.TestBit(messageEncoding, 0)) { messageClass = 3; } } else { // message waiting indication group if (Convert.TestBit(messageEncoding, 5) && !Convert.TestBit(messageEncoding, 4)) { messageAlphabet = 2; } } } #endregion #region Timestamp (adjusted to computers local time) // TP-SCTS: service center time stamp string messageTimestamp = Convert.FromDecimalSemi(pdu.Substring(pduOffset, 14)); pduOffset += 14; int year = byte.Parse(messageTimestamp.Substring(0, 2)); if ((year >= 79) && (year <= 99)) { year += 1900; } else { year += 2000; } DateReceived = new DateTime(year, byte.Parse(messageTimestamp.Substring(2,2)), byte.Parse(messageTimestamp.Substring(4,2)), byte.Parse(messageTimestamp.Substring(6,2)), byte.Parse(messageTimestamp.Substring(8,2)), byte.Parse(messageTimestamp.Substring(10,2))); // calculate offset adjustments we need to do to get localtime int computerGmtOffset = (int)TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes; // local GMT offset string messageGmtOffset = messageTimestamp.Substring(12,2); int messageGmtOffsetNum = 0; byte b = byte.Parse(""+messageGmtOffset[1]); // already swapped if ((b & 0xFF) == 0xFF) { // negative b &= 0xFE; messageGmtOffset = "" + messageGmtOffset[0] + (char)b; messageGmtOffsetNum = -byte.Parse(messageGmtOffset); } else { // positive messageGmtOffsetNum = byte.Parse(messageGmtOffset); } messageGmtOffsetNum *= 15; // message GMT offset this.DateReceived = this.DateReceived.AddMinutes(computerGmtOffset-messageGmtOffsetNum); #endregion #region Message length, encoded message, and header info // TP-UDL: user data length int messageLength = byte.Parse(pdu.Substring(pduOffset,2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; string messageTPUD; if (messageAlphabet == 0) { messageTPUD = pdu.Substring(pduOffset, (int)Math.Ceiling(messageLength*(7.0/8.0))*2); } else { messageTPUD = pdu.Substring(pduOffset, messageLength*2); } // TP-UD: user data pduOffset = 0; int headerLength = 0; if (hasMessageHeader) { // TP-UDHL (header length) headerLength = byte.Parse(messageTPUD.Substring(pduOffset, 2), System.Globalization.NumberStyles.HexNumber); pduOffset += 2; // for each information element int headerLengthRemaining = headerLength*2; int headerOffset = pduOffset; while (headerLengthRemaining > 0) { byte ieElement = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; int ieLength = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; if ((ieElement == 0) && (ieLength == 3)) { // concatenated short message HasMoreParts = true; PartGroupId = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; PartCount = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; PartIndex = byte.Parse(messageTPUD.Substring(headerOffset, 2), System.Globalization.NumberStyles.HexNumber); headerOffset += 2; headerLengthRemaining -= 2; } else { headerOffset += ieLength*2; headerLengthRemaining -= ieLength*2; } } pduOffset = headerLength*2+2; } #endregion #region Message decoding if (messageAlphabet == 0) { Text = Convert.FromSeptets(messageTPUD); // cut sms if (Text.Length > messageLength) { Text = Text.Substring(0, messageLength); } // remove header if (hasMessageHeader) { headerLength++; Text = Text.Substring((int)Math.Ceiling(headerLength/7.0)*7); } } else { Text = Convert.FromOctets(messageTPUD.Substring(pduOffset, messageTPUD.Length - pduOffset)); } #endregion }