├── SharpSMS ├── DataEncoding.cs ├── MessageIndicationOperation.cs ├── Wbxml │ ├── ServiceLoadingAction.cs │ ├── ServiceIndicationAction.cs │ ├── WBXMLDocument.cs │ ├── ServiceLoading.cs │ └── ServiceIndication.cs ├── MessageClass.cs ├── IndicationType.cs ├── ISmsMessageContent.cs ├── Wap │ ├── Wdp.cs │ ├── WdpBinaryMessage.cs │ ├── WdpMessage.cs │ ├── WapPushMessage.cs │ └── Wsp.cs ├── Properties │ └── AssemblyInfo.cs ├── ShortMessageType.cs ├── TelematicDevice.cs ├── TextMessage.cs ├── ProtocoldentifierBuilder.cs ├── SharpSMS.csproj ├── MessageIndication.cs └── SMSSubmit.cs ├── SharpSMS.sln ├── Samples ├── Properties │ └── AssemblyInfo.cs ├── Samples.csproj ├── Program.cs ├── SampleMessage.cs └── Modem.cs ├── .gitignore ├── README.md └── LICENSE.txt /SharpSMS/DataEncoding.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Encodings for SMS messages 5 | /// 6 | public enum DataEncoding 7 | { 8 | /// 9 | /// Default 7bit alphabet used in GSM 10 | /// 11 | Default7bit, 12 | /// 13 | /// 8 bit encoding 14 | /// 15 | Data8bit, 16 | /// 17 | /// UCS2 16 bit encoding 18 | /// 19 | UCS2_16bit 20 | } 21 | } -------------------------------------------------------------------------------- /SharpSMS/MessageIndicationOperation.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Specifies if phone stores or discards message indication 5 | /// 6 | public enum MessageIndicationOperation 7 | { 8 | /// 9 | /// Message Indication Operation is not used - Default 10 | /// 11 | NotSet, 12 | /// 13 | /// Store Indication Message 14 | /// 15 | Store, 16 | /// 17 | /// Discard Indication Message 18 | /// 19 | Discard 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SharpSMS/Wbxml/ServiceLoadingAction.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS.Wbxml 2 | { 3 | /// 4 | /// Values for Action attribute in Service Loading Message 5 | /// 6 | public enum ServiceLoadingAction : byte 7 | { 8 | /// 9 | /// Action not set 10 | /// 11 | NotSet, 12 | /// 13 | /// Execute low 14 | /// 15 | Execute_low, 16 | /// 17 | /// Execute high 18 | /// 19 | Execute_high, 20 | /// 21 | /// Cache 22 | /// 23 | Cache 24 | } 25 | } -------------------------------------------------------------------------------- /SharpSMS/MessageClass.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Message class indicates where message will be stored 5 | /// 6 | public enum MessageClass 7 | { 8 | /// 9 | /// Flash message only to display 10 | /// 11 | ImmediateDisplay, 12 | /// 13 | /// Default store 14 | /// 15 | MESpecific, 16 | /// 17 | /// Message for the SIM 18 | /// 19 | SIMSpecific, 20 | /// 21 | /// TE Specific 22 | /// 23 | TESpecific 24 | } 25 | } -------------------------------------------------------------------------------- /SharpSMS/IndicationType.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Type of message that indication is representing 5 | /// 6 | public enum IndicationType 7 | { 8 | /// 9 | /// Voicmail Message Waiting 10 | /// 11 | Voicemail, 12 | /// 13 | /// Fax Message Waiting 14 | /// 15 | FaxMessage, 16 | /// 17 | /// Email Message Waiting 18 | /// 19 | EmailMessage, 20 | /// 21 | /// Other Message Waiting 22 | /// 23 | OtherMessage 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SharpSMS/ISmsMessageContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SharpSMS 6 | { 7 | /// 8 | /// Abstract class representing body content of the SMS 9 | /// 10 | public interface ISmsMessageContent 11 | { 12 | /// 13 | /// Encoding of the data (7-bit, 8-bit, 16-bit) 14 | /// 15 | DataEncoding DataEncoding { get; set; } 16 | 17 | /// 18 | /// Returns byte array suitable to be send in sms message. Including all user headers (if any) 19 | /// 20 | /// byte array 21 | byte[] GetSMSBytes(); 22 | 23 | /// 24 | /// Returns byte array of the user header 25 | /// 26 | /// 27 | byte[] GetUDHBytes(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SharpSMS/Wbxml/ServiceIndicationAction.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS.Wbxml 2 | { 3 | /// 4 | /// Values for Action attribute in Service Indication Message 5 | /// 6 | public enum ServiceIndicationAction : byte 7 | { 8 | /// 9 | /// Action not set 10 | /// 11 | NotSet, 12 | /// 13 | /// No signaling 14 | /// 15 | Signal_none, 16 | /// 17 | /// Low signaling 18 | /// 19 | Signal_low, 20 | /// 21 | /// Medium signaling 22 | /// 23 | Signal_medium, 24 | /// 25 | /// High signaling 26 | /// 27 | Signal_high, 28 | /// 29 | /// Delete after display 30 | /// 31 | Delete 32 | } 33 | } -------------------------------------------------------------------------------- /SharpSMS/Wap/Wdp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace SharpSMS.Wap 7 | { 8 | /// 9 | /// Helper static class representing Wireless Datagram Protocol 10 | /// 11 | public class Wdp 12 | { 13 | // Ports for the WDP information element, instructing the handset which 14 | // application to load on receving the message 15 | public const byte INFORMATIONELEMENT_IDENTIFIER_APPLICATIONPORT = 0x05; // 16 Bit 16 | 17 | public const int WAP_PORT_PUSH_SESSION_DESTINATION = 0x0B84; 18 | public const int WAP_PORT_PUSH_SESSION_SOURCE = 0x23F0; 19 | public const int WAP_PORT_VCARD = 0x23F4; 20 | public const int WAP_PORT_VCALENDAR = 0x23F5; 21 | 22 | public const int WAP_PORT_NOKIA_RINGTONE = 0x1581; 23 | public const int WAP_PORT_NOKIA_OPERATORLOGO = 0x1582; 24 | public const int WAP_PORT_NOKIA_CLILOGO = 0x1583; 25 | public const int WAP_PORT_NOKIA_MULTIPART = 0x158A; 26 | public const int WAP_PORT_NOKIA_OTASETTINGS = 0xC34F; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SharpSMS.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSMS", "SharpSMS\SharpSMS.csproj", "{B5C82198-500A-4E4B-A3BF-6015745D3CB0}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{FEEE4A19-03FB-4988-9B88-ADE85487DAF4}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B5C82198-500A-4E4B-A3BF-6015745D3CB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B5C82198-500A-4E4B-A3BF-6015745D3CB0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B5C82198-500A-4E4B-A3BF-6015745D3CB0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B5C82198-500A-4E4B-A3BF-6015745D3CB0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {FEEE4A19-03FB-4988-9B88-ADE85487DAF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {FEEE4A19-03FB-4988-9B88-ADE85487DAF4}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {FEEE4A19-03FB-4988-9B88-ADE85487DAF4}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {FEEE4A19-03FB-4988-9B88-ADE85487DAF4}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /Samples/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpSMS Samples")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpSMS Samples")] 13 | [assembly: AssemblyCopyright("Copyright © 2007")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a275974e-d8ab-49ff-bfb0-2d7d16dfe834")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SharpSMS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpSMS")] 9 | [assembly: AssemblyDescription(".NET Class for SMS PDU formating")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpSMS")] 13 | [assembly: AssemblyCopyright("Copyright © 2007")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1da885a0-1953-41d7-8e8f-236358f67989")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.0.0.0")] 36 | [assembly: AssemblyFileVersion("2.0.0.0")] 37 | -------------------------------------------------------------------------------- /SharpSMS/Wap/WdpBinaryMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SharpSMS.Wap 6 | { 7 | /// 8 | /// Class represents Wap Push binary sms 9 | /// 10 | public class WdpBinaryMessage : WdpMessage, ISmsMessageContent 11 | { 12 | 13 | #region Constuctors 14 | 15 | /// 16 | /// Creates instance of the WapPushBinary 17 | /// 18 | public WdpBinaryMessage() 19 | : this(null) 20 | { 21 | } 22 | 23 | /// 24 | /// Creates instance of the WapPushBinary 25 | /// 26 | /// Binary data of the message 27 | public WdpBinaryMessage(byte[] data) 28 | { 29 | this.DestinationPort = Wdp.WAP_PORT_PUSH_SESSION_DESTINATION; 30 | this.SourcePort = Wdp.WAP_PORT_PUSH_SESSION_SOURCE; 31 | 32 | this.DataEncoding = DataEncoding.Data8bit; 33 | this.Data = data; 34 | } 35 | #endregion 36 | 37 | #region Public methods 38 | /// 39 | /// Returns array of bytes representing the SMS 40 | /// 41 | /// 42 | public byte[] GetSMSBytes() 43 | { 44 | return Data; 45 | } 46 | /// 47 | /// Returns array of bytes repesenting the WDP header 48 | /// 49 | /// 50 | public byte[] GetUDHBytes() 51 | { 52 | return this.GetWdpHeader(); 53 | } 54 | #endregion 55 | 56 | #region Properties 57 | /// 58 | /// Data encoding of the message 59 | /// 60 | public DataEncoding DataEncoding { get; set; } 61 | /// 62 | /// Content of the message 63 | /// 64 | public byte[] Data { get; set; } 65 | #endregion 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /SharpSMS/ShortMessageType.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Represents message type field for Protocol Identifier 5 | /// 6 | public enum ShortMessageType : byte 7 | { 8 | /// 9 | /// A short message type 0 indicates that the ME must acknowledge receipt of the short message but may discard its contents. 10 | /// 11 | ShortMessageType0 = 0x00, 12 | /// 13 | /// Replace Short Message Type 1 14 | /// 15 | ReplaceMessageType1 = 0x01, 16 | /// 17 | /// Replace Short Message Type 2 18 | /// 19 | ReplaceMessageType2 = 0x02, 20 | /// 21 | /// Replace Short Message Type 3 22 | /// 23 | ReplaceMessageType3 = 0x03, 24 | /// 25 | /// Replace Short Message Type 4 26 | /// 27 | ReplaceMessageType4 = 0x04, 28 | /// 29 | /// Replace Short Message Type 5 30 | /// 31 | ReplaceMessageType5 = 0x05, 32 | /// 33 | /// Replace Short Message Type 6 34 | /// 35 | ReplaceMessageType6 = 0x06, 36 | /// 37 | /// Replace Short Message Type 7 38 | /// 39 | ReplaceMessageType7 = 0x07, 40 | /// 41 | /// Inicates to the MS to inform the user that a call can be established to the address specified within the TP-OA 42 | /// 43 | ReturnCall = 0x1F, 44 | /// 45 | /// ME Data download is facility whereby the ME shall process the short message in its entirety including all SMS elements contained in the SMS deliver to the ME 46 | /// 47 | MEDataDownload = 0x3D, 48 | /// 49 | /// The ME De-personalization Short Message is an ME-specific message which instructs the ME to de-personalities the ME 50 | /// 51 | MEDePersonlaization = 0x3E, 52 | /// 53 | /// SIM Data download is a facility whereby the ME must pass the short message in its entirety including all SMS elements 54 | /// 55 | SIMDataDownload = 0x3F, 56 | } 57 | } -------------------------------------------------------------------------------- /SharpSMS/Wap/WdpMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace SharpSMS.Wap 7 | { 8 | /// 9 | /// Represents WDP Message 10 | /// 11 | public abstract class WdpMessage 12 | { 13 | /// 14 | /// Source port of the message 15 | /// 16 | public int SourcePort { get; set; } 17 | /// 18 | /// Destination port of the message 19 | /// 20 | public int DestinationPort { get; set; } 21 | 22 | /// 23 | /// Generates the WDP (Wireless Datagram Protocol) or UDH (User Data Header) for the 24 | /// SMS message. In the case comprising the Application Port information element 25 | /// indicating to the handset which application to start on receipt of the message 26 | /// 27 | /// byte array comprising the header 28 | public byte[] GetWdpHeader() 29 | { 30 | MemoryStream stream = new MemoryStream(); 31 | 32 | stream.WriteByte(Wdp.INFORMATIONELEMENT_IDENTIFIER_APPLICATIONPORT); 33 | 34 | byte[] destPort = ToBigEndian(DestinationPort, 2); 35 | byte[] sourcePort = ToBigEndian(SourcePort, 2); 36 | 37 | // Length of port information = 2*16 bit numbers = 4 bytes 38 | stream.WriteByte((byte)(destPort.Length + sourcePort.Length)); 39 | stream.Write(destPort, 0, destPort.Length); 40 | stream.Write(sourcePort, 0, sourcePort.Length); 41 | 42 | MemoryStream headerStream = new MemoryStream(); 43 | 44 | stream.WriteTo(headerStream); 45 | return headerStream.ToArray(); 46 | } 47 | 48 | /// 49 | /// Converts number into BigEndian array of bytes 50 | /// 51 | /// Number 52 | /// Number of bytes to return 53 | private byte[] ToBigEndian(long number, byte byteLen) 54 | { 55 | byte[] outputArray = new byte[byteLen]; 56 | 57 | outputArray[byteLen - 1] = (byte)number; 58 | for (int i = byteLen - 2; i >= 0; i--) 59 | outputArray[i] = (byte)(number >> (8 * (i + 1))); 60 | 61 | return outputArray; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SharpSMS/TelematicDevice.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Represents Telematic device 5 | /// 6 | public enum TelematicDevice : byte 7 | { 8 | /// 9 | /// implicit - device type is specific or can be concluded on the basis of the address 10 | /// 11 | Implicit = 0x00, 12 | /// 13 | /// Telex (or teletex reduced to telex format) 14 | /// 15 | Telex = 0x01, 16 | /// 17 | /// Group 3 telefax 18 | /// 19 | Group3Telefax = 0x02, 20 | /// 21 | /// Group 4 telefax 22 | /// 23 | Group4Telefax = 0x03, 24 | /// 25 | /// Voice telephone 26 | /// 27 | VoiceTelephone = 0x04, 28 | /// 29 | /// ERMES 30 | /// 31 | ERMES = 0x05, 32 | /// 33 | /// National Paging System 34 | /// 35 | NationalPagingSystem = 0x06, 36 | /// 37 | /// Videotex (T.100/T.101) 38 | /// 39 | Videotex = 0x07, 40 | /// 41 | /// Teletex 42 | /// 43 | Teletex = 0x08, 44 | /// 45 | /// Teletex, in PSPDN 46 | /// 47 | TeletexPSDN = 0x09, 48 | /// 49 | /// Teletex, in CSPDN 50 | /// 51 | TeletexCSPDN =0x0A, 52 | /// 53 | /// Teletex, in analog PSTN 54 | /// 55 | TeletexPSTN = 0x0B, 56 | /// 57 | /// Teletex, in digital ISDN 58 | /// 59 | TeletexISDN = 0X0C, 60 | /// 61 | /// UCI (Universal Computer Interface, ETSI DE/PS 3 01-3) 62 | /// 63 | UCI = 0x0D, 64 | /// 65 | /// S message handling facility 66 | /// 67 | MessageHandlingFacility = 0x10, 68 | /// 69 | /// Sny public X.400-based message handling system 70 | /// 71 | X400 = 0x11, 72 | /// 73 | /// Internet Electronic Mail 74 | /// 75 | Email = 0x12, 76 | /// 77 | /// A GSM mobile station 78 | /// 79 | GSMMobileStation = 0x1F 80 | } 81 | } -------------------------------------------------------------------------------- /SharpSMS/TextMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace SharpSMS 5 | { 6 | /// 7 | /// Class represent standard plain text message 8 | /// 9 | public class TextMessage : ISmsMessageContent 10 | { 11 | #region Constuctor 12 | /// 13 | /// Creates new plain text message 14 | /// 15 | public TextMessage() 16 | { 17 | this.DataEncoding = DataEncoding.Default7bit; 18 | this.Text = string.Empty; 19 | } 20 | #endregion 21 | 22 | #region Override Methods 23 | /// 24 | /// Method returns byte array suitable to be send in SMS 25 | /// 26 | /// byte array 27 | public byte[] GetSMSBytes() 28 | { 29 | byte[] text = GetDataBytes(); 30 | return text; 31 | } 32 | 33 | /// 34 | /// Method returns user header bytes. In plain text message it's always empty 35 | /// 36 | /// 37 | public byte[] GetUDHBytes() 38 | { 39 | return new byte[]{}; 40 | } 41 | #endregion 42 | 43 | #region Private Methods 44 | 45 | /// 46 | /// Returns message text encoded in DataEncoding 47 | /// 48 | /// byte array 49 | protected byte[] GetDataBytes() 50 | { 51 | byte[] data = new byte[] {}; 52 | 53 | switch (DataEncoding) 54 | { 55 | case DataEncoding.Default7bit: 56 | data = Encoding.ASCII.GetBytes(Text); 57 | break; 58 | case DataEncoding.Data8bit: 59 | data = Encoding.GetEncoding("iso-8859-1").GetBytes(Text); 60 | break; 61 | case DataEncoding.UCS2_16bit: 62 | data = Encoding.BigEndianUnicode.GetBytes(Text); 63 | break; 64 | } 65 | 66 | return data; 67 | } 68 | #endregion 69 | 70 | #region Properties 71 | 72 | /// 73 | /// Text of the message 74 | /// 75 | public string Text { get; set; } 76 | 77 | /// 78 | /// Encoding of the data (7-bit, 8-bit, 16-bit) 79 | /// 80 | public DataEncoding DataEncoding { get; set; } 81 | #endregion 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Samples/Samples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FEEE4A19-03FB-4988-9B88-ADE85487DAF4} 8 | Exe 9 | Properties 10 | Samples 11 | Samples 12 | v2.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {b5c82198-500a-4e4b-a3bf-6015745d3cb0} 47 | SharpSMS 48 | 49 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /Samples/Program.cs: -------------------------------------------------------------------------------- 1 | using SharpSMS; 2 | using System; 3 | 4 | namespace Samples 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | Modem modem = new Modem("COM3", 115200); 11 | 12 | while (true) 13 | { 14 | PrintMenu(); 15 | ConsoleKeyInfo key = Console.ReadKey(true); 16 | SMSSubmit sms = new SMSSubmit(); 17 | 18 | switch (key.KeyChar) 19 | { 20 | case '1': 21 | sms = SampleMessage.PlainText(); 22 | break; 23 | case '2': 24 | sms = SampleMessage.FlashMessage(); 25 | break; 26 | case '3': 27 | sms = SampleMessage.ReplacebleMessage("This message will be replaced: FOO"); 28 | break; 29 | case '4': 30 | sms = SampleMessage.ReplacebleMessage("This is replacement message: BAR"); 31 | break; 32 | case '5': 33 | sms = SampleMessage.ActivateVoicemailIndication(); 34 | break; 35 | case '6': 36 | sms = SampleMessage.DeactivateVoicemailIndication(); 37 | break; 38 | case '7': 39 | sms = SampleMessage.ServiceIndicationMessage(); 40 | break; 41 | case '8': 42 | sms = SampleMessage.ServiceLoadingMessage(); 43 | break; 44 | case '9': 45 | sms = SampleMessage.WapPushConfiguration(); 46 | break; 47 | case '0': 48 | return; 49 | } 50 | 51 | Console.Write("\nEnter phone number: "); 52 | sms.PhoneNumber = Console.ReadLine(); 53 | 54 | modem.SendSMS(sms); 55 | } 56 | } 57 | 58 | static void PrintMenu() 59 | { 60 | Console.WriteLine("[1] Send \"Hello world\" message"); 61 | Console.WriteLine("[2] Send flash message"); 62 | Console.WriteLine("[3] Send replacable message"); 63 | Console.WriteLine("[4] Send replacement message"); 64 | Console.WriteLine("[5] Activate Voicemail indication"); 65 | Console.WriteLine("[6] Deactivate Voicemail indication"); 66 | Console.WriteLine("[7] Send SI wap push message"); 67 | Console.WriteLine("[8] Send SL wap push message"); 68 | Console.WriteLine("[9] Send Configuration wap push message"); 69 | Console.WriteLine("[0] Exit"); 70 | 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SharpSMS/ProtocoldentifierBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Class helps to assemble the TP-PID Protocol Identifier value 5 | /// 6 | public class ProtocoldentifierBuilder 7 | { 8 | #region Constants 9 | /// 10 | /// Bits 7, 6, 5 to identify SME to SME message 11 | /// 12 | public const byte TP_PID_SME_TO_SME = 0x00; 13 | /// 14 | /// Bits 7, 6, 5 to identify Telematic device message 15 | /// 16 | public const byte TP_PID_TELEMATIC_DEVICE = 0x20; 17 | /// 18 | /// Bits 7, 6 to identify Short Message 19 | /// 20 | public const byte TP_PID_MESSAGE_TYPE = 0x40; 21 | /// 22 | /// Bits 7, 6 to identify SC specific protocol 23 | /// 24 | public const byte TP_PID_SC_SPECIFIC = 0x0C; 25 | #endregion 26 | 27 | #region Constructors 28 | /// 29 | /// Creates new TP-PID with value 0x00 30 | /// 31 | public ProtocoldentifierBuilder() 32 | { 33 | PTBits = 0x00; 34 | } 35 | 36 | /// 37 | /// Creates new TP-PID for given message type 38 | /// 39 | /// Message type 40 | public ProtocoldentifierBuilder(ShortMessageType messageType) 41 | { 42 | SetMessageType(messageType); 43 | } 44 | #endregion 45 | 46 | #region Public Methods 47 | /// 48 | /// Set message type for TP-PID 49 | /// 50 | /// 51 | public void SetMessageType(ShortMessageType messageType) 52 | { 53 | PTBits = (byte)(TP_PID_MESSAGE_TYPE | (byte)messageType); 54 | } 55 | 56 | /// 57 | /// Set telematic device for TP-PID 58 | /// 59 | /// 60 | public void SetTelematicDevice(TelematicDevice device) 61 | { 62 | PTBits = (byte)(TP_PID_TELEMATIC_DEVICE | (byte)device); 63 | } 64 | 65 | /// 66 | /// Set protocol value for SME to SME communication 67 | /// 68 | /// 69 | public void SetSMEtoSME(byte protocolBits) 70 | { 71 | protocolBits <<= 2; 72 | PTBits = (byte)(TP_PID_SME_TO_SME | (protocolBits >> 2)); 73 | } 74 | 75 | /// 76 | /// Set protocol value for SC communication 77 | /// 78 | /// SC specific bits 79 | public void SetSCSpecific(byte protocolBits) 80 | { 81 | protocolBits <<= 2; 82 | PTBits = (byte)(TP_PID_SC_SPECIFIC | (protocolBits >> 2)); 83 | } 84 | 85 | /// 86 | /// Returns value that can be used as Protocol Identifier for message 87 | /// 88 | /// 89 | public byte ToByte() 90 | { 91 | return PTBits; 92 | } 93 | #endregion 94 | 95 | #region Fields 96 | /// 97 | /// Property to store bits fo the TP-PID value 98 | /// 99 | private byte PTBits { get; set; } 100 | #endregion 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /SharpSMS/SharpSMS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {B5C82198-500A-4E4B-A3BF-6015745D3CB0} 9 | Library 10 | Properties 11 | SharpSMS 12 | SharpSMS 13 | v2.0 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | MinimumRecommendedRules.ruleset 30 | true 31 | bin\Debug\SharpSMS.XML 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | AllRules.ruleset 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | -------------------------------------------------------------------------------- /Samples/SampleMessage.cs: -------------------------------------------------------------------------------- 1 | using SharpSMS; 2 | using SharpSMS.Wap; 3 | using SharpSMS.Wbxml; 4 | using System; 5 | using System.Text; 6 | 7 | namespace Samples 8 | { 9 | class SampleMessage 10 | { 11 | public static SMSSubmit PlainText() 12 | { 13 | TextMessage textMessage = new TextMessage(); 14 | textMessage.DataEncoding = DataEncoding.Default7bit; 15 | textMessage.Text = "Hello World from SharpSMS!"; 16 | 17 | SMSSubmit sms = new SMSSubmit(); 18 | sms.MessageToSend = textMessage; 19 | 20 | // SMS center will retry the delivery for 5 days 21 | sms.ValidityPeriod = new TimeSpan(5, 0, 0, 0); 22 | 23 | return sms; 24 | } 25 | 26 | public static SMSSubmit FlashMessage() 27 | { 28 | TextMessage textMessage = new TextMessage(); 29 | textMessage.DataEncoding = DataEncoding.Default7bit; 30 | textMessage.Text = "Flash message from SharpSMS!"; 31 | 32 | SMSSubmit sms = new SMSSubmit(); 33 | sms.MessageToSend = textMessage; 34 | sms.Indication.Class = MessageClass.ImmediateDisplay; 35 | 36 | return sms; 37 | } 38 | 39 | public static SMSSubmit ReplacebleMessage(string text) 40 | { 41 | TextMessage textMessage = new TextMessage(); 42 | textMessage.Text = text; 43 | 44 | SMSSubmit sms = new SMSSubmit(); 45 | sms.MessageToSend = textMessage; 46 | 47 | sms.ProtocolIdentifier = new ProtocoldentifierBuilder(ShortMessageType.ReplaceMessageType1).ToByte(); 48 | 49 | return sms; 50 | } 51 | 52 | public static SMSSubmit ActivateVoicemailIndication() 53 | { 54 | TextMessage textMessage = new TextMessage(); 55 | textMessage.Text = "You have a voicemail."; 56 | 57 | SMSSubmit sms = new SMSSubmit(); 58 | sms.MessageToSend = textMessage; 59 | 60 | sms.Indication.Operation = MessageIndicationOperation.Discard; 61 | sms.Indication.Type = IndicationType.Voicemail; 62 | sms.Indication.IsActive = true; 63 | 64 | return sms; 65 | } 66 | 67 | public static SMSSubmit DeactivateVoicemailIndication() 68 | { 69 | SMSSubmit sms = new SMSSubmit(new TextMessage()); 70 | 71 | sms.Indication.Operation = MessageIndicationOperation.Discard; 72 | sms.Indication.Type = IndicationType.Voicemail; 73 | sms.Indication.IsActive = false; 74 | 75 | return sms; 76 | } 77 | 78 | public static SMSSubmit ServiceIndicationMessage() 79 | { 80 | ServiceIndication si = new ServiceIndication(); 81 | si.Action = ServiceIndicationAction.Signal_medium; 82 | si.Text = "Service indication from SharpSMS"; 83 | si.Href = "https://github.com/pbansky/SharpSMS"; 84 | si.Expires = DateTime.Now.AddDays(3); 85 | 86 | WapPushMessage wapPushMessage = new WapPushMessage(si); 87 | wapPushMessage.XWapInitiatorURI = "SharpSMS"; 88 | 89 | return new SMSSubmit(wapPushMessage); 90 | } 91 | 92 | public static SMSSubmit ServiceLoadingMessage() 93 | { 94 | ServiceLoading sl = new ServiceLoading(); 95 | sl.Action = ServiceLoadingAction.Execute_high; 96 | // This is a cab file with Total Commander for Windows Mobile 5/6/6.5 97 | sl.Href = "http://ghisler.fileburst.com/ce/tcmdphone.cab"; 98 | 99 | WapPushMessage wapPushMessage = new WapPushMessage(sl); 100 | wapPushMessage.XWapInitiatorURI = "SharpSMS"; 101 | return new SMSSubmit(wapPushMessage); 102 | } 103 | 104 | public static SMSSubmit WapPushConfiguration() 105 | { 106 | // This is a configuration XML for Windows Mobile Internet Explorer Favorites 107 | string configXML = @""; 108 | 109 | WapPushMessage wapPushMessage = new WapPushMessage(); 110 | wapPushMessage.XWapInitiatorURI = "SharpSMS"; 111 | 112 | wapPushMessage.ContentType = "text/vnd.wap.connectivity-xml"; 113 | wapPushMessage.Data = Encoding.UTF8.GetBytes(configXML); 114 | 115 | wapPushMessage.Security = Wsp.SecurityMethod.USERPIN; 116 | wapPushMessage.UserPin = "1234"; 117 | 118 | return new SMSSubmit(wapPushMessage); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SharpSMS 2 | ======== 3 | 4 | SharpSMS is a library to create SMS messages in PDU format. It provides full control over the SMS including the UDH header, it supports WAP Push messages, Service Indication and Service Location. Messages can be signed for authentication with IMSI or user PIN. 5 | 6 | 7 | Creating simple text message 8 | ---------------------------- 9 | 10 | Following code will create plain text message with content *Hello World from SharpSMS!* to be delivered to phone number *+1 (234) 456 7890* 11 | 12 | TextMessage textMessage = new TextMessage(); 13 | textMessage.Text = "Hello World from SharpSMS!"; 14 | 15 | SMSSubmit sms = new SMSSubmit(); 16 | sms.PhoneNumber = "+1234567890"; 17 | sms.MessageToSend = textMessage; 18 | 19 | List messageList = sms.GetPDUList(); 20 | 21 | Now, the **messageList** contains PDU formatted message(s) that are ready to be send over GSM modem or internet gateway. 22 | 23 | 24 | Tweaking SMS header 25 | ------------------- 26 | 27 | SMS message have several header fields defined according to GSM 03.40 specification. These fields are exposed as properties of **SMSSubmit** class: 28 | 29 | Indication 30 | MessageReference 31 | PhoneNumber 32 | ProtocolIdentifier 33 | RequestDeliveryConfirmation 34 | ValidityPeriod 35 | 36 | ### Flash Message 37 | 38 | Flash message refers to message that is displayed on the phone screen but not stored. This type of message is usually sent by GSM operator to display prepaid balance or confirm message delivery. To create a flash message **MessageClass** must be set. Following code demonstrates how to create flash message: 39 | 40 | TextMessage textMessage = new TextMessage(); 41 | textMessage.DataEncoding = DataEncoding.Default7bit; 42 | textMessage.Text = "Flash message from SharpSMS!"; 43 | 44 | SMSSubmit sms = new SMSSubmit(); 45 | sms.PhoneNumber = "+1234567890"; 46 | sms.MessageToSend = textMessage; 47 | sms.Indication.Class = MessageClass.ImmediateDisplay; 48 | 49 | 50 | ### Voicemail Indication 51 | 52 | GSM 03.40 specification defines several *Indication types*, for example voicemail, email etc. For each indication can be defined *Operation*, which determines how the indication will be treated when received by the phone. Following code creates SMS message that will make the **voicemail** icon appear on the phone: 53 | 54 | TextMessage textMessage = new TextMessage(); 55 | textMessage.Text = "You have a voicemail."; 56 | 57 | SMSSubmit sms = new SMSSubmit(); 58 | sms.PhoneNumber = "+1234567890"; 59 | sms.MessageToSend = textMessage; 60 | 61 | sms.Indication.Operation = MessageIndicationOperation.Discard; 62 | sms.Indication.Type = IndicationType.Voicemail; 63 | sms.Indication.IsActive = true; 64 | 65 | Following code will remove the **voicemail** icon from the phone status bar: 66 | 67 | TextMessage textMessage = new TextMessage(); 68 | 69 | SMSSubmit sms = new SMSSubmit(); 70 | sms.PhoneNumber = "+1234567890"; 71 | sms.MessageToSend = textMessage; 72 | 73 | sms.Indication.Operation = MessageIndicationOperation.Discard; 74 | sms.Indication.Type = IndicationType.Voicemail; 75 | sms.Indication.IsActive = false; 76 | 77 | 78 | User Data Header and Wap Push 79 | ----------------------------- 80 | 81 | SMS message can contain user data header, which extends possibilities to address specific functions of the phone; including ring tones, logos or configurations delivered using SMS message. This functionality is called Over The Air Provisioning (OTA). Modern smartphones like Android, Windows Phone or iPhone do not support this type of messages anymore. 82 | However, some Android devices are equipped with push router, that can process OTA provisioning messages. 83 | Following code will create Wap Push message with configuration for Windows Mobile. Message will be signed with user PIN, so user has to enter the PIN in order for message to be processed: 84 | 85 | string configXML = @""; 86 | 87 | WapPushMessage wapPushMessage = new WapPushMessage(); 88 | wapPushMessage.XWapInitiatorURI = "SharpSMS"; 89 | 90 | wapPushMessage.ContentType = "text/vnd.wap.connectivity-xml"; 91 | wapPushMessage.Data = Encoding.UTF8.GetBytes(configXML); 92 | 93 | wapPushMessage.Security = Wsp.SecurityMethod.USERPIN; 94 | wapPushMessage.UserPin = "1234"; 95 | 96 | SMSSubmit sms = new SMSSubmit(); 97 | sms.PhoneNumber = "+1234567890"; 98 | sms.MessageToSend = wapPushMessage 99 | 100 | 101 | Sending the message 102 | ------------------- 103 | 104 | PDU formatted message(s) obtained using **SMSSubmit.GetPDUList()** method can be send via GSM modem using **AT+CMGS** command. This is demonstrated in sample codes. Internet gateways can be used as an alternative to modem. 105 | -------------------------------------------------------------------------------- /SharpSMS/Wbxml/WBXMLDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace SharpSMS.Wbxml 7 | { 8 | /// 9 | /// Abstract class for creating messages tokenized into WBXML document 10 | /// 11 | public abstract class WBXMLDocument 12 | { 13 | #region Constants 14 | /// 15 | /// Binary representation of NULL 16 | /// 17 | public const byte NULL = 0x00; 18 | 19 | /// 20 | /// Binary representation of v1.1 21 | /// 22 | public const byte VERSION_1_1 = 0x01; 23 | /// 24 | /// Binary representation of v1.2 25 | /// 26 | public const byte VERSION_1_2 = 0x02; 27 | /// 28 | /// Binary representation of UTF-8 encoding 29 | /// 30 | public const byte CHARSET_UTF_8 = 0x6A; 31 | /// 32 | /// Binary representation of token end 33 | /// 34 | public const byte TAGTOKEN_END = 0x01; 35 | /// 36 | /// Binary representation of inline string identifier 37 | /// 38 | public const byte TOKEN_INLINE_STRING_FOLLOWS = 0x03; 39 | /// 40 | /// Binary representation of quoted data 41 | /// 42 | public const byte TOKEN_OPAQUEDATA_FOLLOWS = 0xC3; 43 | #endregion 44 | 45 | #region Properties 46 | /// 47 | /// MIME Content type of the message 48 | /// 49 | public string ContentType { get; set; } 50 | #endregion 51 | 52 | #region Abstract Method 53 | 54 | /// 55 | /// Method return tokenized XML 56 | /// 57 | /// byte of array 58 | public abstract byte[] GetWBXMLBytes(); 59 | 60 | #endregion 61 | 62 | #region Public Methods 63 | 64 | /// 65 | /// Methods returns tokenized tag according to conditions 66 | /// 67 | /// original value of the tokenized-tag 68 | /// 69 | /// 70 | /// Modified value of the tokenized tag 71 | public byte SetTagTokenIndications(byte token, bool hasAttributes, bool hasContent) 72 | { 73 | if (hasAttributes) 74 | token |= 0x80; 75 | if (hasContent) 76 | token |= 0x40; 77 | 78 | return token; 79 | } 80 | 81 | /// 82 | /// Methods writes text into stream, tokenized to be used in the WBXML document 83 | /// 84 | /// Output Stream 85 | /// Text tobe tokenized 86 | protected void WriteInlineString(MemoryStream stream, string text) 87 | { 88 | stream.WriteByte(WBXMLDocument.TOKEN_INLINE_STRING_FOLLOWS); 89 | 90 | byte[] bytes = Encoding.UTF8.GetBytes(text); 91 | stream.Write(bytes, 0, bytes.Length); 92 | 93 | stream.WriteByte(WBXMLDocument.NULL); // end of the string 94 | } 95 | 96 | /// 97 | /// Method writes date into stream, tokenized to be used in the WBXML document 98 | /// 99 | /// 100 | /// 101 | protected void WriteDate(MemoryStream stream, DateTime date) 102 | { 103 | byte[] buffer = new byte[7]; 104 | 105 | buffer[0] = Convert.ToByte(Convert.ToString(date.Year / 100), 16); 106 | buffer[1] = Convert.ToByte(Convert.ToString(date.Year % 100), 16); 107 | buffer[2] = Convert.ToByte(Convert.ToString(date.Month), 16); 108 | buffer[3] = Convert.ToByte(Convert.ToString(date.Day), 16); 109 | 110 | int dateLength = 4; 111 | 112 | if (date.Hour > 0) 113 | { 114 | buffer[4] = Convert.ToByte(Convert.ToString(date.Hour), 16); 115 | dateLength = 5; 116 | } 117 | 118 | if (date.Minute > 0) 119 | { 120 | buffer[5] = Convert.ToByte(Convert.ToString(date.Minute), 16); 121 | dateLength = 6; 122 | } 123 | 124 | if (date.Second > 0) 125 | { 126 | buffer[6] = Convert.ToByte(Convert.ToString(date.Second), 16); 127 | dateLength = 7; 128 | } 129 | 130 | // write to stream 131 | stream.WriteByte(WBXMLDocument.TOKEN_OPAQUEDATA_FOLLOWS); 132 | stream.WriteByte((byte)dateLength); 133 | stream.Write(buffer, 0, dateLength); 134 | } 135 | 136 | #endregion 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Samples/Modem.cs: -------------------------------------------------------------------------------- 1 | using SharpSMS; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO.Ports; 5 | using System.Threading; 6 | 7 | namespace Samples 8 | { 9 | class Modem 10 | { 11 | public Modem(string serialPortName, int baudRate) 12 | { 13 | commPort = new SerialPort(serialPortName, baudRate); 14 | } 15 | 16 | /// 17 | /// Send given message to the serial port and initialize modem 18 | /// 19 | /// 20 | public void SendSMS(SMSSubmit sms) 21 | { 22 | List messageList = sms.GetPDUList(); 23 | 24 | if (!InitModem()) 25 | Console.WriteLine("Error: No response from modem."); 26 | 27 | foreach (byte[] messagePart in messageList) 28 | { 29 | SendMessageToModem(messagePart); 30 | Thread.Sleep(500); 31 | Console.WriteLine("Message sent to modem."); 32 | } 33 | } 34 | 35 | /// 36 | /// Initialize modem 37 | /// 38 | /// True if modem was successfuly initialized 39 | bool InitModem() 40 | { 41 | try 42 | { 43 | if (!commPort.IsOpen) 44 | { 45 | WriteModemLog("Initializing modem....\r\n"); 46 | //commPort.Handshake = Handshake.RequestToSend; 47 | commPort.WriteBufferSize = 2048; 48 | commPort.ReadTimeout = 5000; 49 | commPort.ReadTimeout = 5000; 50 | commPort.Open(); 51 | commPort.NewLine = "\r"; 52 | 53 | string dataRead = string.Empty; 54 | int initLoop = 0; 55 | while (initLoop < 15) 56 | { 57 | commPort.WriteLine("AT"); 58 | commPort.WriteLine("AT"); 59 | commPort.WriteLine("AT"); 60 | dataRead = ReadFromPort(commPort, 1); 61 | 62 | if (dataRead.Contains("OK")) 63 | break; 64 | else 65 | initLoop++; 66 | } 67 | commPort.WriteLine("ATZ"); 68 | dataRead = ReadFromPort(commPort, 1); 69 | 70 | if (dataRead.Contains("OK")) 71 | return true; 72 | else 73 | return false; 74 | } 75 | } 76 | catch 77 | { 78 | return false; 79 | } 80 | 81 | return true; 82 | } 83 | 84 | /// 85 | /// Send message bytes to serial port modem 86 | /// 87 | /// 88 | /// 89 | bool SendMessageToModem(byte[] messageBytes) 90 | { 91 | bool retValue; 92 | if (commPort != null && commPort.IsOpen && messageBytes.Length > 0) 93 | { 94 | int messageLength = messageBytes.Length - 1; 95 | string message = BytesToHexString(messageBytes); 96 | string dataRead = string.Empty; 97 | commPort.WriteLine(string.Format("AT+CMGS={0}", messageLength)); 98 | Thread.Sleep(250); 99 | 100 | dataRead = ReadFromPort(commPort, 1); 101 | 102 | if (dataRead.Contains(">")) 103 | { 104 | commPort.Write(message); 105 | byte[] escChar = new byte[1] { 26 }; 106 | commPort.Write(escChar, 0, escChar.Length); 107 | Thread.Sleep(250); 108 | 109 | dataRead = "0000"; 110 | while (!(dataRead.Contains("OK") || dataRead.Contains("ERROR") || dataRead == string.Empty)) 111 | dataRead = ReadFromPort(commPort, 5); 112 | 113 | retValue = true; 114 | } 115 | else 116 | retValue = false; 117 | } 118 | else 119 | retValue = false; 120 | 121 | return retValue; 122 | } 123 | 124 | /// 125 | /// Reads string from given serial port 126 | /// 127 | /// SerialPort to read 128 | /// Time out for reading operations in seconds 129 | /// 130 | string ReadFromPort(SerialPort port, int secondsTimeOut) 131 | { 132 | int timeOut = 0; 133 | string dataRead = string.Empty; 134 | 135 | while (timeOut < secondsTimeOut * 10) 136 | { 137 | if (port.BytesToRead > 0) 138 | { 139 | dataRead = port.ReadExisting(); 140 | break; 141 | } 142 | timeOut++; 143 | Thread.Sleep(100); 144 | } 145 | 146 | WriteModemLog(dataRead); 147 | return dataRead; 148 | } 149 | 150 | /// 151 | /// Converts array of byte into string of hexa values 152 | /// 153 | /// 154 | /// 155 | string BytesToHexString(byte[] srcArray) 156 | { 157 | string retString = string.Empty; 158 | foreach (byte b in srcArray) 159 | retString += b.ToString("X2"); 160 | 161 | return retString; 162 | } 163 | 164 | /// 165 | /// Writes informations into modem log 166 | /// 167 | /// 168 | private void WriteModemLog(string message) 169 | { 170 | Console.WriteLine("Modem: "+message); 171 | } 172 | 173 | SerialPort commPort; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /SharpSMS/Wbxml/ServiceLoading.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace SharpSMS.Wbxml 7 | { 8 | /// 9 | /// Class represents Service Loading Messages 10 | /// 11 | public class ServiceLoading : WBXMLDocument 12 | { 13 | #region Constants 14 | // ServiceIndication 1.0 Public Identifier 15 | public const byte DOCUMENT_DTD_ServiceIndication = 0x06; 16 | 17 | // Tag Tokens 18 | public const byte TAGTOKEN_sl = 0x05; 19 | 20 | // Attribute Tokens 21 | public const byte ATTRIBUTESTARTTOKEN_action_execute_low = 0x05; 22 | public const byte ATTRIBUTESTARTTOKEN_action_execute_high = 0x06; 23 | public const byte ATTRIBUTESTARTTOKEN_action_cache = 0x07; 24 | public const byte ATTRIBUTESTARTTOKEN_href = 0x08; 25 | public const byte ATTRIBUTESTARTTOKEN_href_http = 0x09; // http:// 26 | public const byte ATTRIBUTESTARTTOKEN_href_http_www = 0x0A; // http://www. 27 | public const byte ATTRIBUTESTARTTOKEN_href_https = 0x0B; // https:// 28 | public const byte ATTRIBUTESTARTTOKEN_href_https_www = 0x0C; // https://www. 29 | 30 | // Attribute Value Tokens 31 | public const byte ATTRIBUTEVALUETOKEN_com = 0x85; // .com/ 32 | public const byte ATTRIBUTEVALUETOKEN_edu = 0x86; // .edu/ 33 | public const byte ATTRIBUTEVALUETOKEN_net = 0x87; // .net/ 34 | public const byte ATTRIBUTEVALUETOKEN_org = 0x88; // .org/ 35 | #endregion 36 | 37 | #region Properties 38 | 39 | /// 40 | /// Location of the file to download 41 | /// 42 | public string Href { get; set; } 43 | 44 | /// 45 | /// Action to do after download 46 | /// 47 | public ServiceLoadingAction Action { get; set; } 48 | #endregion 49 | 50 | #region Fields 51 | private Dictionary hrefStartTokens; 52 | private Dictionary attributeValueTokens; 53 | #endregion 54 | 55 | #region Constructors 56 | 57 | /// 58 | /// Default constructor 59 | /// 60 | public ServiceLoading() 61 | { 62 | // Set the content type 63 | this.ContentType = "application/vnd.wap.slc"; 64 | 65 | // Create token dictonary 66 | hrefStartTokens = new Dictionary(); 67 | hrefStartTokens.Add("https://www.", ATTRIBUTESTARTTOKEN_href_https_www); 68 | hrefStartTokens.Add("http://www.", ATTRIBUTESTARTTOKEN_href_http_www); 69 | hrefStartTokens.Add("https://", ATTRIBUTESTARTTOKEN_href_https); 70 | hrefStartTokens.Add("http://", ATTRIBUTESTARTTOKEN_href_http); 71 | 72 | attributeValueTokens = new Dictionary(); 73 | attributeValueTokens.Add(".com/", ATTRIBUTEVALUETOKEN_com); 74 | attributeValueTokens.Add(".edu/", ATTRIBUTEVALUETOKEN_edu); 75 | attributeValueTokens.Add(".net/", ATTRIBUTEVALUETOKEN_net); 76 | attributeValueTokens.Add(".org/", ATTRIBUTEVALUETOKEN_org); 77 | } 78 | 79 | /// 80 | /// Creates Service Loading Message 81 | /// 82 | /// Link to the file 83 | /// Action after download 84 | public ServiceLoading(string href, ServiceLoadingAction action) : this() 85 | { 86 | this.Href = href; 87 | this.Action = action; 88 | } 89 | 90 | /// 91 | /// Creates Service Loading Message 92 | /// 93 | /// Link to the file 94 | public ServiceLoading(string href) 95 | : this(href, ServiceLoadingAction.NotSet) 96 | { 97 | } 98 | #endregion 99 | 100 | #region Override Methods 101 | 102 | /// 103 | /// Returns tokenized Service Loading message. 104 | /// 105 | /// byte array 106 | public override byte[] GetWBXMLBytes() 107 | { 108 | MemoryStream wbxmlBytes = new MemoryStream(); 109 | 110 | // header 111 | wbxmlBytes.WriteByte(WBXMLDocument.VERSION_1_2); 112 | wbxmlBytes.WriteByte(DOCUMENT_DTD_ServiceIndication); 113 | wbxmlBytes.WriteByte(WBXMLDocument.CHARSET_UTF_8); 114 | wbxmlBytes.WriteByte(WBXMLDocument.NULL); // String table length 115 | 116 | // xml 117 | wbxmlBytes.WriteByte(SetTagTokenIndications(TAGTOKEN_sl, true, false)); // with attributes 118 | 119 | #region Tokenizing of the HREF 120 | 121 | // find the starting attribute token (http:// https:// etc..) 122 | int pos = 0; 123 | byte hrefTagToken = ATTRIBUTESTARTTOKEN_href; 124 | foreach (string startString in hrefStartTokens.Keys) 125 | { 126 | if (this.Href.StartsWith(startString)) 127 | { 128 | hrefTagToken = hrefStartTokens[startString]; 129 | pos = startString.Length; 130 | break; 131 | } 132 | } 133 | 134 | // find the `A` domain token. (.com .net .org etc..) 135 | int domainPos = -1; 136 | string domain = string.Empty; 137 | foreach (string domainString in attributeValueTokens.Keys) 138 | { 139 | domainPos = this.Href.IndexOf(domainString); 140 | if (domainPos >= 0) 141 | { 142 | domain = domainString; 143 | break; 144 | } 145 | } 146 | #endregion 147 | 148 | // write href url 149 | if (domainPos >= 0) // encode domain 150 | { 151 | wbxmlBytes.WriteByte(hrefTagToken); 152 | WriteInlineString(wbxmlBytes, Href.Substring(pos, domainPos - pos)); 153 | wbxmlBytes.WriteByte(attributeValueTokens[domain]); 154 | WriteInlineString(wbxmlBytes, Href.Substring(domainPos + domain.Length)); 155 | } 156 | else // no domain to encode 157 | { 158 | wbxmlBytes.WriteByte(hrefTagToken); 159 | WriteInlineString(wbxmlBytes, Href.Substring(pos)); 160 | } 161 | 162 | // writes action signal 163 | if (Action != ServiceLoadingAction.NotSet) 164 | wbxmlBytes.WriteByte(GetActionToken(Action)); 165 | 166 | // End tag 167 | wbxmlBytes.WriteByte(WBXMLDocument.TAGTOKEN_END); 168 | 169 | byte[] wbxmlBytesArray = wbxmlBytes.ToArray(); 170 | wbxmlBytes.Close(); 171 | 172 | return wbxmlBytesArray; 173 | } 174 | #endregion 175 | 176 | #region Private Methods 177 | 178 | /// 179 | /// Returns token for give Action 180 | /// 181 | /// Service Loading Action 182 | /// Token 183 | protected byte GetActionToken(ServiceLoadingAction action) 184 | { 185 | byte actionToken; 186 | 187 | switch (action) 188 | { 189 | case ServiceLoadingAction.Execute_high: 190 | actionToken = ATTRIBUTESTARTTOKEN_action_execute_high; 191 | break; 192 | case ServiceLoadingAction.Execute_low: 193 | actionToken = ATTRIBUTESTARTTOKEN_action_execute_low; 194 | break; 195 | default: 196 | actionToken = ATTRIBUTESTARTTOKEN_action_cache; 197 | break; 198 | } 199 | 200 | return actionToken; 201 | } 202 | #endregion 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /SharpSMS/MessageIndication.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSMS 2 | { 3 | /// 4 | /// Class represents Message Indication/Data Coding scheme of the message 5 | /// 6 | public class MessageIndication 7 | { 8 | #region Constants 9 | /// 10 | /// Coding Scheme Bit 4 set; message class is specified by least significant bits 1,0 11 | /// 12 | const byte CODING_SCHEME_MESSAGE_CLASS_SPECIFIED = 0x10; 13 | /// 14 | /// Coding Scheme Bits 3,2; default encoding 15 | /// 16 | const byte CODING_SCHEME_DEFAULT_ENCODING = 0x00; 17 | /// 18 | /// Coding Scheme Bits 3,2; 8-bit encoding 19 | /// 20 | const byte CODING_SCHEME_8BIT_ENCODING = 0x04; 21 | /// 22 | /// Coding Scheme Bits 3,2; UCS2 encoding 23 | /// 24 | const byte CODING_SCHEME_UCS2_ENCODING = 0x08; 25 | /// 26 | /// Coding Scheme Bits 1,0; Message Class - Immediate Display 27 | /// 28 | const byte CODING_SCHEME_MESSAGE_CLASS_IMMEDIATE_DISPLAY = 0x00; 29 | /// 30 | /// Coding Scheme Bits 1,0; Message Class - ME Specific 31 | /// 32 | const byte CODING_SCHEME_MESSAGE_CLASS_MESPECIFIC = 0x01; 33 | /// 34 | /// Coding Scheme Bits 1,0; Message Class - SIM Specific 35 | /// 36 | const byte CODING_SCHEME_MESSAGE_CLASS_SIMSPECIFIC = 0x02; 37 | /// 38 | /// Coding Scheme Bits 1,0; Message Class - TE Specific 39 | /// 40 | const byte CODING_SCHEME_MESSAGE_CLASS_TESPECIFIC = 0x03; 41 | /// 42 | /// Coding Scheme Bits 7..4; Message Indication Discard 43 | /// 44 | const byte CODING_SCHEME_MESSAGE_INDICATION_DISCARD = 0xC0; 45 | /// 46 | /// Coding Scheme Bits 7..4; Message Indication Store - default encoding 47 | /// 48 | const byte CODING_SCHEME_MESSAGE_INDICATION_STORE_DEFAULT_ENCODING = 0xD0; 49 | /// 50 | /// Coding Scheme Bits 7..4; Message Indication Store - UCS2 encoding 51 | /// 52 | const byte CODING_SCHEME_MESSAGE_INDICATION_STORE_UCS2_ENCODING = 0xE0; 53 | /// 54 | /// Coding Scheme Bit 3; Message Indication set to active 55 | /// 56 | const byte CODING_SCHEME_INDICATION_ACTIVE = 0x08; 57 | /// 58 | /// Coding Scheme Bit 1,0; Message Indication - Voicemail 59 | /// 60 | const byte CODING_SCHEME_INDICATION_VOICEMAIL = 0x00; 61 | /// 62 | /// Coding Scheme Bit 1,0; Message Indication - Fax 63 | /// 64 | const byte CODING_SCHEME_INDICATION_FAX = 0x01; 65 | /// 66 | /// Coding Scheme Bit 1,0; Message Indication - Email 67 | /// 68 | const byte CODING_SCHEME_INDICATION_EMAIL = 0x02; 69 | /// 70 | /// Coding Scheme Bit 1,0; Message Indication - Other 71 | /// 72 | const byte CODING_SCHEME_INDICATION_OTHER = 0x03; 73 | #endregion 74 | 75 | /// 76 | /// Creates new message indication with default options 77 | /// 78 | public MessageIndication() 79 | : this(MessageClass.MESpecific) 80 | { 81 | } 82 | 83 | /// 84 | /// Creates new message indication 85 | /// 86 | /// Define if and where message will be stored 87 | public MessageIndication(MessageClass messageClass) 88 | { 89 | this.Class = messageClass; 90 | this.Type = IndicationType.Voicemail; 91 | this.Operation = MessageIndicationOperation.NotSet; 92 | this.IsActive = false; 93 | } 94 | 95 | /// 96 | /// Returns TP-DCS Data coding scheme value that represents the message indication information 97 | /// 98 | /// Data encoding used in the message 99 | /// 100 | public byte ToByte(DataEncoding dataEncoding) 101 | { 102 | byte dataCodingScheme = CODING_SCHEME_MESSAGE_CLASS_SPECIFIED; 103 | 104 | // Set encoding 105 | if (dataEncoding == DataEncoding.Default7bit) 106 | dataCodingScheme |= CODING_SCHEME_DEFAULT_ENCODING; 107 | else if (dataEncoding == DataEncoding.Data8bit) 108 | dataCodingScheme |= CODING_SCHEME_8BIT_ENCODING; 109 | else if (dataEncoding == DataEncoding.UCS2_16bit) 110 | dataCodingScheme |= CODING_SCHEME_UCS2_ENCODING; 111 | 112 | // Set indication 113 | if (this.Class == SharpSMS.MessageClass.ImmediateDisplay) 114 | dataCodingScheme |= CODING_SCHEME_MESSAGE_CLASS_IMMEDIATE_DISPLAY; 115 | else if (this.Class == SharpSMS.MessageClass.MESpecific) 116 | dataCodingScheme |= CODING_SCHEME_MESSAGE_CLASS_MESPECIFIC; 117 | else if (this.Class == SharpSMS.MessageClass.SIMSpecific) 118 | dataCodingScheme |= CODING_SCHEME_MESSAGE_CLASS_SIMSPECIFIC; 119 | else if (this.Class == SharpSMS.MessageClass.TESpecific) 120 | dataCodingScheme |= CODING_SCHEME_MESSAGE_CLASS_TESPECIFIC; 121 | 122 | if (this.Operation != MessageIndicationOperation.NotSet) 123 | dataCodingScheme = GetWaitingIndication(dataEncoding); 124 | 125 | return dataCodingScheme; 126 | } 127 | 128 | /// 129 | /// Returns coding scheme part for MessageWaitingIndication 130 | /// 131 | /// 132 | private byte GetWaitingIndication(DataEncoding dataEncoding) 133 | { 134 | byte result = 0x00; 135 | 136 | // Is it Discard or Store? Store depends on alphabet encoding 137 | if (this.Operation == MessageIndicationOperation.Discard) 138 | result |= CODING_SCHEME_MESSAGE_INDICATION_DISCARD; 139 | else if (this.Operation == MessageIndicationOperation.Store && dataEncoding == DataEncoding.Default7bit) 140 | result |= CODING_SCHEME_MESSAGE_INDICATION_STORE_DEFAULT_ENCODING; 141 | else if (this.Operation == MessageIndicationOperation.Store && dataEncoding == DataEncoding.UCS2_16bit) 142 | result |= CODING_SCHEME_MESSAGE_INDICATION_STORE_UCS2_ENCODING; 143 | 144 | // Activeting or Deactivating Indication 145 | if (this.IsActive) 146 | result |= CODING_SCHEME_INDICATION_ACTIVE; 147 | 148 | switch (this.Type) 149 | { 150 | case IndicationType.Voicemail: 151 | result |= CODING_SCHEME_INDICATION_VOICEMAIL; 152 | break; 153 | case IndicationType.FaxMessage: 154 | result |= CODING_SCHEME_INDICATION_FAX; 155 | break; 156 | case IndicationType.EmailMessage: 157 | result |= CODING_SCHEME_INDICATION_EMAIL; 158 | break; 159 | case IndicationType.OtherMessage: 160 | result |= CODING_SCHEME_INDICATION_OTHER; 161 | break; 162 | } 163 | 164 | return result; 165 | } 166 | 167 | /// 168 | /// Message classs pecifies how message will be treated on recipients device 169 | /// 170 | public MessageClass Class { get; set; } 171 | 172 | /// 173 | /// Type of the Indication (Voicemail, Fax, Email ...) 174 | /// 175 | public IndicationType Type { get; set; } 176 | 177 | /// 178 | /// Defines if indication message will be discarded or stored on the device 179 | /// 180 | public MessageIndicationOperation Operation { get; set; } 181 | 182 | /// 183 | /// True sets indication to active state. False removes the indication 184 | /// 185 | public bool IsActive { get; set; } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /SharpSMS/Wap/WapPushMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | using System.Security.Cryptography; 6 | using SharpSMS.Wbxml; 7 | 8 | namespace SharpSMS.Wap 9 | { 10 | /// 11 | /// Class representing Wap Push sms 12 | /// 13 | public class WapPushMessage : WdpMessage, ISmsMessageContent 14 | { 15 | #region Constructors 16 | 17 | /// 18 | /// Creates instance of the WapPushMessage. Data will be taken from WBXML document 19 | /// 20 | public WapPushMessage() 21 | { 22 | this.DestinationPort = Wdp.WAP_PORT_PUSH_SESSION_DESTINATION; 23 | this.SourcePort = Wdp.WAP_PORT_PUSH_SESSION_SOURCE; 24 | 25 | this.DataEncoding = DataEncoding.Data8bit; 26 | this.PushFlag = 0x00; 27 | this.Security = Wsp.SecurityMethod.None; 28 | this.ContentType = "application/vnd.wap.connectivity-wbxml"; 29 | } 30 | 31 | /// 32 | /// Creates instance of the WapPushMessage 33 | /// 34 | /// Tokenized XML message 35 | public WapPushMessage(WBXMLDocument wbxmlDocument) 36 | : this() 37 | { 38 | this.Data = wbxmlDocument.GetWBXMLBytes(); 39 | this.ContentType = wbxmlDocument.ContentType; 40 | } 41 | 42 | /// 43 | /// Creates instance of the WapPushMessage 44 | /// 45 | /// Binary data representing the message 46 | public WapPushMessage(byte[] data) 47 | { 48 | this.Data = data; 49 | } 50 | 51 | /// 52 | /// Creates instance of the WapPushMessage 53 | /// 54 | /// Message content 55 | public WapPushMessage(string message) 56 | { 57 | this.Data = Encoding.UTF8.GetBytes(message); 58 | this.DataEncoding = DataEncoding.Data8bit; 59 | } 60 | 61 | #endregion 62 | 63 | #region Public methods 64 | 65 | /// 66 | /// Returns byte array suitable to be send in sms message. All wraped in the Wap header 67 | /// 68 | /// byte array 69 | public byte[] GetSMSBytes() 70 | { 71 | byte[] headerBuffer = GetWAPWSPHeaderBytes(); 72 | 73 | MemoryStream stream = new MemoryStream(); 74 | stream.Write(headerBuffer, 0, headerBuffer.Length); 75 | stream.Write(this.Data, 0, this.Data.Length); 76 | return stream.ToArray(); 77 | } 78 | 79 | /// 80 | /// Generates the WDP (Wireless Datagram Protocol) or UDH (User Data Header) for the 81 | /// SMS message. In the case comprising the Application Port information element 82 | /// indicating to the handset which application to start on receipt of the message 83 | /// 84 | /// byte array comprising the header 85 | public byte[] GetUDHBytes() 86 | { 87 | return this.GetWdpHeader(); 88 | } 89 | 90 | /// 91 | /// Get security MAC for the message 92 | /// 93 | /// 94 | public byte[] GetSecurityKey() 95 | { 96 | byte[] key = new byte[] { 0x00 }; 97 | 98 | switch (Security) 99 | { 100 | case Wsp.SecurityMethod.NETWPIN: 101 | key = ImsitoKey(this.NetworkPin); 102 | break; 103 | case Wsp.SecurityMethod.USERPIN: 104 | key = Encoding.ASCII.GetBytes(this.UserPin); 105 | break; 106 | case Wsp.SecurityMethod.USERNETWPIN: 107 | byte[] userPin = Encoding.ASCII.GetBytes(this.UserPin); 108 | byte[] netPin = ImsitoKey(this.NetworkPin); 109 | key = new byte[userPin.Length + netPin.Length]; 110 | netPin.CopyTo(key, 0); 111 | userPin.CopyTo(key, netPin.Length); 112 | break; 113 | case Wsp.SecurityMethod.USERPINMAC: 114 | throw new NotSupportedException("USERPINMAC is not supported"); 115 | } 116 | 117 | return key; 118 | } 119 | #endregion 120 | 121 | #region Private methods 122 | 123 | /// Generates the WAP and WSP (Wireless Session Protocol) headers with the well known 124 | /// byte values specfic to a ContentType 125 | private byte[] GetWAPWSPHeaderBytes() 126 | { 127 | MemoryStream stream = new MemoryStream(); 128 | stream.WriteByte(Wsp.TRANSACTIONID_CONNECTIONLESSWSP); 129 | stream.WriteByte(Wsp.PDUTYPE_PUSH); 130 | 131 | #region WSP Header 132 | MemoryStream headersStream = new MemoryStream(); 133 | 134 | #region Security header 135 | MemoryStream securityStream = new MemoryStream(); 136 | 137 | // Write content type 138 | Wsp.WriteHeaderContentType(securityStream, this.ContentType); 139 | 140 | // Accept charset header 0x01 | 0x80 = 0x81 141 | // Accept charset value: UTF8 = 0x61 | 0x80 = 0xEA 142 | 143 | // Security method and MAC 144 | if (this.Security != Wsp.SecurityMethod.None) 145 | { 146 | byte[] macKey = ComputeMac(GetSecurityKey(), this.Data); 147 | Wsp.WriteHeaderSecurity(securityStream, this.Security, macKey); 148 | } 149 | 150 | // Security Header Length 151 | Wsp.WriteValueLength(headersStream, securityStream.Length); 152 | 153 | // Write security header to header stream 154 | securityStream.WriteTo(headersStream); 155 | #endregion 156 | 157 | // Push Flag 158 | if (this.PushFlag != 0x00) 159 | Wsp.WriteHeaderPushFlag(headersStream, this.PushFlag); 160 | 161 | // X WAP Initiator URI 162 | if (!string.IsNullOrEmpty(this.XWapInitiatorURI)) 163 | Wsp.WriteHeaderXWAPInitiatorURI(headersStream, this.XWapInitiatorURI); 164 | 165 | // X WAP Content URI 166 | if (!string.IsNullOrEmpty(this.XWapContentURI)) 167 | Wsp.WriteHeaderXWAPContentURI(headersStream, this.XWapContentURI); 168 | 169 | // X WAP Application ID 170 | if (!string.IsNullOrEmpty(this.XWapApplicationType)) 171 | Wsp.WriteHeaderXWAPApplicationID(headersStream, this.XWapApplicationType); 172 | 173 | // Write complete header length 174 | stream.WriteByte((byte)headersStream.Length); 175 | headersStream.WriteTo(stream); 176 | #endregion 177 | 178 | return stream.ToArray(); 179 | } 180 | 181 | private byte[] ComputeMac(byte[] key, byte[] message) 182 | { 183 | HMACSHA1 hmac = new HMACSHA1(key, true); 184 | return hmac.ComputeHash(message); 185 | } 186 | 187 | private byte[] ImsitoKey(string imsi) 188 | { 189 | imsi = imsi.Trim(); 190 | 191 | if ((imsi.Length % 2) == 1) 192 | { 193 | imsi = "9" + imsi; 194 | } 195 | else 196 | { 197 | imsi = "1" + imsi; 198 | imsi = imsi + "F"; 199 | } 200 | 201 | int numDigit = imsi.Length; 202 | string temp; 203 | char c1; 204 | char c2; 205 | byte[] key = new byte[numDigit / 2]; // always even 206 | int t = 0; 207 | for (int i = 0; i < numDigit; i++) 208 | { 209 | c1 = imsi[i]; 210 | c2 = imsi[++i]; 211 | temp = "" + c2 + c1; 212 | 213 | if (!byte.TryParse(temp, System.Globalization.NumberStyles.HexNumber, null, out key[t])) 214 | { 215 | throw new Exception("No chars in IMSI"); 216 | } 217 | 218 | t++; 219 | } 220 | 221 | return key; 222 | } 223 | 224 | #endregion 225 | 226 | #region Properties 227 | /// 228 | /// Encoding of the data (7-bit, 8-bit, 16-bit) 229 | /// 230 | public DataEncoding DataEncoding { get; set; } 231 | 232 | /// 233 | /// MIME Content type of the message 234 | /// 235 | public string ContentType { get; set; } 236 | 237 | /// 238 | /// Data (body) of the message that will be wrapped into the WAP push headers 239 | /// 240 | public byte[] Data { get; set; } 241 | 242 | /// 243 | /// X-Wap-Initiator-URI 244 | /// 245 | public string XWapInitiatorURI { get; set; } 246 | 247 | /// 248 | /// X-Wap-Content-URI 249 | /// 250 | public string XWapContentURI { get; set; } 251 | 252 | /// 253 | /// X-Wap-Application-ID Type 254 | /// 255 | public string XWapApplicationType { get; set; } 256 | 257 | /// 258 | /// Push Flag 259 | /// 260 | public byte PushFlag { get; set; } 261 | 262 | /// 263 | /// Security method 264 | /// 265 | public Wsp.SecurityMethod Security { get; set; } 266 | 267 | /// 268 | /// User shared secret 269 | /// 270 | public string UserPin { get; set; } 271 | 272 | /// 273 | /// Network pin (IMSI) - Subscriber ID 274 | /// 275 | public string NetworkPin { get; set; } 276 | 277 | #endregion 278 | } 279 | 280 | } 281 | -------------------------------------------------------------------------------- /SharpSMS/Wbxml/ServiceIndication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace SharpSMS.Wbxml 7 | { 8 | /// 9 | /// Class represents Service Indication Messages 10 | /// 11 | public class ServiceIndication : WBXMLDocument 12 | { 13 | #region Constants 14 | // ServiceIndication 1.0 Public Identifier 15 | public const byte DOCUMENT_DTD_ServiceIndication = 0x05; 16 | 17 | // Tag Tokens 18 | public const byte TAGTOKEN_si = 0x05; 19 | public const byte TAGTOKEN_indication = 0x06; 20 | public const byte TAGTOKEN_info = 0x07; 21 | public const byte TAGTOKEN_item = 0x08; 22 | 23 | // Attribute Tokens 24 | public const byte ATTRIBUTESTARTTOKEN_action_signal_none = 0x05; 25 | public const byte ATTRIBUTESTARTTOKEN_action_signal_low = 0x06; 26 | public const byte ATTRIBUTESTARTTOKEN_action_signal_medium = 0x07; 27 | public const byte ATTRIBUTESTARTTOKEN_action_signal_high = 0x08; 28 | public const byte ATTRIBUTESTARTTOKEN_action_signal_delete = 0x09; 29 | public const byte ATTRIBUTESTARTTOKEN_created = 0x0A; 30 | public const byte ATTRIBUTESTARTTOKEN_href = 0x0B; 31 | public const byte ATTRIBUTESTARTTOKEN_href_http = 0x0C; // http:// 32 | public const byte ATTRIBUTESTARTTOKEN_href_http_www = 0x0D; // http://www. 33 | public const byte ATTRIBUTESTARTTOKEN_href_https = 0x0E; // https:// 34 | public const byte ATTRIBUTESTARTTOKEN_href_https_www = 0x0F; // https://www. 35 | public const byte ATTRIBUTESTARTTOKEN_si_expires = 0x10; 36 | public const byte ATTRIBUTESTARTTOKEN_si_id = 0x11; 37 | public const byte ATTRIBUTESTARTTOKEN_class = 0x12; 38 | 39 | // Attribute Value Tokens 40 | public const byte ATTRIBUTEVALUETOKEN_com = 0x85; // .com/ 41 | public const byte ATTRIBUTEVALUETOKEN_edu = 0x86; // .edu/ 42 | public const byte ATTRIBUTEVALUETOKEN_net = 0x87; // .net/ 43 | public const byte ATTRIBUTEVALUETOKEN_org = 0x88; // .org/ 44 | #endregion 45 | 46 | #region Properties 47 | /// 48 | /// Text of the service Indication Message 49 | /// 50 | public string Text { get; set; } 51 | 52 | /// 53 | /// Href - link in the Service Indication Message 54 | /// 55 | public string Href { get; set; } 56 | 57 | /// 58 | /// Level of notification to the user (optional) 59 | /// 60 | public ServiceIndicationAction Action { get; set; } 61 | 62 | /// 63 | /// Creation DateTime of the SI message (optional) 64 | /// 65 | public DateTime Created {get; set; } 66 | 67 | /// 68 | /// Expiration DateTime of the SI message (optional) 69 | /// 70 | public DateTime Expires { get; set; } 71 | 72 | /// 73 | /// ID of the SI message (optional) 74 | /// 75 | public string Id { get; set; } 76 | #endregion 77 | 78 | #region Fields 79 | private Dictionary hrefStartTokens; 80 | private Dictionary attributeValueTokens; 81 | #endregion 82 | 83 | #region Constructors 84 | /// 85 | /// Default constructor 86 | /// 87 | public ServiceIndication() 88 | { 89 | // Set the content type 90 | this.ContentType = "application/vnd.wap.sic"; // HEADER_CONTENTTYPE_application_vnd_wap_sic; 91 | 92 | // Create token dictonary 93 | hrefStartTokens = new Dictionary(); 94 | hrefStartTokens.Add("https://www.", ATTRIBUTESTARTTOKEN_href_https_www); 95 | hrefStartTokens.Add("http://www.", ATTRIBUTESTARTTOKEN_href_http_www); 96 | hrefStartTokens.Add("https://", ATTRIBUTESTARTTOKEN_href_https); 97 | hrefStartTokens.Add("http://", ATTRIBUTESTARTTOKEN_href_http); 98 | 99 | attributeValueTokens = new Dictionary(); 100 | attributeValueTokens.Add(".com/", ATTRIBUTEVALUETOKEN_com); 101 | attributeValueTokens.Add(".edu/", ATTRIBUTEVALUETOKEN_edu); 102 | attributeValueTokens.Add(".net/", ATTRIBUTEVALUETOKEN_net); 103 | attributeValueTokens.Add(".org/", ATTRIBUTEVALUETOKEN_org); 104 | } 105 | 106 | /// 107 | /// Creates Service Indication Message 108 | /// 109 | /// Text of the message 110 | /// Link in the message 111 | /// Action to the user 112 | public ServiceIndication(string text, string href, ServiceIndicationAction action) : this() 113 | { 114 | this.Text = text; 115 | this.Href = href; 116 | this.Action = action; 117 | } 118 | 119 | /// 120 | /// Creates Service Indication Message 121 | /// 122 | /// Text of the message 123 | /// Link in the message 124 | public ServiceIndication(string text, string href) 125 | : this(text, href, ServiceIndicationAction.NotSet) 126 | { 127 | } 128 | 129 | #endregion 130 | 131 | #region Override Methods 132 | 133 | /// 134 | /// Returns tokenized Service Indication message. 135 | /// 136 | /// byte array 137 | public override byte[] GetWBXMLBytes() 138 | { 139 | MemoryStream wbxmlBytes = new MemoryStream(); 140 | 141 | // header 142 | wbxmlBytes.WriteByte(WBXMLDocument.VERSION_1_2); 143 | wbxmlBytes.WriteByte(DOCUMENT_DTD_ServiceIndication); 144 | wbxmlBytes.WriteByte(WBXMLDocument.CHARSET_UTF_8); 145 | wbxmlBytes.WriteByte(WBXMLDocument.NULL); // String table length 146 | 147 | // xml 148 | wbxmlBytes.WriteByte(SetTagTokenIndications(TAGTOKEN_si, false, true)); // with content 149 | wbxmlBytes.WriteByte(SetTagTokenIndications(TAGTOKEN_indication, true, true)); // with cont. and attr. 150 | 151 | #region Tokenizing of the HREF 152 | 153 | // find the starting attribute token (http:// https:// etc..) 154 | int pos = 0; 155 | byte hrefTagToken = ATTRIBUTESTARTTOKEN_href; 156 | foreach (string startString in hrefStartTokens.Keys) 157 | { 158 | if (this.Href.StartsWith(startString)) 159 | { 160 | hrefTagToken = hrefStartTokens[startString]; 161 | pos = startString.Length; 162 | break; 163 | } 164 | } 165 | 166 | // find the `A` domain token. (.com .net .org etc..) 167 | int domainPos = -1; 168 | string domain = string.Empty; 169 | foreach (string domainString in attributeValueTokens.Keys) 170 | { 171 | domainPos = this.Href.IndexOf(domainString); 172 | if (domainPos >= 0) 173 | { 174 | domain = domainString; 175 | break; 176 | } 177 | } 178 | #endregion 179 | 180 | // write href url 181 | if (domainPos >= 0) // encode domain 182 | { 183 | wbxmlBytes.WriteByte(hrefTagToken); 184 | WriteInlineString(wbxmlBytes, Href.Substring(pos, domainPos - pos)); 185 | wbxmlBytes.WriteByte(attributeValueTokens[domain]); 186 | WriteInlineString(wbxmlBytes, Href.Substring(domainPos + domain.Length)); 187 | } 188 | else // no domain to encode 189 | { 190 | wbxmlBytes.WriteByte(hrefTagToken); 191 | WriteInlineString(wbxmlBytes, Href.Substring(pos)); 192 | } 193 | 194 | // writes action signal 195 | if (Action != ServiceIndicationAction.NotSet) 196 | wbxmlBytes.WriteByte(GetActionToken(Action)); 197 | 198 | // writes Created dateTime signal (if set) 199 | if (Created != DateTime.MinValue) 200 | { 201 | wbxmlBytes.WriteByte(ATTRIBUTESTARTTOKEN_created); 202 | WriteDate(wbxmlBytes, Created); 203 | } 204 | 205 | // writes Experi dateTime signal (if set) 206 | if (Expires != DateTime.MinValue) 207 | { 208 | wbxmlBytes.WriteByte(ATTRIBUTESTARTTOKEN_si_expires); 209 | WriteDate(wbxmlBytes, Expires); 210 | } 211 | 212 | // writes Si-id (if set) 213 | if (Id != null) 214 | { 215 | wbxmlBytes.WriteByte(ATTRIBUTESTARTTOKEN_si_id); 216 | WriteInlineString(wbxmlBytes, Id); 217 | } 218 | 219 | // end indication 220 | wbxmlBytes.WriteByte(WBXMLDocument.TAGTOKEN_END); // 221 | 222 | // Writes texts 223 | WriteInlineString(wbxmlBytes, Text); 224 | 225 | // End tags 226 | wbxmlBytes.WriteByte(WBXMLDocument.TAGTOKEN_END); 227 | wbxmlBytes.WriteByte(WBXMLDocument.TAGTOKEN_END); 228 | 229 | byte[] wbxmlBytesArray = wbxmlBytes.ToArray(); 230 | wbxmlBytes.Close(); 231 | 232 | return wbxmlBytesArray; 233 | } 234 | 235 | #endregion 236 | 237 | #region Private Methods 238 | 239 | /// 240 | /// Returns token for give Action 241 | /// 242 | /// Service Indication Action 243 | /// Token 244 | protected byte GetActionToken(ServiceIndicationAction action) 245 | { 246 | byte actionToken; 247 | 248 | switch (action) 249 | { 250 | case ServiceIndicationAction.Delete: 251 | actionToken = ATTRIBUTESTARTTOKEN_action_signal_delete; 252 | break; 253 | case ServiceIndicationAction.Signal_high: 254 | actionToken = ATTRIBUTESTARTTOKEN_action_signal_high; 255 | break; 256 | case ServiceIndicationAction.Signal_low: 257 | actionToken = ATTRIBUTESTARTTOKEN_action_signal_low; 258 | break; 259 | case ServiceIndicationAction.Signal_medium: 260 | actionToken = ATTRIBUTESTARTTOKEN_action_signal_medium; 261 | break; 262 | default: 263 | actionToken = ATTRIBUTESTARTTOKEN_action_signal_none; 264 | break; 265 | } 266 | 267 | return actionToken; 268 | } 269 | #endregion 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /SharpSMS/Wap/Wsp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace SharpSMS.Wap 7 | { 8 | /// 9 | /// Helper static class representing Wireless Session Protocol 10 | /// 11 | public class Wsp 12 | { 13 | #region Constans 14 | public const byte TRANSACTIONID_CONNECTIONLESSWSP = 0x01; // Can be random number 15 | public const byte PDUTYPE_PUSH = 0x06; 16 | 17 | public const byte WSP_ENCODING_VERSION_1_1 = 0x11; 18 | public const byte WSP_ENCODING_VERSION_1_2 = 0x12; 19 | public const byte WSP_ENCODING_VERSION_1_3 = 0x13; 20 | public const byte WSP_ENCODING_VERSION_1_4 = 0x14; 21 | 22 | public const byte HEADER_PUSHFLAG = 0x34; // 0x34 | 0x80 23 | public const byte HEADER_X_WAP_CONTENT_URI = 0x30; // 0x30 | 0x80 24 | public const byte HEADER_X_WAP_INITIATOR_URI = 0x31; // 0x31 | 0x80 25 | public const byte HEADER_X_WAP_APPLICATION_ID = 0x2F; // 0x2F | 0x80 26 | public const byte HEADER_SEC = 0x11; // 0x11 | 0x80 27 | public const byte HEADER_MAC = 0x12; // 0x12 | 0x80 28 | 29 | public const byte SEC_NETWPIN = 0x00; 30 | public const byte SEC_USERPIN = 0x01; 31 | public const byte SEC_USERNETWPIN = 0x02; 32 | public const byte SEC_USERPINMAC = 0x03; 33 | 34 | #endregion 35 | 36 | /// 37 | /// Static constructor 38 | /// 39 | static Wsp() 40 | { 41 | // Fill the dictionary 42 | WspContentTypes = new Dictionary(); 43 | WspContentTypes.Add("*/*", 0x00); 44 | WspContentTypes.Add("text/*", 0x01); 45 | WspContentTypes.Add("text/html", 0x02); 46 | WspContentTypes.Add("text/plain", 0x03); 47 | WspContentTypes.Add("text/x-hdml", 0x04); 48 | WspContentTypes.Add("text/x-ttml", 0x05); 49 | WspContentTypes.Add("text/x-vCalendar", 0x06); 50 | WspContentTypes.Add("text/x-vCard", 0x07); 51 | WspContentTypes.Add("text/vnd.wap.wml", 0x08); 52 | WspContentTypes.Add("text/vnd.wap.wmlscript", 0x09); 53 | WspContentTypes.Add("text/vnd.wap.wta-event", 0x0A); 54 | WspContentTypes.Add("multipart/*", 0x0B); 55 | WspContentTypes.Add("multipart/mixed", 0x0C); 56 | WspContentTypes.Add("multipart/form-data", 0x0D); 57 | WspContentTypes.Add("multipart/byteranges", 0x0E); 58 | WspContentTypes.Add("multipart/alternative", 0x0F); 59 | WspContentTypes.Add("application/*", 0x10); 60 | WspContentTypes.Add("application/java-vm", 0x11); 61 | WspContentTypes.Add("application/x-www-form-urlencoded", 0x12); 62 | WspContentTypes.Add("application/x-hdmlc", 0x13); 63 | WspContentTypes.Add("application/vnd.wap.wmlc", 0x14); 64 | WspContentTypes.Add("application/vnd.wap.wmlscriptc", 0x15); 65 | WspContentTypes.Add("application/vnd.wap.wta-eventc", 0x16); 66 | WspContentTypes.Add("application/vnd.wap.uaprof", 0x17); 67 | WspContentTypes.Add("application/vnd.wap.wtls-ca-certificate", 0x18); 68 | WspContentTypes.Add("application/vnd.wap.wtls-user-certificate", 0x19); 69 | WspContentTypes.Add("application/x-x509-ca-cert", 0x1A); 70 | WspContentTypes.Add("application/x-x509-user-cert", 0x1B); 71 | WspContentTypes.Add("image/*", 0x1C); 72 | WspContentTypes.Add("image/gif", 0x1D); 73 | WspContentTypes.Add("image/jpeg", 0x1E); 74 | WspContentTypes.Add("image/tiff", 0x1F); 75 | WspContentTypes.Add("image/png", 0x20); 76 | WspContentTypes.Add("image/vnd.wap.wbmp", 0x21); 77 | WspContentTypes.Add("application/vnd.wap.multipart.*", 0x22); 78 | WspContentTypes.Add("application/vnd.wap.multipart.mixed", 0x23); 79 | WspContentTypes.Add("application/vnd.wap.multipart.form-data", 0x24); 80 | WspContentTypes.Add("application/vnd.wap.multipart.byteranges", 0x25); 81 | WspContentTypes.Add("application/vnd.wap.multipart.alternative", 0x26); 82 | WspContentTypes.Add("application/xml", 0x27); 83 | WspContentTypes.Add("text/xml", 0x28); 84 | WspContentTypes.Add("application/vnd.wap.wbxml", 0x29); 85 | WspContentTypes.Add("application/x-x968-cross-cert", 0x2A); 86 | WspContentTypes.Add("application/x-x968-ca-cert", 0x2B); 87 | WspContentTypes.Add("application/x-x968-user-cert", 0x2C); 88 | WspContentTypes.Add("text/vnd.wap.si", 0x2D); 89 | 90 | // WSP 1.2 91 | WspContentTypes.Add("application/vnd.wap.sic", 0x2E); 92 | WspContentTypes.Add("text/vnd.wap.sl", 0x2F); 93 | WspContentTypes.Add("application/vnd.wap.slc", 0x30); 94 | WspContentTypes.Add("text/vnd.wap.co", 0x31); 95 | WspContentTypes.Add("application/vnd.wap.coc", 0x32); 96 | WspContentTypes.Add("application/vnd.wap.multipart.related", 0x33); 97 | WspContentTypes.Add("application/vnd.wap.sia", 0x34); 98 | 99 | // WSP 1.3 100 | WspContentTypes.Add("text/vnd.wap.connectivity-xml", 0x35); 101 | WspContentTypes.Add("application/vnd.wap.connectivity-wbxml", 0x36); 102 | 103 | // WSP 1.4 104 | WspContentTypes.Add("application/pkcs7-mime", 0x37); 105 | WspContentTypes.Add("application/vnd.wap.hashed-certificate", 0x38); 106 | WspContentTypes.Add("application/vnd.wap.signed-certificate", 0x39); 107 | WspContentTypes.Add("application/vnd.wap.cert-response", 0x3A); 108 | WspContentTypes.Add("application/xhtml+xml", 0x3B); 109 | WspContentTypes.Add("application/wml+xml", 0x3C); 110 | WspContentTypes.Add("text/css", 0x3D); 111 | WspContentTypes.Add("application/vnd.wap.mms-message", 0x3E); 112 | WspContentTypes.Add("application/vnd.wap.rollover-certificate", 0x3F); 113 | 114 | // WSP 1.5 115 | WspContentTypes.Add("application/vnd.wap.locc+wbxml", 0x40); 116 | WspContentTypes.Add("application/vnd.wap.loc+xml", 0x41); 117 | WspContentTypes.Add("application/vnd.syncml.dm+wbxml", 0x42); 118 | WspContentTypes.Add("application/vnd.syncml.dm+xml", 0x43); 119 | WspContentTypes.Add("application/vnd.syncml.notification", 0x44); 120 | WspContentTypes.Add("application/vnd.wap.xhtml+xml", 0x45); 121 | WspContentTypes.Add("application/vnd.wv.csp.cir", 0x46); 122 | WspContentTypes.Add("application/vnd.oma.dd+xml", 0x47); 123 | WspContentTypes.Add("application/vnd.oma.drm.message", 0x48); 124 | WspContentTypes.Add("application/vnd.oma.drm.content", 0x49); 125 | WspContentTypes.Add("application/vnd.oma.drm.rights+xml", 0x4A); 126 | WspContentTypes.Add("application/vnd.oma.drm.rights+wbxml", 0x4B); 127 | 128 | // http://www.wapforum.org/wina/push-app-id.htm 129 | WspPushAppTypes = new Dictionary(); 130 | WspPushAppTypes.Add("x-wap-application:*", 0x00); 131 | WspPushAppTypes.Add("x-wap-application:push.sia", 0x01); 132 | WspPushAppTypes.Add("x-wap-application:wml.ua", 0x02); 133 | WspPushAppTypes.Add("x-wap-application:wta.ua", 0x03); 134 | WspPushAppTypes.Add("x-wap-application:mms.ua", 0x04); 135 | WspPushAppTypes.Add("x-wap-application:push.syncml", 0x05); 136 | WspPushAppTypes.Add("x-wap-application:loc.ua", 0x06); 137 | WspPushAppTypes.Add("x-wap-application:syncml.dm", 0x07); 138 | WspPushAppTypes.Add("x-wap-application:drm.ua", 0x08); 139 | WspPushAppTypes.Add("x-wap-application:emn.ua", 0x09); 140 | WspPushAppTypes.Add("x-wap-application:wv.ua", 0x0A); 141 | 142 | WspPushAppTypes.Add("x-wap-microsoft:localcontent.ua", 0x8000); 143 | WspPushAppTypes.Add("x-wap-microsoft:imclient.ua ", 0x8001); 144 | WspPushAppTypes.Add("x-wap-docomo:imode.mail.ua ", 0x8002); 145 | WspPushAppTypes.Add("x-wap-docomo:imode.mr.ua", 0x8003); 146 | WspPushAppTypes.Add("x-wap-docomo:imode.mf.ua", 0x8004); 147 | WspPushAppTypes.Add("x-motorola:location.ua ", 0x8005); 148 | WspPushAppTypes.Add("x-motorola:now.ua", 0x8006); 149 | WspPushAppTypes.Add("x-motorola:otaprov.ua", 0x8007); 150 | WspPushAppTypes.Add("x-motorola:browser.ua", 0x8008); 151 | WspPushAppTypes.Add("x-motorola:splash.ua", 0x8009); 152 | WspPushAppTypes.Add("x-wap-nai:mvsw.command ", 0x800B); 153 | WspPushAppTypes.Add("x-wap-openwave:iota.ua", 0x8010); 154 | } 155 | 156 | #region Dictionaries Query Methods 157 | 158 | /// 159 | /// Method returns token for given Content Type. If not found returns -1 160 | /// 161 | /// Content Type to tokenize 162 | /// Token 163 | public static int GetContentType(string contentType) 164 | { 165 | if (WspContentTypes.ContainsKey(contentType)) 166 | return WspContentTypes[contentType]; 167 | else 168 | return -1; 169 | } 170 | 171 | /// 172 | /// Method returns token for given Application Type. If not found returns -1 173 | /// 174 | /// Application Type to tokenize 175 | /// Token 176 | public static int GetpplicationType(string applicationType) 177 | { 178 | if (WspPushAppTypes.ContainsKey(applicationType)) 179 | return WspPushAppTypes[applicationType]; 180 | else 181 | return -1; 182 | } 183 | #endregion 184 | 185 | #region Methods to Write WSP Values 186 | 187 | /// 188 | /// Writes "extension media" (null terminated string) to stream, suitable for WSP protocol 189 | /// 190 | /// Output stream 191 | /// Text to write 192 | public static void WriteExtensionMedia(MemoryStream stream, string extension) 193 | { 194 | byte[] bytes = Encoding.UTF8.GetBytes(extension); 195 | 196 | // write the text 197 | stream.Write(bytes, 0, bytes.Length); 198 | 199 | // string must be null terminated 200 | stream.WriteByte(0x00); 201 | } 202 | 203 | /// 204 | /// Writes text to stream, suitable for WSP protocol 205 | /// 206 | /// Output stream 207 | /// Text to wrap 208 | public static void WriteTextString(MemoryStream stream, string text) 209 | { 210 | byte[] bytes = Encoding.UTF8.GetBytes(text); 211 | 212 | // If the first character in the TEXT is in the range of 128-255, a Quote character must precede it. 213 | // Otherwise the Quote character must be omitted. The Quote is not part of the contents 214 | if ((text[0] & 0x80) > 0x00) 215 | { 216 | stream.WriteByte(0x7f); 217 | } 218 | 219 | // write the text 220 | stream.Write(bytes, 0, bytes.Length); 221 | 222 | // string must be null terminated 223 | stream.WriteByte(0x00); 224 | } 225 | 226 | /// 227 | /// Writes integer value to the stream, according to WSP specifications 228 | /// 229 | /// Output stream 230 | /// value 231 | public static void WriteInteger(MemoryStream stream, long theValue) 232 | { 233 | if (theValue < 128) 234 | WriteShortInteger(stream, (byte)theValue); 235 | else 236 | WriteLongInteger(stream, theValue); 237 | } 238 | 239 | /// 240 | /// Writes WSP-short-integer (byte) value to the stream, according to WSP specifications 241 | /// 242 | /// Output stream 243 | /// value 244 | public static void WriteShortInteger(MemoryStream stream, byte theValue) 245 | { 246 | stream.WriteByte((byte)(theValue | (byte)0x80)); 247 | } 248 | 249 | /// 250 | /// Writes long-integer value to the stream, according to WSP specifications 251 | /// 252 | /// Output stream 253 | /// value 254 | public static void WriteLongInteger(MemoryStream stream, long number) 255 | { 256 | int nOctets = 0; 257 | while ((number >> (8 * nOctets)) > 0) 258 | { 259 | nOctets++; 260 | } 261 | stream.WriteByte((byte) nOctets); 262 | 263 | for (int i = nOctets; i > 0; i--) 264 | { 265 | byte octet = (byte) (number >> (8 * (i - 1))); 266 | byte byteValue = (byte) ((byte) octet & (byte) (0xff)); 267 | stream.WriteByte(byteValue); 268 | } 269 | } 270 | 271 | /// 272 | /// Writes "UIntVar" value to the stream, according to WSP specifications 273 | /// 274 | /// Output stream 275 | /// value 276 | public static void WriteUintvar(MemoryStream stream, long number) 277 | { 278 | int nOctets = 1; 279 | while ((number >> (7 * nOctets)) > 0) 280 | { 281 | nOctets++; 282 | } 283 | 284 | for (int i = nOctets; i > 0; i--) 285 | { 286 | byte octet = (byte) (number >> (7 * (i - 1))); 287 | byte byteValue = (byte) ((byte) octet & (byte) 0x7f); 288 | if (i > 1) 289 | { 290 | byteValue = (byte) (byteValue | (byte) 0x80); 291 | } 292 | stream.WriteByte(byteValue); 293 | } 294 | } 295 | 296 | /// 297 | /// Writes number into the stream 298 | /// 299 | /// Output stream 300 | /// value 301 | public static void WriteValueLength(MemoryStream stream, long number) 302 | { 303 | // ShortLength | (Length-quote Length) 304 | if (number >=0 && number <= 30) 305 | { 306 | // Short-length 307 | stream.WriteByte((byte)number); 308 | } 309 | else 310 | { 311 | // Length-quote == Octet 31 312 | stream.WriteByte(31); 313 | WriteUintvar(stream, number); 314 | } 315 | } 316 | #endregion 317 | 318 | #region WSP Header Writing Methods 319 | 320 | /// 321 | /// Writes header for given Content Type into stream 322 | /// 323 | /// Output Stream 324 | /// Content Type 325 | public static void WriteHeaderContentType(MemoryStream stream, string contentType) 326 | { 327 | int wellKnownContentType = GetContentType(contentType.ToLower()); 328 | 329 | if (wellKnownContentType == -1) 330 | { 331 | WriteValueLength(stream, contentType.Length + 1); 332 | WriteExtensionMedia(stream, contentType); 333 | } 334 | else 335 | { 336 | WriteShortInteger(stream, (byte)wellKnownContentType); 337 | } 338 | } 339 | 340 | /// 341 | /// Writes header for given X-WAP-Application-ID 342 | /// 343 | /// Output stream 344 | /// Application Type 345 | public static void WriteHeaderXWAPApplicationID(MemoryStream stream, string applicationType) 346 | { 347 | int wellKnownAppId = GetpplicationType(applicationType.ToLower()); 348 | 349 | WriteShortInteger(stream, HEADER_X_WAP_APPLICATION_ID); 350 | 351 | if (wellKnownAppId == -1) 352 | WriteTextString(stream, applicationType); 353 | else 354 | WriteInteger(stream, wellKnownAppId); 355 | } 356 | 357 | /// 358 | /// Writes header for given X-WAP-Initiator-URI 359 | /// 360 | /// Output stream 361 | /// Initiator URI 362 | public static void WriteHeaderXWAPInitiatorURI(MemoryStream stream, string initiatorURI) 363 | { 364 | WriteShortInteger(stream, HEADER_X_WAP_INITIATOR_URI); 365 | WriteTextString(stream, initiatorURI); 366 | } 367 | 368 | /// 369 | /// Writes header for given X-WAP-Content-URI 370 | /// 371 | /// Output stream 372 | /// Content URI 373 | public static void WriteHeaderXWAPContentURI(MemoryStream stream, string contentURI) 374 | { 375 | WriteShortInteger(stream, HEADER_X_WAP_CONTENT_URI); 376 | WriteTextString(stream, contentURI); 377 | } 378 | 379 | /// 380 | /// Writes header for given Push Flag 381 | /// 382 | /// Output stream 383 | /// Push Flag 384 | public static void WriteHeaderPushFlag(MemoryStream stream, byte pushFlag) 385 | { 386 | WriteShortInteger(stream, HEADER_PUSHFLAG); 387 | WriteShortInteger(stream, pushFlag); 388 | } 389 | 390 | /// 391 | /// Writes security header 392 | /// 393 | /// Ouput stream 394 | /// Security method 395 | /// Key 396 | public static void WriteHeaderSecurity(MemoryStream stream, SecurityMethod method, byte[] macKey) 397 | { 398 | WriteShortInteger(stream, HEADER_SEC); 399 | 400 | switch (method) 401 | { 402 | case SecurityMethod.NETWPIN: 403 | WriteShortInteger(stream, SEC_NETWPIN); 404 | break; 405 | case SecurityMethod.USERPIN: 406 | WriteShortInteger(stream, SEC_USERPIN); 407 | break; 408 | case SecurityMethod.USERNETWPIN: 409 | WriteShortInteger(stream, SEC_USERNETWPIN); 410 | break; 411 | case SecurityMethod.USERPINMAC: 412 | WriteShortInteger(stream, SEC_USERPINMAC); 413 | break; 414 | } 415 | 416 | WriteShortInteger(stream, HEADER_MAC); 417 | 418 | string macKeyString = string.Empty; 419 | foreach (byte b in macKey) 420 | macKeyString += b.ToString("X2"); 421 | WriteTextString(stream, macKeyString); 422 | } 423 | 424 | #endregion 425 | 426 | #region Fields 427 | 428 | /// 429 | /// Represent Content Type to Token Dictionary 430 | /// 431 | private static Dictionary WspContentTypes; 432 | 433 | /// 434 | /// Represent Application Type to Token Dictionary 435 | /// 436 | private static Dictionary WspPushAppTypes; 437 | #endregion 438 | 439 | /// 440 | /// Security method for message signing 441 | /// 442 | public enum SecurityMethod 443 | { 444 | /// 445 | /// No security will be applied 446 | /// 447 | None, 448 | /// 449 | /// Message is signed with network PIN 450 | /// 451 | NETWPIN, 452 | /// 453 | /// Message is signed with user PIN 454 | /// 455 | USERPIN, 456 | /// 457 | /// Message is signed with network PIN and user PIN 458 | /// 459 | USERNETWPIN, 460 | /// 461 | /// Not supported 462 | /// 463 | USERPINMAC, 464 | } 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /SharpSMS/SMSSubmit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace SharpSMS 7 | { 8 | /// 9 | /// Class represents SMS message to send 10 | /// 11 | public class SMSSubmit 12 | { 13 | #region Constants 14 | /// 15 | /// First Octet Bits 1,0 - SMS-SUBMIT 16 | /// 17 | public const byte FIRST_OCTET_SMS_SUBMIT = 0x01; 18 | /// 19 | /// First Octet Bits 4,3 - Validity period is in relative format 20 | /// 21 | public const byte FIRST_OCTET_VALIDITY_PERIOD_RELATIVEFORMAT = 0x10; 22 | /// 23 | /// First Octet Bit 5 - Status delivery requested 24 | /// 25 | public const byte FIRST_OCTET_REQUEST_DELIVERY_CONFIRMATION = 0x20; 26 | /// 27 | /// First Octet Bit 6 - User data header is present 28 | /// 29 | public const byte FIRST_OCTET_USER_DATA_HEADER = 0x40; 30 | /// 31 | /// User data header - concated message 32 | /// 33 | public const byte PDU_UDH_CONCATED_MESSAGE_16BIT = 0x08; 34 | /// 35 | /// User data header - Length of concated message length 36 | /// 37 | public const byte PDU_UDH_CONCATED_MESSAGE_16BIT_HEADERLENGTH = 0x04; 38 | /// 39 | /// User data header - Length of concated header 40 | /// 41 | public const byte CONCATED_UDH_FULL_LENGTH = 0x07; 42 | 43 | #endregion 44 | 45 | #region Properties 46 | 47 | /// 48 | /// Request for message delivery confirmation (optional) 49 | /// 50 | public bool RequestDeliveryConfirmation { get; set; } 51 | 52 | /// 53 | /// Validation period of the message (optional) 54 | /// 55 | public TimeSpan ValidityPeriod { get; set; } 56 | 57 | /// 58 | /// Message reference (optional) 59 | /// 60 | public byte MessageReference { get; set; } 61 | 62 | /// 63 | /// Message protocol identifier (optional) 64 | /// 65 | public byte ProtocolIdentifier { get; set; } 66 | 67 | /// 68 | /// Phone number of the message recipient 69 | /// 70 | public string PhoneNumber { get; set; } 71 | 72 | /// 73 | /// Specifies how message will be displayed and stored on the phone 74 | /// 75 | public MessageIndication Indication { get; set; } 76 | 77 | /// 78 | /// Message data 79 | /// 80 | public ISmsMessageContent MessageToSend { get; set; } 81 | 82 | #endregion 83 | 84 | #region Constructor 85 | 86 | /// 87 | /// Creates instance of the SubmitSMS message 88 | /// 89 | public SMSSubmit() 90 | : this(null) 91 | { 92 | } 93 | 94 | /// 95 | /// Creates instance of the SubmitSMS message 96 | /// 97 | /// Message to be sent 98 | public SMSSubmit(ISmsMessageContent messageToSent) 99 | { 100 | // Set the default values 101 | this.RequestDeliveryConfirmation = false; 102 | this.ValidityPeriod = TimeSpan.MinValue; 103 | this.MessageReference = 0x00; 104 | this.ProtocolIdentifier = 0x00; 105 | this.MessageToSend = messageToSent; 106 | 107 | this.Indication = new MessageIndication(MessageClass.MESpecific); 108 | this.Indication.Type = IndicationType.Voicemail; 109 | this.Indication.Operation = MessageIndicationOperation.NotSet; 110 | this.Indication.IsActive = false; 111 | } 112 | #endregion 113 | 114 | #region Public Methods 115 | /// 116 | /// Method returns list of PDU formated messages. 117 | /// If data fits into one message than the list has only one item. 118 | /// Otherwise the data is concated into more messages. 119 | /// 120 | /// 121 | public List GetPDUList() 122 | { 123 | int maxOctetsCount; 124 | 125 | switch (MessageToSend.DataEncoding) 126 | { 127 | case DataEncoding.Default7bit: 128 | maxOctetsCount = 160; 129 | break; 130 | case DataEncoding.Data8bit: 131 | maxOctetsCount = 140; // Might be lower, some roaming partners don't like it when set to 140 132 | break; 133 | case DataEncoding.UCS2_16bit: 134 | maxOctetsCount = 140; 135 | break; 136 | default: 137 | maxOctetsCount = 140; 138 | break; 139 | } 140 | 141 | byte[] body = MessageToSend.GetSMSBytes(); 142 | byte[] messageUdh = MessageToSend.GetUDHBytes(); 143 | 144 | // If UDH exists then reserve one more byte for UDH length 145 | int maxLen = maxOctetsCount - messageUdh.Length; 146 | maxLen -= (messageUdh.Length > 0) ? 1 : 0; 147 | 148 | // Count parts 149 | int parts = (int)Math.Ceiling((double)body.Length / (double)maxLen); 150 | parts = (int)Math.Ceiling((double)(body.Length + parts*messageUdh.Length) / (double)maxLen); 151 | 152 | if (parts > 1) 153 | parts = (int)Math.Ceiling((double)(body.Length + parts * messageUdh.Length + parts * (CONCATED_UDH_FULL_LENGTH + 1)) / (double)maxLen); 154 | 155 | List messagePart = new List(); 156 | byte[] udhByteArray; 157 | byte[] messageByteArray; 158 | ConcatedUDH conUDH = new ConcatedUDH(); 159 | 160 | // This will hold the byteCount send in message (without UDH) 161 | int bytesCount = maxOctetsCount; 162 | // Thiis will hold the position in the message array 163 | int messagePos = 0; 164 | 165 | if (parts > 1) 166 | { 167 | conUDH.Reference = 01; 168 | conUDH.Parts = (byte)parts; 169 | 170 | for (int i = 0; i < parts; i++) 171 | { 172 | conUDH.Sequence = (byte)(i + 1); 173 | udhByteArray = GetConcatPdu(conUDH, messageUdh); 174 | 175 | // if it's not last message then 176 | if (i < (parts - 1)) 177 | bytesCount = maxOctetsCount - udhByteArray.Length - 1; 178 | else 179 | bytesCount = body.Length - messagePos; 180 | 181 | byte[] bodyPart = new byte[bytesCount]; 182 | Array.Copy(body, messagePos, bodyPart, 0, bytesCount); 183 | 184 | // Encode message with given encoding 185 | byte[] encodedBody = GetEncodedMessage(bodyPart); 186 | 187 | // Takes the message length before encoding 188 | int messageLength = (bodyPart.Length) + udhByteArray.Length; 189 | 190 | // There is a align in 7bit encoding 191 | if (MessageToSend.DataEncoding == DataEncoding.Default7bit) 192 | messageLength++; 193 | 194 | //creates message 195 | messageByteArray = new byte[encodedBody.Length + udhByteArray.Length]; 196 | Array.Copy(udhByteArray, messageByteArray, udhByteArray.Length); 197 | Array.Copy(encodedBody, 0, messageByteArray, udhByteArray.Length, encodedBody.Length); 198 | messagePos += bytesCount; 199 | 200 | messageByteArray = GetPDUBytes(messageByteArray, messageLength, (udhByteArray.Length > 0)); 201 | messagePart.Add(messageByteArray); 202 | } 203 | } 204 | else 205 | { 206 | conUDH.Parts = 1; 207 | udhByteArray = GetConcatPdu(conUDH, messageUdh); 208 | 209 | // Encode body according to given Encoding :) 210 | byte[] encodedBody = GetEncodedMessage(body); 211 | 212 | // Takes the message length before encoding 213 | int messageLength = (body.Length) + udhByteArray.Length; 214 | 215 | messageByteArray = new byte[encodedBody.Length + udhByteArray.Length]; 216 | Array.Copy(udhByteArray, messageByteArray, udhByteArray.Length); 217 | Array.Copy(encodedBody, 0, messageByteArray, udhByteArray.Length, encodedBody.Length); 218 | 219 | messageByteArray = GetPDUBytes(messageByteArray, messageLength, (udhByteArray.Length > 0)); 220 | messagePart.Add(messageByteArray); 221 | } 222 | 223 | return messagePart; 224 | } 225 | #endregion 226 | 227 | #region Private Methods 228 | 229 | private byte[] GetEncodedMessage(byte[] messageData) 230 | { 231 | switch (MessageToSend.DataEncoding) 232 | { 233 | case DataEncoding.Default7bit: 234 | return OctetsToSeptets(messageData); 235 | case DataEncoding.Data8bit: 236 | return messageData; 237 | case DataEncoding.UCS2_16bit: 238 | return messageData; 239 | default: 240 | return messageData; 241 | } 242 | } 243 | 244 | /// 245 | /// Method adds `Concat` information into the User Header 246 | /// 247 | /// stucture representing the 248 | /// message user header 249 | private byte[] GetConcatPdu(ConcatedUDH udhStruct, byte[] messageUDH) 250 | { 251 | MemoryStream stream = new MemoryStream(); 252 | 253 | // If we have message for concating, write concating header 254 | if (udhStruct.Parts > 1) 255 | { 256 | MemoryStream concatHeader = new MemoryStream(); 257 | concatHeader.WriteByte(PDU_UDH_CONCATED_MESSAGE_16BIT); 258 | concatHeader.WriteByte(PDU_UDH_CONCATED_MESSAGE_16BIT_HEADERLENGTH); 259 | 260 | concatHeader.WriteByte((byte)(udhStruct.Reference >> 8)); 261 | concatHeader.WriteByte((byte)udhStruct.Reference); 262 | 263 | concatHeader.WriteByte(udhStruct.Parts); 264 | concatHeader.WriteByte(udhStruct.Sequence); 265 | 266 | stream.WriteByte((byte)(concatHeader.Length + messageUDH.Length)); 267 | concatHeader.WriteTo(stream); 268 | } 269 | // if we have some messageuUDH, write the header length 270 | else if (messageUDH.Length > 0) 271 | stream.WriteByte((byte)messageUDH.Length); 272 | 273 | stream.Write(messageUDH, 0, messageUDH.Length); 274 | return stream.ToArray(); 275 | } 276 | 277 | /// 278 | /// Returns given bytes PDU formated with the user header 279 | /// 280 | /// Message body 281 | /// Length of the message 282 | /// Does message have custom header inside 283 | private byte[] GetPDUBytes(byte[] messageBody, int messageLength, bool hasCustomHeader) 284 | { 285 | MemoryStream message = new MemoryStream(); 286 | 287 | byte[] header = GetPDUHeader(messageLength, hasCustomHeader); 288 | 289 | message.Write(header, 0, header.Length); 290 | message.Write(messageBody, 0, messageBody.Length); 291 | 292 | byte[] messageBytes = message.ToArray(); 293 | message.Close(); 294 | 295 | return messageBytes; 296 | } 297 | 298 | /// 299 | /// Returns PDU header of the text message. 300 | /// Including phone number and other flags 301 | /// 302 | /// lenght of data in message 303 | /// Does message have custom header inside 304 | private byte[] GetPDUHeader(int dataLength, bool hasCustomHeader) 305 | { 306 | MemoryStream header = new MemoryStream(); 307 | header.WriteByte(0x00); // Length of SMSC 308 | // TP-MTI Message type 309 | header.WriteByte(GetFirstOctet(hasCustomHeader)); 310 | header.WriteByte(this.MessageReference); // TP-MR Message Reference 311 | 312 | WritePhoneNumber(header, PhoneNumber); 313 | // TP-PID. Protocol identifier. 314 | header.WriteByte(ProtocolIdentifier); 315 | // TP-DCS Data coding scheme 316 | header.WriteByte(this.Indication.ToByte(this.MessageToSend.DataEncoding)); 317 | 318 | // TP-SCTS. Time stamp (semi-octets) 319 | if (ValidityPeriod > TimeSpan.MinValue) 320 | header.WriteByte(GetValidityPeriod()); 321 | 322 | header.WriteByte((byte)(dataLength)); // +1 is to count also this byte 323 | 324 | byte[] headerBytes = header.ToArray(); 325 | header.Close(); 326 | 327 | return headerBytes; 328 | } 329 | 330 | /// 331 | /// Return first octet of the PDU header depending message properties settings 332 | /// 333 | /// 334 | private byte GetFirstOctet(bool hasCustomHeader) 335 | { 336 | byte firstOctet = FIRST_OCTET_SMS_SUBMIT; 337 | 338 | if (ValidityPeriod > TimeSpan.MinValue) 339 | firstOctet |= FIRST_OCTET_VALIDITY_PERIOD_RELATIVEFORMAT; 340 | 341 | if (RequestDeliveryConfirmation) 342 | firstOctet |= FIRST_OCTET_REQUEST_DELIVERY_CONFIRMATION; 343 | 344 | if (hasCustomHeader) 345 | firstOctet |= FIRST_OCTET_USER_DATA_HEADER; 346 | 347 | return firstOctet; 348 | } 349 | 350 | /// 351 | /// Returns value representing current validity period 352 | /// 353 | /// 354 | private byte GetValidityPeriod() 355 | { 356 | TimeSpan value = ValidityPeriod; 357 | byte validity; 358 | 359 | if (value.Days > 441) 360 | value = new TimeSpan(440,0,0,0); 361 | 362 | if (value.Days > 30) //Up to 441 days 363 | validity = (byte) (192 + (int) (value.Days / 7)); 364 | else if (value.Days > 1) //Up to 30 days 365 | validity = (byte) (166 + value.Days); 366 | else if (value.Hours > 12) //Up to 24 hours 367 | validity = (byte) (143 + (value.Hours - 12) * 2 + value.Minutes / 30); 368 | else if (value.Hours > 1 || value.Minutes > 1) //Up to 12 days 369 | validity = (byte) (value.Hours * 12 + value.Minutes / 5 - 1); 370 | else 371 | validity = 0x00; 372 | 373 | return validity; 374 | } 375 | 376 | /// 377 | /// Writes phone in PDU format 378 | /// 379 | /// Destination stream 380 | /// Phone number to write 381 | private void WritePhoneNumber(MemoryStream stream, string phoneNumber) 382 | { 383 | if (string.IsNullOrEmpty(phoneNumber)) 384 | throw new ArgumentException("Phone number is not set"); 385 | 386 | bool isInternational = phoneNumber.StartsWith("+"); 387 | byte numberFormat = 0x81; 388 | 389 | if (isInternational) 390 | { 391 | phoneNumber = phoneNumber.Remove(0, 1); 392 | numberFormat = 0x91; 393 | } 394 | 395 | byte numberLength = (byte)phoneNumber.Length; 396 | 397 | stream.WriteByte(numberLength); 398 | stream.WriteByte(numberFormat); 399 | WriteBcdNumber(stream, phoneNumber); 400 | } 401 | 402 | /// 403 | /// Encodes phone number into BCD. 404 | /// 405 | /// Destination stream 406 | /// Phone number to write 407 | private void WriteBcdNumber(MemoryStream stream, string phoneNumber) 408 | { 409 | int bcd = 0x00; 410 | int n = 0; 411 | 412 | // First convert to a "half octet" value 413 | for (int i = 0; i < phoneNumber.Length; i++) 414 | { 415 | switch (phoneNumber[i]) 416 | { 417 | case '0': 418 | bcd |= 0x00; 419 | break; 420 | case '1': 421 | bcd |= 0x10; 422 | break; 423 | case '2': 424 | bcd |= 0x20; 425 | break; 426 | case '3': 427 | bcd |= 0x30; 428 | break; 429 | case '4': 430 | bcd |= 0x40; 431 | break; 432 | case '5': 433 | bcd |= 0x50; 434 | break; 435 | case '6': 436 | bcd |= 0x60; 437 | break; 438 | case '7': 439 | bcd |= 0x70; 440 | break; 441 | case '8': 442 | bcd |= 0x80; 443 | break; 444 | case '9': 445 | bcd |= 0x90; 446 | break; 447 | case '*': 448 | bcd |= 0xA0; 449 | break; 450 | case '#': 451 | bcd |= 0xB0; 452 | break; 453 | case 'a': 454 | bcd |= 0xC0; 455 | break; 456 | case 'b': 457 | bcd |= 0xE0; 458 | break; 459 | } 460 | 461 | n++; 462 | 463 | if (n == 2) 464 | { 465 | stream.WriteByte((byte)bcd); 466 | n = 0; 467 | bcd = 0x00; 468 | } 469 | else 470 | { 471 | bcd >>= 4; 472 | } 473 | } 474 | 475 | if (n == 1) 476 | { 477 | bcd |= 0xF0; 478 | stream.WriteByte((byte)bcd); 479 | } 480 | } 481 | 482 | /// 483 | /// Converts array of characters into septets 484 | /// 485 | /// Array of octets 486 | private byte[] OctetsToSeptets(byte[] octetsArray) 487 | { 488 | int arrayLength = octetsArray.Length; 489 | // Extend octets array with byte 490 | byte[] workingArray = new byte[arrayLength + 1]; 491 | Array.Copy(octetsArray, workingArray, arrayLength); 492 | 493 | arrayLength = workingArray.Length; 494 | 495 | MemoryStream octets = new MemoryStream(); 496 | 497 | for (int i = 0; i < arrayLength; ++i) 498 | { 499 | int m = i % 8; 500 | if (m != 7) 501 | { 502 | int n; 503 | if (i == arrayLength - 1) 504 | n = 0 & PowSum(0, m); 505 | else 506 | { 507 | n = workingArray[i + 1] & PowSum(0, m); 508 | workingArray[i + 1] -= (byte)n; 509 | } 510 | 511 | workingArray[i] /= (byte)Math.Pow(2, m); 512 | workingArray[i] += (byte)(Math.Pow(2, 7 - m) * n); 513 | 514 | // We dont want the last 0x00 byte to be added 515 | if ((arrayLength - i) > 1) 516 | octets.WriteByte(workingArray[i]); 517 | } 518 | } 519 | 520 | return octets.ToArray(); 521 | } 522 | 523 | /// 524 | /// Sum POW(2, i), where i goes through 0 to n. 525 | /// 526 | /// Start bit 527 | /// Number of bits 528 | private static int PowSum(int startBit, int n) 529 | { 530 | int sum = 0; 531 | for (int i = n; i >= startBit; --i) 532 | { 533 | sum += (int)Math.Pow(2, i); 534 | } 535 | return sum; 536 | } 537 | 538 | #endregion 539 | 540 | #region Private Structs 541 | private struct ConcatedUDH 542 | { 543 | public byte Parts; 544 | public byte Sequence; 545 | public byte Reference; 546 | } 547 | #endregion 548 | } 549 | } 550 | --------------------------------------------------------------------------------