diff --git a/.gitignore b/.gitignore index e72bf56..1d7a4b8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ #Log folders Logs/ +**/*/launchSettings.json + # User-specific files *.suo *.user diff --git a/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs b/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs index 8959fee..3c339da 100644 --- a/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs +++ b/src/HeboTech.ATLib.TestConsole/FunctionalityTest.cs @@ -1,6 +1,7 @@ using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Events; using HeboTech.ATLib.Modems.Cinterion; +using HeboTech.ATLib.Modems.D_LINK; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System; @@ -12,22 +13,54 @@ namespace HeboTech.ATLib.TestConsole { public static class FunctionalityTest { + private static readonly string debugPath = System.IO.Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "Downloads", + "atlog", + "log.txt" + ); + + private static void Log(string message) + { + string formattedLine = $"({DateTime.Now}) {message}"; + try + { + System.IO.File.AppendAllLines(debugPath, [formattedLine]); + } + catch (Exception e) + { + //Console.WriteLine($"Error while logging: {e}"); + } + } + public static async Task RunAsync(System.IO.Stream stream, string pin) { using AtChannel atChannel = AtChannel.Create(stream); - //atChannel.EnableDebug((string line) => Console.WriteLine(line)); + atChannel.EnableDebug(Log); using IMC55i modem = new MC55i(atChannel); + //using IDWM222 modem = new DWM222(atChannel); atChannel.Open(); await atChannel.ClearAsync(); + modem.ErrorReceived += Modem_ErrorReceived; + + modem.GenericEvent += Modem_GenericEvent; + modem.IncomingCall += Modem_IncomingCall; modem.MissedCall += Modem_MissedCall; modem.CallStarted += Modem_CallStarted; modem.CallEnded += Modem_CallEnded; + + modem.SmsStorageReferenceReceived += Modem_SmsStorageReferenceReceived; modem.SmsReceived += Modem_SmsReceived; + + modem.SmsStatusReportReceived += Modem_SmsStatusReportReceived; + modem.SmsStatusReportStorageReferenceReceived += Modem_SmsStatusReportStorageReferenceReceived; + + modem.BroadcastMessageReceived += Modem_BroadcastMessageReceived; + modem.BroadcastMessageStorageReferenceReceived += Modem_BroadcastMessageStorageReferenceReceived; + modem.UssdResponseReceived += Modem_UssdResponseReceived; - modem.ErrorReceived += Modem_ErrorReceived; - modem.GenericEvent += Modem_GenericEvent; // Configure modem with required settings before PIN var requiredSettingsBeforePin = await modem.SetRequiredSettingsBeforePinAsync(); @@ -81,14 +114,6 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) var batteryStatus = await modem.GetBatteryStatusAsync(); Console.WriteLine($"Battery Status: {batteryStatus}"); - { - if (modem is IMC55i mc55i) - { - var mc55iBatteryStatus = await mc55i.MC55i_GetBatteryStatusAsync(); - Console.WriteLine($"MC55i Battery Status: {mc55iBatteryStatus}"); - } - } - var productInfo = await modem.GetProductIdentificationInformationAsync(); Console.WriteLine($"Product Information:{Environment.NewLine}{productInfo}"); @@ -98,18 +123,21 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) var dateTime = await modem.GetDateTimeAsync(); Console.WriteLine($"Date and time: {dateTime}"); + var selectMessageService = await modem.SetSelectMessageService(0); + Console.WriteLine($"Setting select message service: {selectMessageService}"); - var newSmsIndicationResult = await modem.SetNewSmsIndicationAsync(2, 1, 0, 0, 1); + var newSmsIndicationResult = await modem.SetNewSmsIndicationAsync(2, 1, 0, 2, 0); // 2, 1, 0, 2, 0 (CSMS=0) Console.WriteLine($"Setting new SMS indication: {newSmsIndicationResult}"); var supportedStorages = await modem.GetSupportedPreferredMessageStoragesAsync(); Console.WriteLine($"Supported storages:{Environment.NewLine}{supportedStorages}"); var currentStorages = await modem.GetPreferredMessageStoragesAsync(); Console.WriteLine($"Current storages:{Environment.NewLine}{currentStorages}"); - var setPreferredStorages = await modem.SetPreferredMessageStorageAsync(MessageStorage.SM, MessageStorage.SM, MessageStorage.SM); + var setPreferredStorages = await modem.SetPreferredMessageStorageAsync(MessageStorage.MT, MessageStorage.MT, MessageStorage.MT); Console.WriteLine($"Storages set:{Environment.NewLine}{setPreferredStorages}"); - Console.WriteLine("Done. Press 'a' to answer call, 'd' to dial, 'h' to hang up, 's' to send SMS, 'r' to read an SMS, 'l' to list all SMSs, 'u' to send USSD code, 'x' to send raw command, 'z' to send raw command with response, '+' to enable debug, '-' to disable debug and 'q' to exit..."); + Log("Initialization done"); + Console.WriteLine("Done. Press 'a' to answer call, 'd' to dial, 'h' to hang up, 's' to send SMS, 'r' to read an SMS, 'l' to list all SMSs, 'p' to delete an SMS, 'u' to send USSD code, 'x' to send raw command, 'z' to send raw command with response, '+' to enable debug, '-' to disable debug and 'q' to exit..."); ConsoleKey key; while ((key = Console.ReadKey().Key) != ConsoleKey.Q) { @@ -164,21 +192,38 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) string smsMessage = Console.ReadLine(); Console.WriteLine("Sending SMS..."); - IEnumerable<ModemResponse<SmsReference>> smsReferences = await modem.SendSmsAsync(phoneNumber, smsMessage); + IEnumerable<ModemResponse<SmsReference>> smsReferences = await modem.SendSmsAsync(new SmsSubmitRequest(phoneNumber, smsMessage) { EnableStatusReportRequest = true, ValidityPeriod = ValidityPeriod.Relative(RelativeValidityPeriods.Minutes_5) }); foreach (var smsReference in smsReferences) Console.WriteLine($"SMS Reference: {smsReference}"); break; } case ConsoleKey.R: - Console.WriteLine("Enter SMS index:"); - if (int.TryParse(Console.ReadLine(), out int smsIndex)) { - var sms = await modem.ReadSmsAsync(smsIndex); - Console.WriteLine(sms); + Console.WriteLine("Enter SMS index:"); + if (int.TryParse(Console.ReadLine(), out int smsIndex)) + { + var sms = await modem.ReadSmsAsync(smsIndex); + if (sms.Success) + { + Console.WriteLine(sms.Result); + } + } + else + Console.WriteLine("Invalid SMS index"); + break; + } + case ConsoleKey.P: + { + Console.WriteLine("Enter SMS index:"); + if (int.TryParse(Console.ReadLine(), out int smsIndex)) + { + var deleteResponse = await modem.DeleteSmsAsync(smsIndex); + Console.WriteLine(deleteResponse); + } + else + Console.WriteLine("Invalid SMS index"); + break; } - else - Console.WriteLine("Invalid SMS index"); - break; case ConsoleKey.U: Console.WriteLine("Enter USSD Code:"); var ussd = Console.ReadLine(); @@ -188,10 +233,14 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) case ConsoleKey.L: Console.WriteLine("List all SMSs:"); var smss = await modem.ListSmssAsync(SmsStatus.ALL); + Console.WriteLine($"{smss.Result.Count} SMSs:"); if (smss.Success && smss.Result.Any()) { foreach (var sms in smss.Result) - Console.WriteLine($"------------------------------------------------{Environment.NewLine}{sms}"); + { + Console.WriteLine($"------------------------------------------------"); + Console.WriteLine($"Index: {sms.Index}, {sms.Sms}"); + } Console.WriteLine($"------------------------------------------------"); } @@ -208,6 +257,36 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) } } + private static void Modem_BroadcastMessageStorageReferenceReceived(object sender, BreadcastMessageStorageReferenceReceivedEventArgs e) + { + Console.WriteLine($"Broadcast Message. Index {e.Index} at storage location {e.Storage}"); + } + + private static void Modem_SmsStorageReferenceReceived(object sender, SmsStorageReferenceReceivedEventArgs e) + { + Console.WriteLine($"SMS Deliver. Index {e.Index} at storage location {e.Storage}"); + } + + private static void Modem_SmsStatusReportStorageReferenceReceived(object sender, SmsStatusReportStorageReferenceEventArgs e) + { + Console.WriteLine($"SMS Status Report. Index {e.Index} at storage location {e.Storage}"); + } + + private static void Modem_BroadcastMessageReceived(object sender, BreadcastMessageReceivedEventArgs e) + { + Console.WriteLine($"Broadcast Message: {e.BroadcastMessage}"); + } + + private static void Modem_SmsReceived(object sender, SmsReceivedEventArgs e) + { + Console.WriteLine($"SMS Deliver: {e.SmsDeliver}"); + } + + private static void Modem_SmsStatusReportReceived(object sender, SmsStatusReportEventArgs e) + { + Console.WriteLine($"SMS Status Report: {e.SmsStatusReport}"); + } + private static void Modem_GenericEvent(object sender, GenericEventArgs e) { Console.WriteLine($"Generic event: {e.Message}"); @@ -234,11 +313,6 @@ private static void Modem_CallStarted(object sender, CallStartedEventArgs e) Console.WriteLine("Call started"); } - private static void Modem_SmsReceived(object sender, SmsReceivedEventArgs e) - { - Console.WriteLine($"SMS received. Index {e.Index} at storage location {e.Storage}"); - } - private static void Modem_MissedCall(object sender, MissedCallEventArgs e) { Console.WriteLine($"Missed call at {e.Time} from {e.PhoneNumber}"); diff --git a/src/HeboTech.ATLib.TestConsole/Program.cs b/src/HeboTech.ATLib.TestConsole/Program.cs index 38e515e..aceaae4 100644 --- a/src/HeboTech.ATLib.TestConsole/Program.cs +++ b/src/HeboTech.ATLib.TestConsole/Program.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.IO.Ports; using System.Linq; using System.Net.Sockets; using System.Reflection; @@ -29,7 +28,9 @@ static async Task Main(string[] args) Console.OutputEncoding = System.Text.Encoding.UTF8; - string pin = args[0]; + string address = args[0]; + int port = int.Parse(args[1]); + string pin = args[2]; @@ -45,8 +46,8 @@ static async Task Main(string[] args) /* ######## UNCOMMENT THIS SECTION TO USE NETWORK SOCKET ######## */ - using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.Connect("192.168.1.144", 7000); + using Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.Connect(address, port); Console.WriteLine("Network socket opened"); Stream stream; stream = new NetworkStream(socket); diff --git a/src/HeboTech.ATLib.TestConsole/StressTest.cs b/src/HeboTech.ATLib.TestConsole/StressTest.cs index a877474..e32d3be 100644 --- a/src/HeboTech.ATLib.TestConsole/StressTest.cs +++ b/src/HeboTech.ATLib.TestConsole/StressTest.cs @@ -20,7 +20,7 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) modem.IncomingCall += Modem_IncomingCall; modem.MissedCall += Modem_MissedCall; - modem.SmsReceived += Modem_SmsReceived; + modem.SmsStorageReferenceReceived += Modem_SmsReceived; await modem.DisableEchoAsync(); @@ -73,7 +73,7 @@ public static async Task RunAsync(System.IO.Stream stream, string pin) Console.ReadKey(); } - private static void Modem_SmsReceived(object sender, Events.SmsReceivedEventArgs e) + private static void Modem_SmsReceived(object sender, Events.SmsStorageReferenceReceivedEventArgs e) { Console.WriteLine($"SMS received. Index {e.Index} at storage location {e.Storage}"); } diff --git a/src/HeboTech.ATLib.Tests/CodingSchemes/Gsm7Tests.cs b/src/HeboTech.ATLib.Tests/CodingSchemes/Gsm7Tests.cs index 9ee8464..d60b174 100644 --- a/src/HeboTech.ATLib.Tests/CodingSchemes/Gsm7Tests.cs +++ b/src/HeboTech.ATLib.Tests/CodingSchemes/Gsm7Tests.cs @@ -7,10 +7,12 @@ namespace HeboTech.ATLib.Tests.PDU public class Gsm7Tests { [Theory] - [InlineData("41", "41")] - [InlineData("4142", "4121")] - [InlineData("54616461203A29", "D430390CD2A500")] - [InlineData("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67", "54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719")] + [InlineData("41", "41")] // A + [InlineData("4142", "4121")] // AB + [InlineData("54616461203A29", "D430390CD2A500")] // Tada :) + [InlineData("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67", "54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719")] // "The quick brown fox jumps over the lazy dog" + [InlineData("44657369676E00486F6D65", "C4F23C7D760390EF7619")] // Design@Home + [InlineData("4C696E65311B0A4C696E6532", "CCB4BB1CDB289869775906")] // Line1\r\nLine2 public void Pack_returns_packed_bytes(string gsm7Bit, string expected) { byte[] result = Gsm7.Pack(Convert.FromHexString(gsm7Bit)); @@ -19,10 +21,12 @@ public void Pack_returns_packed_bytes(string gsm7Bit, string expected) } [Theory] - [InlineData("41" , "41")] - [InlineData("4121", "4142")] - [InlineData("D430390CD2A500", "54616461203A29")] - [InlineData("54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719", "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67")] + [InlineData("41" , "41")] // A + [InlineData("4121", "4142")] // AB + [InlineData("D430390CD2A500", "54616461203A29")] // Tada :) + [InlineData("54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719", "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67")] // "The quick brown fox jumps over the lazy dog" + [InlineData("C4F23C7D760390EF7619", "44657369676E00486F6D65")] // Design@Home + [InlineData("CCB4BB1CDB289869775906", "4C696E65311B0A4C696E6532")] // Line1\r\nLine2 public void Unpack_returns_unpacked_bytes(string gsm7Bit, string expected) { byte[] result = Gsm7.Unpack(Convert.FromHexString(gsm7Bit)); @@ -32,101 +36,82 @@ public void Unpack_returns_unpacked_bytes(string gsm7Bit, string expected) [Theory] [InlineData("A", "41")] - [InlineData("AB", "4142")] - [InlineData("The quick brown fox jumps over the lazy dog", "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67")] - [InlineData("Tada :)", "54616461203A29")] + [InlineData("AB", "4121")] + [InlineData("The quick brown fox jumps over the lazy dog", "54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719")] + [InlineData("Tada :)", "D430390CD2A500")] + [InlineData("HELLO123", "C82293F98CC966")] + [InlineData("Design@Home", "C4F23C7D760390EF7619")] + [InlineData("Hello world", "C8329BFD06DDDF723619")] + [InlineData("It is easy to send text messages.", "493A283D0795C3F33C88FE06CDCB6E32885EC6D341EDF27C1E3E97E72E")] + [InlineData("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostru", "CCB7BCDC06A5E1F37A1B447EB3DF72D03C4D0785DB653A0B347EBBE7E531BD4CAFCB4161721A9E9EA7C769F7195466A7E92CD0BC4C0691DFA072BA3E6FBFC9207AB90D7FCB4169F7384D4E93EB6E3AA84E07B1C3E2B7BC0C2AD341E437FB2D2F83DAE1B33B0C0AB3D3F17AD855A583CAEE741B142683DA6977BA0DB297DDE9709B058AD7D37390FB3DA7CBEB")] // 160 characters public void EncodeToBytes_returns_encoded_bytes(string gsm7Bit, string expected) { - byte[] result = Gsm7.EncodeToBytes(gsm7Bit.ToCharArray()); + byte[] result = Gsm7.Encode(gsm7Bit.ToCharArray()); Assert.Equal(Convert.FromHexString(expected), result); } [Theory] - [InlineData("41", "A")] - [InlineData("4142", "AB")] - [InlineData("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67", "The quick brown fox jumps over the lazy dog")] - [InlineData("54616461203A29", "Tada :)")] - public void DecodeFromBytes_returns_decoded_text(string gsm7Bit, string expected) + [InlineData("Hello world", 1, "906536FB0DBABFE56C32")] + public void EncodeToBytes_with_padding_returns_encoded_bytes(string gsm7Bit, int padding, string expected) { - string result = Gsm7.DecodeFromBytes(Convert.FromHexString(gsm7Bit)); - - Assert.Equal(expected, result); - } - - [Theory] - [InlineData("A", "41")] - [InlineData("AB", "4121")] - [InlineData("ABC", "41E110")] - [InlineData("Google", "C7F7FBCC2E03")] - [InlineData("SMS Rulz", "D3E61424ADB3F5")] - [InlineData("Hello.", "C8329BFD7601")] - [InlineData("Hello world", "C8329BFD06DDDF723619")] - [InlineData("This is testdata!", "54747A0E4ACF41F4F29C4E0ED3C321")] - [InlineData("The quick brown fox jumps over the lazy dog", "54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719")] - [InlineData("Tada :)", "D430390CD2A500")] - [InlineData("hellohello", "E8329BFD4697D9EC37")] - [InlineData("Hi", "C834")] - public void Encode_and_pack_returns_encoded_text(string gsm7Bit, string expected) - { - byte[] result = Gsm7.Pack(Gsm7.EncodeToBytes(gsm7Bit)); + byte[] result = Gsm7.Encode(gsm7Bit.ToCharArray(), padding); Assert.Equal(Convert.FromHexString(expected), result); } [Theory] - [InlineData("41", 0, "A")] - [InlineData("4121", 0, "AB")] - [InlineData("C834", 0, "Hi")] - [InlineData("41E110", 0, "ABC")] - [InlineData("C7F7FBCC2E03", 0, "Google")] - [InlineData("D430390CD2A500", 0, "Tada :)")] - [InlineData("D3E61424ADB3F5", 0, "SMS Rulz")] - [InlineData("C8329BFD7601", 0, "Hello.")] - [InlineData("C8329BFD06DDDF723619", 0, "Hello world")] - [InlineData("54747A0E4ACF41F4F29C4E0ED3C321", 0, "This is testdata!")] - [InlineData("54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719", 0, "The quick brown fox jumps over the lazy dog")] - [InlineData( - "986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D1FA7DD6750999DA6B340F33219447E83CAE9FABCFD2683E8E536FC2D07A5DDE334394DAEBBE9A03A1DC40E8BDFF232A84C0791DFECB7BC0C6A87CFEE3028CC4EC7EB6117A84A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7E7", 1, - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis")] - public void Unpack_and_decode_returns_decoded_text(string gsm7Bit, int paddingBits, string expected) + [InlineData("41", "A")] + [InlineData("4121", "AB")] + [InlineData("54741914AFA7C76B9058FEBEBB41E6371EA4AEB7E173D0DB5E9683E8E832881DD6E741E4F719", "The quick brown fox jumps over the lazy dog")] + [InlineData("D430390CD2A500", "Tada :)")] + [InlineData("C82293F98CC966", "HELLO123")] + [InlineData("C4F23C7D760390EF7619", "Design@Home")] + [InlineData("CCB7BCDC06A5E1F37A1B447EB3DF72D03C4D0785DB653A0B347EBBE7E531BD4CAFCB4161721A9E9EA7C769F7195466A7E92CD0BC4C0691DFA072BA3E6FBFC9207AB90D7FCB4169F7384D4E93EB6E3AA84E07B1C3E2B7BC0C2AD341E437FB2D2F83DAE1B33B0C0AB3D3F17AD855A583CAEE741B142683DA6977BA0DB297DDE9709B058AD7D37390FB3DA7CBEB", "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostru")] // 160 characters + [InlineData("CC98822903", "L1\nL2")] + [InlineData("493A283D0795C3F33C88FE06CDCB6E32885EC6D341EDF27C1E3E97E72E", "It is easy to send text messages.")] + public void DecodeFromBytes_returns_decoded_text(string gsm7Bit, string expected) { - byte[] unpacked = Gsm7.Unpack(Convert.FromHexString(gsm7Bit), paddingBits); - string result = Gsm7.DecodeFromBytes(unpacked); + string result = Gsm7.Decode(Convert.FromHexString(gsm7Bit)); Assert.Equal(expected, result); } [Theory] - [InlineData("{", "1B28")] - [InlineData("{}", "1B281B29")] - [InlineData("()", "2829")] - public void EncodeToBytes_returns_encoded_bytes_with_default_extension_table(string gsm7Bit, string expected) + [InlineData("\u001B", "1B")] // ESC character at the end of a string results in a space + [InlineData(" ", "20")] // Space character at the end of a string results in a space + [InlineData(" ", "2010")] // Two spaces at the end of a string results in two spaces + [InlineData("{", "1B14")] + [InlineData("{}", "1BD42605")] + [InlineData("()", "A814")] + public void EncodeToBytes_returns_encoded_bytes_with_default_extension_table(string text, string expected) { - byte[] result = Gsm7.EncodeToBytes(gsm7Bit); + byte[] result = Gsm7.Encode(text); Assert.Equal(Convert.FromHexString(expected), result); } [Theory] [InlineData("À", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "14")] - [InlineData("Φ", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "1B12")] - [InlineData("ΦΣ", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "1B121B18")] + [InlineData("Φ", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "1B09")] + [InlineData("ΦΣ", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "1BC90603")] public void EncodeToBytes_returns_encoded_bytes_with_extension_table(string gsm7Bit, Gsm7Extension singleShift, Gsm7Extension lockingShift, string expected) { - byte[] result = Gsm7.EncodeToBytes(gsm7Bit, singleShift, lockingShift); + byte[] result = Gsm7.Encode(gsm7Bit, 0, singleShift, lockingShift); Assert.Equal(Convert.FromHexString(expected), result); } [Theory] - [InlineData(new byte[] { 0x1B }, " ")] - [InlineData(new byte[] { 0x1B, 0x28 }, "{")] - [InlineData(new byte[] { 0x1B, 0x28, 0x1B, 0x29 }, "{}")] - [InlineData(new byte[] { 0x28, 0x29 }, "()")] - public void DecodeFromBytes_returns_decoded_text_with_default_extension_table(byte[] gsm7Bit, string expected) + [InlineData("1B", " ")] // ESC character at the end of a string results in a space + [InlineData("20", " ")] // Space results in a space + [InlineData("2010", " ")] // Two spaces at the end of a string results in two spaces + [InlineData("1B14", "{")] + [InlineData("1BD42605", "{}")] + [InlineData("A814", "()")] + public void DecodeFromBytes_returns_decoded_text_with_default_extension_table(string gsm7Bit, string expected) { - string result = Gsm7.DecodeFromBytes(gsm7Bit); + string result = Gsm7.Decode(Convert.FromHexString(gsm7Bit)); Assert.Equal(expected, result); } @@ -134,10 +119,10 @@ public void DecodeFromBytes_returns_decoded_text_with_default_extension_table(by [Theory] [InlineData("14", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "À")] [InlineData("1B", Gsm7Extension.Portugese, Gsm7Extension.Portugese, " ")] - [InlineData("1B12", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "Φ")] + [InlineData("1B09", Gsm7Extension.Portugese, Gsm7Extension.Portugese, "Φ")] public void DecodeFromBytes_returns_decoded_text_with_extension_table(string gsm7Bit, Gsm7Extension singleShift, Gsm7Extension lockingShift, string expected) { - string result = Gsm7.DecodeFromBytes(Convert.FromHexString(gsm7Bit), singleShift, lockingShift); + string result = Gsm7.Decode(Convert.FromHexString(gsm7Bit), 0, singleShift, lockingShift); Assert.Equal(expected, result); } @@ -146,14 +131,14 @@ public void DecodeFromBytes_returns_decoded_text_with_extension_table(string gsm [InlineData("A", 0, "41")] [InlineData("Hello world", 0, "C8329BFD06DDDF723619")] [InlineData("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostru", 0, "CCB7BCDC06A5E1F37A1B447EB3DF72D03C4D0785DB653A0B347EBBE7E531BD4CAFCB4161721A9E9E8FD3EE33A8CC4ED359A079990C22BF41E5747DDE7E9341F4721BFE9683D2EE719A9C26D7DD74509D0E6287C56F791954A683C86FF65B5E06B5C36777181466A7E3F5B00B54A583CAEE741B142683DA6977BA0DB297DDE9709B058AD7D37390FB3DA7CBEB")] - [InlineData("Hello world", 1, "906536FB0DBABFE56C3200")] - [InlineData("Hi", 2, "20D300")] - [InlineData("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis", 1, "986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D4F8FD3EE33A8CC4ED359A079990C22BF41E5747DDE7E9341F4721BFE9683D2EE719A9C26D7DD74509D0E6287C56F791954A683C86FF65B5E06B5C36777181466A7E3F5B0AB4A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7E700")] - [InlineData(" nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolor", 1, "40EEF79C2EAF9341657C593E4ED3C3F4F4DB0DAAB3D9E1F6F80D6287C56F797A0E72A7E769509D0E0AB3D3F17A1A0E2AE341E53068FC6EB7DFE43768FC76CFCBF17A98EE22D6D37350B84E2F83D2F2BABC0C22BFD96F3928ED06C9CB7079195D7693CBF2341D947683EC6F761D4E0FD3CB207B999DA683CAF37919344EB3D9F53688FC66BFE500")] - [InlineData("e eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 1, "CAA0721D64AE9FD3613AC85D67B3C32078589E0ED3EB7257113F2EC3E9E5BA1C344FBBE9A0F7781C2E8FC374D0B80E4F93C3F4301DE47EBB4170F93B4D2EBBE92CD0BCEEA683D26ED0B8CE868741F17A1AF4369BD3E37418442ECFCBF2BA9B0E6ABFD9EC341D1476A7DBA03419549ED341ECB0F82DAFB75D00")] + [InlineData("Hello world", 1, "906536FB0DBABFE56C32")] + [InlineData("Hi", 2, "20D3")] + [InlineData("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis", 1, "986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D4F8FD3EE33A8CC4ED359A079990C22BF41E5747DDE7E9341F4721BFE9683D2EE719A9C26D7DD74509D0E6287C56F791954A683C86FF65B5E06B5C36777181466A7E3F5B0AB4A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7E7")] + [InlineData(" nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolor", 1, "40EEF79C2EAF9341657C593E4ED3C3F4F4DB0DAAB3D9E1F6F80D6287C56F797A0E72A7E769509D0E0AB3D3F17A1A0E2AE341E53068FC6EB7DFE43768FC76CFCBF17A98EE22D6D37350B84E2F83D2F2BABC0C22BFD96F3928ED06C9CB7079195D7693CBF2341D947683EC6F761D4E0FD3CB207B999DA683CAF37919344EB3D9F53688FC66BFE5")] + [InlineData("e eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 1, "CAA0721D64AE9FD3613AC85D67B3C32078589E0ED3EB7257113F2EC3E9E5BA1C344FBBE9A0F7781C2E8FC374D0B80E4F93C3F4301DE47EBB4170F93B4D2EBBE92CD0BCEEA683D26ED0B8CE868741F17A1AF4369BD3E37418442ECFCBF2BA9B0E6ABFD9EC341D1476A7DBA03419549ED341ECB0F82DAFB75D")] public void Encoder_returns_encoded_text_with_padding(string gsm7Bit, int paddingBits, string expected) { - byte[] result = Gsm7.Pack(Gsm7.EncodeToBytes(gsm7Bit), paddingBits); + byte[] result = Gsm7.Encode(gsm7Bit, paddingBits); Assert.Equal(expected, BitConverter.ToString(result).Replace("-", "")); } @@ -171,12 +156,11 @@ public void Encoder_returns_encoded_text_with_padding(string gsm7Bit, int paddin [InlineData("Tada :)")] [InlineData("hellohello")] [InlineData("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud")] + [InlineData("Line1\r\nLine2")] public void Encode_decode_returns_original_text(string text) { - byte[] encoded = Gsm7.EncodeToBytes(text); - byte[] packed = Gsm7.Pack(encoded); - byte[] decoded = Gsm7.Unpack(packed); - string result = Gsm7.DecodeFromBytes(decoded); + byte[] encoded = Gsm7.Encode(text); + string result = Gsm7.Decode(encoded); Assert.Equal(text, result); } diff --git a/src/HeboTech.ATLib.Tests/PDU/PhoneNumberDecoderTests.cs b/src/HeboTech.ATLib.Tests/PDU/PhoneNumberDecoderTests.cs new file mode 100644 index 0000000..a726597 --- /dev/null +++ b/src/HeboTech.ATLib.Tests/PDU/PhoneNumberDecoderTests.cs @@ -0,0 +1,34 @@ +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using HeboTech.ATLib.PDU; +using System; +using Xunit; + +namespace HeboTech.ATLib.Tests.PDU +{ + public class PhoneNumberDecoderTests + { + [Theory] + // National + [InlineData("8189674523F1", "987654321")] + + // International + [InlineData("912143658709", "+1234567890")] + [InlineData("91447721436587", "+447712345678")] + [InlineData("914477214365F7", "+44771234567")] + [InlineData("915155214365F7", "+15551234567")] + + // Alphanumeric + [InlineData("D0C4F23C7D760390EF7619", "Design@Home")] + [InlineData("50C82293F98CC966", "HELLO123")] + [InlineData("D0D4323B1D06", "Telia")] + public void Decode_PhoneNumber_tests(string data, string number) + { + var bytes = Convert.FromHexString(data); + PhoneNumberDTO phoneNumber = PhoneNumberDecoder.DecodePhoneNumber(bytes); + + Assert.NotNull(phoneNumber); + Assert.Equal(number, phoneNumber.Number); + } + } +} diff --git a/src/HeboTech.ATLib.Tests/PDU/ReceivedMessageTypeParserTests.cs b/src/HeboTech.ATLib.Tests/PDU/ReceivedMessageTypeParserTests.cs new file mode 100644 index 0000000..b88da7c --- /dev/null +++ b/src/HeboTech.ATLib.Tests/PDU/ReceivedMessageTypeParserTests.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HeboTech.ATLib.Tests.PDU +{ + internal class ReceivedMessageTypeParserTests + { + } +} diff --git a/src/HeboTech.ATLib.Tests/PDU/SmsDecoderTests.cs b/src/HeboTech.ATLib.Tests/PDU/SmsDecoderTests.cs new file mode 100644 index 0000000..ed8596a --- /dev/null +++ b/src/HeboTech.ATLib.Tests/PDU/SmsDecoderTests.cs @@ -0,0 +1,48 @@ +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.PDU; +using System; +using Xunit; + +namespace HeboTech.ATLib.Tests.PDU +{ + public class SmsDecoderTests + { + [Theory] + [InlineData("07917238010010F5040BC87238880900F10000993092516195800AE8329BFD4697D9EC37", "+27831000015", "27838890001", "29.03.2099 15:16:59 +02:00", "hellohello")] + [InlineData("07911326040000F0040B911346610089F60000208062917314800CC8F71D14969741F977FD07", "+31624000000", "+31641600986", "26.08.2002 19:37:41 +02:00", "How are you?")] + public void Decode_SmsDeliver(string data, string serviceCenterNumber, string senderNumber, string timestamp, string message) + { + var bytes = Convert.FromHexString(data); + Sms sms = SmsDecoder.Decode(bytes, SmsStatus.REC_UNREAD); + SmsDeliver smsDeliver = sms as SmsDeliver; + + Assert.NotNull(sms); + Assert.NotNull(smsDeliver); + Assert.Equal(serviceCenterNumber, smsDeliver.ServiceCenterNumber.ToString()); + Assert.Equal(senderNumber, smsDeliver.SenderNumber.ToString()); + Assert.Equal(DateTimeOffset.Parse(timestamp), smsDeliver.Timestamp); + Assert.Equal(message, smsDeliver.Message); + } + + [Theory] + [InlineData("06916309002100067708A025057218422180218500404221802185004000", "+3690001200", "52502781", "08.12.2024 12:58:00 +01:00", "08.12.2024 12:58:00 +01:00", 119, SmsDeliveryStatus.Message_received_by_SME)] + [InlineData("06918509002100067808A025057218422180219500404221802195004000", "+5890001200", "52502781", "08.12.2024 12:59:00 +01:00", "08.12.2024 12:59:00 +01:00", 120, SmsDeliveryStatus.Message_received_by_SME)] + [InlineData("06918509002100067808A025057218422180219500404221802195004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "+5890001200", "52502781", "08.12.2024 12:59:00 +01:00", "08.12.2024 12:59:00 +01:00", 120, SmsDeliveryStatus.Message_received_by_SME)] + public void Decode_SmsStatusReport(string data, string serviceCenterNumber, string senderNumber, string serviceCenterTimestamp, string dischargeTimestamp, int messageReference, SmsDeliveryStatus status) + { + var bytes = Convert.FromHexString(data); + Sms sms = SmsDecoder.Decode(bytes, SmsStatus.REC_UNREAD); + SmsStatusReport smsStatusReport = sms as SmsStatusReport; + + Assert.NotNull(sms); + Assert.NotNull(smsStatusReport); + Assert.Equal(serviceCenterNumber, smsStatusReport.ServiceCenterAddress.ToString()); + Assert.Equal(senderNumber, smsStatusReport.RecipientAddress.ToString()); + Assert.Equal(DateTimeOffset.Parse(serviceCenterTimestamp), smsStatusReport.ServiceCenterTimestamp); + Assert.Equal(DateTimeOffset.Parse(dischargeTimestamp), smsStatusReport.DischargeTime); + Assert.Equal(status, smsStatusReport.Status); + Assert.Equal(MessageTypeIndicatorInbound.SMS_STATUS_REPORT, smsStatusReport.MessageTypeIndicator); + Assert.Equal(messageReference, smsStatusReport.MessageReference); + } + } +} diff --git a/src/HeboTech.ATLib.Tests/PDU/SmsDeliverDecoderTests.cs b/src/HeboTech.ATLib.Tests/PDU/SmsDeliverDecoderTests.cs index 74037af..1ae08a4 100644 --- a/src/HeboTech.ATLib.Tests/PDU/SmsDeliverDecoderTests.cs +++ b/src/HeboTech.ATLib.Tests/PDU/SmsDeliverDecoderTests.cs @@ -1,6 +1,7 @@ using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Extensions; using HeboTech.ATLib.PDU; +using System; using Xunit; namespace HeboTech.ATLib.Tests.PDU @@ -8,8 +9,18 @@ namespace HeboTech.ATLib.Tests.PDU public class SmsDeliverDecoderTests { [Theory] - [InlineData("07917238010010F5040BC87238880900F10000993092516195800AE8329BFD4697D9EC37", "+27831000015", "27838890001", "99-03-29-15-16-59-+02", "hellohello")] - [InlineData("07911326040000F0040B911346610089F60000208062917314800CC8F71D14969741F977FD07", "+31624000000", "+31641600986", "02-08-26-19-37-41-+02", "How are you?")] + [InlineData("07917238010010F5040BC87238880900F10000993092516195800AE8329BFD4697D9EC37", "+27831000015", "27838890001", "29.03.2099 15:16:59 +02:00", "hellohello")] + [InlineData("07911326040000F0040B911346610089F60000208062917314800CC8F71D14969741F977FD07", "+31624000000", "+31641600986", "2002.08.26 19:37:41 +02:00", "How are you?")] + + [InlineData("069174290021104009D0D4323B1D06000042218091255540A0060804E2C30401D6327BFD6EB7CB6E103DCD06BDE7F31048589E33D6207A999D0EBBDCEFD7FAEDA687DD7479F90C32BFE5A00768FD6EB7CBA034E81C769F41ED3219B47EBBE961377DFD96D3CB7410394DA7BB40D2F2393DA7CBCBF2B4FB3C87CBDFF3F27C5E7683D0613948CC4ED3E9A0B29B2C2FD341EF33A8FD00B91FA0B39A212FCF41F007885E66A7C3", "+4792001201", "Telia", "08.12.2024 19:52:55 +01:00", "Velkommen til oss! Besøk telia.no/kontantreg for å komme i gang med kontantkortet ditt. Registreringsprosessen har blitt endret og må nå gjøres på telia")] + + // Three part message + // Part 1 + [InlineData("06917429002120440A916425068179000042219011816540A0050003020301986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D4F8FD3EE33A8CC4ED359A079990C22BF41E5747DDE7E9341F4721BFE9683D2EE719A9C26D7DD74509D0E6287C56F791954A683C86FF65B5E06B5C36777181466A7E3F5B0AB4A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7E7", "+4792001202", "+4652601897", "09.12.2024 11:18:56 +01:00", "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis")] + // Part 2 + [InlineData("06917429002120400A916425068179000042219011816540A005000302030240EEF79C2EAF9341657C593E4ED3C3F4F4DB0DAAB3D9E1F6F80D6287C56F797A0E72A7E769509D0E0AB3D3F17A1A0E2AE341E53068FC6EB7DFE43768FC76CFCBF17A98EE22D6D37350B84E2F83D2F2BABC0C22BFD96F3928ED06C9CB7079195D7693CBF2341D947683EC6F761D4E0FD3CB207B999DA683CAF37919344EB3D9F53688FC66BFE5", "+4792001202", "+4652601897", "09.12.2024 11:18:56 +01:00", " nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolor")] + // Part 3 + [InlineData("06917429002120440A91642506817900004221901181754090050003020303CAA0721D64AE9FD3613AC85D67B3C32078589E0ED3EB7257113F2EC3E9E5BA1C344FBBE9A0F7781C2E8FC374D0B80E4F93C3F4301DE47EBB4170F93B4D2EBBE92CD0BCEEA683D26ED0B8CE868741F17A1AF4369BD3E37418442ECFCBF2BA9B0E6ABFD9EC341D1476A7DBA03419549ED341ECB0F82DAFB75D", "+4792001202", "+4652601897", "09.12.2024 11:18:57 +01:00", "e eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")] public void Decode_SmsDeliver_tests(string data, string serviceCenterNumber, string senderNumber, string timestamp, string message) { var bytes = data.ToByteArray(); @@ -18,7 +29,7 @@ public void Decode_SmsDeliver_tests(string data, string serviceCenterNumber, str Assert.NotNull(pduMessage); Assert.Equal(serviceCenterNumber, pduMessage.ServiceCenterNumber.ToString()); Assert.Equal(senderNumber, pduMessage.SenderNumber.ToString()); - Assert.Equal(timestamp, pduMessage.Timestamp.ToString("yy-MM-dd-HH-mm-ss-zz")); + Assert.Equal(DateTimeOffset.Parse(timestamp), pduMessage.Timestamp); Assert.Equal(message, pduMessage.Message); } } diff --git a/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs b/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs index 533fd1b..b473490 100644 --- a/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs +++ b/src/HeboTech.ATLib.Tests/PDU/SmsSubmitEncoderTests.cs @@ -10,7 +10,6 @@ namespace HeboTech.ATLib.Tests.PDU { public class SmsSubmitEncoderTests { - [Theory] [InlineData("", "56840182", "Tada", CharacterSet.Gsm7, true, new string[] { "00010008A065481028000004D430390C" })] [InlineData("", "56840182", "Tada :)", CharacterSet.Gsm7, true, new string[] { "00010008A065481028000007D430390CD2A500" })] @@ -20,6 +19,9 @@ public class SmsSubmitEncoderTests [InlineData("", "12345678", "😀", CharacterSet.UCS2, true, new string[] { "00010008A021436587000804D83DDE00" })] [InlineData("", "12345678", "😀😹📱📶📞", CharacterSet.UCS2, true, new string[] { "00010008A021436587000814D83DDE00D83DDE39D83DDCF1D83DDCF6D83DDCDE" })] [InlineData("", "12345678", "A😀B😹C📱D📶E📞F", CharacterSet.UCS2, true, new string[] { "00010008A0214365870008200041D83DDE000042D83DDE390043D83DDCF10044D83DDCF60045D83DDCDE0046" })] + [InlineData("", "12345678", "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostru", CharacterSet.Gsm7, false, new string[] { "010008A0214365870000A0CCB7BCDC06A5E1F37A1B447EB3DF72D03C4D0785DB653A0B347EBBE7E531BD4CAFCB4161721A9E9EA7C769F7195466A7E92CD0BC4C0691DFA072BA3E6FBFC9207AB90D7FCB4169F7384D4E93EB6E3AA84E07B1C3E2B7BC0C2AD341E437FB2D2F83DAE1B33B0C0AB3D3F17AD855A583CAEE741B142683DA6977BA0DB297DDE9709B058AD7D37390FB3DA7CBEB" })] // 160 characters (one SMS) + + // Three part message [InlineData( "1", "5125551234", @@ -27,9 +29,9 @@ public class SmsSubmitEncoderTests CharacterSet.Gsm7, true, new string[] { - "0041000B915121551532F40000A00500030C0301986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D4F8FD3EE33A8CC4ED359A079990C22BF41E5747DDE7E9341F4721BFE9683D2EE719A9C26D7DD74509D0E6287C56F791954A683C86FF65B5E06B5C36777181466A7E3F5B0AB4A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7E700", - "0041000B915121551532F40000A00500030C030240EEF79C2EAF9341657C593E4ED3C3F4F4DB0DAAB3D9E1F6F80D6287C56F797A0E72A7E769509D0E0AB3D3F17A1A0E2AE341E53068FC6EB7DFE43768FC76CFCBF17A98EE22D6D37350B84E2F83D2F2BABC0C22BFD96F3928ED06C9CB7079195D7693CBF2341D947683EC6F761D4E0FD3CB207B999DA683CAF37919344EB3D9F53688FC66BFE500", - "0041000B915121551532F40000900500030C0303CAA0721D64AE9FD3613AC85D67B3C32078589E0ED3EB7257113F2EC3E9E5BA1C344FBBE9A0F7781C2E8FC374D0B80E4F93C3F4301DE47EBB4170F93B4D2EBBE92CD0BCEEA683D26ED0B8CE868741F17A1AF4369BD3E37418442ECFCBF2BA9B0E6ABFD9EC341D1476A7DBA03419549ED341ECB0F82DAFB75D00" +"0041000B915121551532F40000A00500030C0301986F79B90D4AC3E7F53688FC66BFE5A0799A0E0AB7CB741668FC76CFCB637A995E9783C2E4343C3D4F8FD3EE33A8CC4ED359A079990C22BF41E5747DDE7E9341F4721BFE9683D2EE719A9C26D7DD74509D0E6287C56F791954A683C86FF65B5E06B5C36777181466A7E3F5B0AB4A0795DDE936284C06B5D3EE741B642FBBD3E1360B14AFA7E7", + "0041000B915121551532F40000A00500030C030240EEF79C2EAF9341657C593E4ED3C3F4F4DB0DAAB3D9E1F6F80D6287C56F797A0E72A7E769509D0E0AB3D3F17A1A0E2AE341E53068FC6EB7DFE43768FC76CFCBF17A98EE22D6D37350B84E2F83D2F2BABC0C22BFD96F3928ED06C9CB7079195D7693CBF2341D947683EC6F761D4E0FD3CB207B999DA683CAF37919344EB3D9F53688FC66BFE5", + "0041000B915121551532F40000900500030C0303CAA0721D64AE9FD3613AC85D67B3C32078589E0ED3EB7257113F2EC3E9E5BA1C344FBBE9A0F7781C2E8FC374D0B80E4F93C3F4301DE47EBB4170F93B4D2EBBE92CD0BCEEA683D26ED0B8CE868741F17A1AF4369BD3E37418442ECFCBF2BA9B0E6ABFD9EC341D1476A7DBA03419549ED341ECB0F82DAFB75D" })] public void Encode_SmsSubmit_test(string countryCode, string subscriberNumber, string encodedMessage, CharacterSet dataCodingScheme, bool includeEmptySmscLength, string[] answer) @@ -40,11 +42,13 @@ public void Encode_SmsSubmit_test(string countryCode, string subscriberNumber, s encodedMessage, dataCodingScheme) { - IncludeEmptySmscLength = includeEmptySmscLength, MessageReferenceNumber = 12 - }); + }, includeEmptySmscLength); - Assert.Equal(answer, encoded.ToArray()); + for (int i = 0; i < answer.Length; i++) + { + Assert.Equal(answer[i], encoded.ElementAt(i)); + } } [Theory] @@ -57,10 +61,27 @@ public void Encode_SmsSubmit_message_too_long_test(string countryCode, string su new string('a', characterCount), dataCodingScheme) { - IncludeEmptySmscLength = includeEmptySmscLength, MessageReferenceNumber = 12 }; - Assert.Throws<ArgumentException>(() => SmsSubmitEncoder.Encode(request).ToList()); + Assert.Throws<ArgumentException>(() => SmsSubmitEncoder.Encode(request, includeEmptySmscLength).ToList()); + } + + [Theory] + [InlineData("", "56840182", 39_015, CharacterSet.Gsm7, true, 255)] // Max message length is 39015 characters. Max (multi)message size is 153. 39015/153=255 + [InlineData("", "56840182", 17_085, CharacterSet.UCS2, true, 255)] // Max message length is 17085 characters. Max (multi)message size is 67. 17085/67=255 + public void Encode_SmsSubmit_message_max_length_test(string countryCode, string subscriberNumber, int characterCount, CharacterSet dataCodingScheme, bool includeEmptySmscLength, int expectedMessageParts) + { + var request = new SmsSubmitRequest( + new PhoneNumber(countryCode, subscriberNumber), + new string('a', characterCount), + dataCodingScheme) + { + MessageReferenceNumber = 12 + }; + + IEnumerable<string> encoded = SmsSubmitEncoder.Encode(request, includeEmptySmscLength); + + Assert.Equal(expectedMessageParts, encoded.Count()); } } } diff --git a/src/HeboTech.ATLib/CodingSchemes/Gsm7.cs b/src/HeboTech.ATLib/CodingSchemes/Gsm7.cs index a0af7ef..250a1ab 100644 --- a/src/HeboTech.ATLib/CodingSchemes/Gsm7.cs +++ b/src/HeboTech.ATLib/CodingSchemes/Gsm7.cs @@ -14,7 +14,7 @@ internal static class Gsm7 // ` is not a conversion, just a untranslatable letter private static readonly Dictionary<Gsm7Extension, string> regularTable = new Dictionary<Gsm7Extension, string>() { - { Gsm7Extension.Default, "@£$¥èéùìòÇ`Øø`ÅåΔ_ΦΓΛΩΠΨΣΘΞ`ÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑÜ`¿abcdefghijklmnopqrstuvwxyzäöñüà" }, + { Gsm7Extension.Default, "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞ\x1BÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑÜ`¿abcdefghijklmnopqrstuvwxyzäöñüà" }, { Gsm7Extension.Turkish, "@£$¥€éùıòÇLĞğCÅåΔ_ΦΓΛΩΠΨΣΘFEŞRßÉ !\"#¤%&'()ΞS,ş./0123456789*C<->?İABCDEFGHI:+L=NOPQRSTUVWXYJ;ÖMܧçabcdefghiZKlÑnopqrstuvwxyjÄömüà" }, { Gsm7Extension.Spanish, "@£$¥èéùìòÇ`Øø`ÅåΔ_ΦΓΛΩΠΨΣΘΞ`ÆæßÉ !\"#¤%&'()*=,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑÜ`¿abcdefghijklmnopqrstuvwxyzäöñüà" }, { Gsm7Extension.Portugese, "@£$¥êéúíóçLÔôCÁáΔ_ªÇÀ∞^\\€ÓFEÂRÊÉ !\"#º%&'()|S,â./0123456789*C<->?ÍABCDEFGHI:+L=NOPQRSTUVWXYJ;ÕMܧ~abcdefghiZKlÚnopqrstuvwxyjÃõmüà" }, @@ -68,7 +68,11 @@ public static bool IsGsm7Compatible(IEnumerable<char> text, Gsm7Extension lockin return true; } - public static byte[] EncodeToBytes(IEnumerable<char> text, Gsm7Extension lockingShift = Gsm7Extension.Default, Gsm7Extension singleShift = Gsm7Extension.Default) + /// <summary> + /// Encodes 8-bit text (as bytes) into 7-bit text (as bytes) + /// </summary> + /// <returns></returns> + public static byte[] Encode(IEnumerable<char> text, int fillBits = 0, Gsm7Extension lockingShift = Gsm7Extension.Default, Gsm7Extension singleShift = Gsm7Extension.Default) { string defaultString = regularTable[lockingShift]; string extendedString = extendedTable[singleShift]; @@ -89,29 +93,37 @@ public static byte[] EncodeToBytes(IEnumerable<char> text, Gsm7Extension locking int intExtendedTable = extendedString.IndexOf(c); if (intExtendedTable != -1) { - byteGSMOutput.Add(27); + byteGSMOutput.Add(0x1B); byteGSMOutput.Add((byte)intExtendedTable); } } - return byteGSMOutput.ToArray(); + var packed = Pack(byteGSMOutput.ToArray(), fillBits); + + return packed; } - public static string DecodeFromBytes(IEnumerable<byte> bytes, Gsm7Extension lockingShift = Gsm7Extension.Default, Gsm7Extension singleShift = Gsm7Extension.Default) + /// <summary> + /// Decodes 7-bit text (as bytes) into 8-bit text (as bytes) + /// </summary> + /// <returns></returns> + public static string Decode(IEnumerable<byte> bytes, int fillBits = 0, Gsm7Extension lockingShift = Gsm7Extension.Default, Gsm7Extension singleShift = Gsm7Extension.Default) { + var unpacked = Unpack(bytes.ToArray(), fillBits); + string defaultString = regularTable[lockingShift]; string extendedString = extendedTable[singleShift]; - StringBuilder sb = new StringBuilder(bytes.Count()); + StringBuilder sb = new StringBuilder(unpacked.Count()); bool isExtended = false; - for (int i = 0; i < bytes.Count(); i++) + for (int i = 0; i < unpacked.Count(); i++) { - byte b = bytes.ElementAt(i); + byte b = unpacked.ElementAt(i); - if (b == 27) + if (b == 0x1B) { - if (i == bytes.Count() - 1) // If the ESC character is the last character for some reason - treat it as a space + if (i == unpacked.Count() - 1) // If the ESC character is the last character for some reason - treat it as a space { sb.Append(' '); continue; @@ -137,10 +149,10 @@ public static string DecodeFromBytes(IEnumerable<byte> bytes, Gsm7Extension lock return sb.ToString(); } - public static byte[] Pack(byte[] data, int paddingBits = 0) + internal static byte[] Pack(byte[] data, int paddingBits = 0) { // Array for all packed bits (n x 7) - BitArray packedBits = new BitArray((int)Math.Ceiling(data.Length * 7 / 8.0) * 8 + paddingBits); + BitArray packedBits = new BitArray((data.Length * 7) + paddingBits); // Loop through all characters for (int i = 0; i < data.Length; i++) @@ -156,7 +168,8 @@ public static byte[] Pack(byte[] data, int paddingBits = 0) } // Convert the bit array to a byte array - byte[] packed = new byte[(int)Math.Ceiling(packedBits.Length / 8.0)]; + int numberOfBytes = packedBits.Length % 8 == 0 ? packedBits.Length / 8 : (packedBits.Length / 8) + 1; + byte[] packed = new byte[numberOfBytes]; packedBits.CopyTo(packed, 0); // Return the septets packed as octets @@ -166,7 +179,36 @@ public static byte[] Pack(byte[] data, int paddingBits = 0) return packed; } - public static byte[] Unpack(byte[] data, int paddingBits = 0) + //internal static byte[] Pack(byte[] data, int paddingBits = 0) + //{ + // // Array for all packed bits (n x 7) + // BitArray packedBits = new BitArray((int)Math.Ceiling(data.Length * 7 / 8.0) * 8 + paddingBits); + + // // Loop through all characters + // for (int i = 0; i < data.Length; i++) + // { + // // Only 7 bits in each byte is data + // for (int j = 0; j < 7; j++) + // { + // // For each 7 bits in each byte, add it to the bit array + // int index = (i * 7) + j + paddingBits; + // bool isSet = (data[i] & (1 << j)) != 0; + // packedBits.Set(index, isSet); + // } + // } + + // // Convert the bit array to a byte array + // byte[] packed = new byte[(int)Math.Ceiling(packedBits.Length / 8.0)]; + // packedBits.CopyTo(packed, 0); + + // // Return the septets packed as octets + // // If the last character is empty - skip it + // if (packed[^1] == 0) + // return packed[..^1]; + // return packed; + //} + + internal static byte[] Unpack(byte[] data, int paddingBits = 0) { BitArray packedBits = new BitArray(data); packedBits.Length += paddingBits; diff --git a/src/HeboTech.ATLib/DTOs/BroadcastMessage.cs b/src/HeboTech.ATLib/DTOs/BroadcastMessage.cs new file mode 100644 index 0000000..02baf27 --- /dev/null +++ b/src/HeboTech.ATLib/DTOs/BroadcastMessage.cs @@ -0,0 +1,9 @@ +namespace HeboTech.ATLib.DTOs +{ + public class BroadcastMessage + { + public BroadcastMessage() + { + } + } +} diff --git a/src/HeboTech.ATLib/DTOs/Sms.cs b/src/HeboTech.ATLib/DTOs/Sms.cs index 4fafdc6..e7d45a8 100644 --- a/src/HeboTech.ATLib/DTOs/Sms.cs +++ b/src/HeboTech.ATLib/DTOs/Sms.cs @@ -1,36 +1,26 @@ -using System; +using HeboTech.ATLib.PDU; namespace HeboTech.ATLib.DTOs { - public class Sms + public abstract class Sms { - public Sms(SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message) - : this(status, sender, receiveTime, message, 0, 1, 1) + protected Sms(MessageTypeIndicatorInbound messageTypeIndicator) { + MessageTypeIndicator = messageTypeIndicator; } - public Sms(SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message, int messageReferenceNumber, int totalNumberOfParts, int partNumber) + protected Sms(MessageTypeIndicatorInbound messageTypeIndicator, int messageReference) + : this(messageTypeIndicator) { - Status = status; - Sender = sender; - ReceiveTime = receiveTime; - Message = message; - MessageReferenceNumber = messageReferenceNumber; - TotalNumberOfParts = totalNumberOfParts; - PartNumber = partNumber; + MessageReference = messageReference; } - public SmsStatus Status { get; } - public PhoneNumberDTO Sender { get; } - public DateTimeOffset ReceiveTime { get;} - public string Message { get; } - public int MessageReferenceNumber { get; } - public int TotalNumberOfParts { get; } - public int PartNumber { get; } + public int MessageReference { get; } + public MessageTypeIndicatorInbound MessageTypeIndicator { get; } public override string ToString() { - return $"Sender:\t\t{Sender}{Environment.NewLine}ReceiveTime:\t{ReceiveTime}{Environment.NewLine}Ref. no.:\t{MessageReferenceNumber}{Environment.NewLine}Part:\t\t{PartNumber}/{TotalNumberOfParts}{Environment.NewLine}Message:\t{Message}"; + return $"MTI: {MessageTypeIndicator}. Msg. ref.: {MessageReference}."; } } -} +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/DTOs/SmsDeliver.cs b/src/HeboTech.ATLib/DTOs/SmsDeliver.cs index 05e14dd..dbc28fa 100644 --- a/src/HeboTech.ATLib/DTOs/SmsDeliver.cs +++ b/src/HeboTech.ATLib/DTOs/SmsDeliver.cs @@ -1,13 +1,15 @@ -using System; +using HeboTech.ATLib.PDU; +using System; namespace HeboTech.ATLib.DTOs { /// <summary> /// Data object for a received SMS /// </summary> - public class SmsDeliver + public class SmsDeliver : Sms { public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumber, string message, DateTimeOffset timestamp) + : base(MessageTypeIndicatorInbound.SMS_DELIVER) { ServiceCenterNumber = serviceCenterNumber; SenderNumber = senderNumber; @@ -15,13 +17,13 @@ public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumbe Timestamp = timestamp; } - public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumber, string message, DateTimeOffset timestamp, int messageReferenceNumber, int totalNumberOfParts, int partNumber) + public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumber, string message, DateTimeOffset timestamp, int messageReference, int totalNumberOfParts, int partNumber) + : base(MessageTypeIndicatorInbound.SMS_DELIVER, messageReference) { ServiceCenterNumber = serviceCenterNumber; SenderNumber = senderNumber; Message = message; Timestamp = timestamp; - MessageReferenceNumber = messageReferenceNumber; TotalNumberOfParts = totalNumberOfParts; PartNumber = partNumber; } @@ -30,8 +32,16 @@ public SmsDeliver(PhoneNumberDTO serviceCenterNumber, PhoneNumberDTO senderNumbe public PhoneNumberDTO SenderNumber { get; } public string Message { get; } public DateTimeOffset Timestamp { get; } - public int MessageReferenceNumber { get; } public int TotalNumberOfParts { get; } public int PartNumber { get; } + + public void DeliverMethod() + { + } + + public override string ToString() + { + return base.ToString() + $" Timestamp: {Timestamp}. From: {SenderNumber}. Message: {Message}."; + } } } diff --git a/src/HeboTech.ATLib/DTOs/SmsDeliveryStatus.cs b/src/HeboTech.ATLib/DTOs/SmsDeliveryStatus.cs new file mode 100644 index 0000000..245610a --- /dev/null +++ b/src/HeboTech.ATLib/DTOs/SmsDeliveryStatus.cs @@ -0,0 +1,55 @@ +namespace HeboTech.ATLib.DTOs +{ + /// <summary> + /// Bits 0..6. Bit 7 is reserved. + /// </summary> + public enum SmsDeliveryStatus : byte + { + // Suffix _1, _2 and _3 are used to separate identical names, one suffix for each 'group' of messages + + // Transaction completed + Message_received_by_SME = 0b0000_0000, + Forwarded_to_SME_but_unconfirmed_delivery = 0b0000_0001, + Message_replaced_by_the_SC = 0b0000_0010, + + // 000_0011..000_1111 Reserved + // 001_0000..0011111 Values specific to each SC + + // Temporary error, SC still trying to transfer to SM + Congestion_1 = 0b0010_0000, + SME_busy_1 = 0b0010_0001, + Service_rejected_1 = 0b0010_0011, + Quality_of_service_not_available_1 = 0b0010_0100, + Error_in_SME_1 = 0b0010_0101, + + // 0100110..0101111 Reserved + // 0110000..0111111 Values specific to each SC + + // Permanent error, SC is not making any more transfer attempts + Remote_procedure_error = 0b0100_0000, + Incompatible_destination = 0b0100_0001, + Connection_rejected_by_SME = 0b0100_0010, + Not_obtainable = 0b0100_0011, + Quality_of_service_not_available_2 = 0b0100_0100, + No_interworking_available = 0b0100_0101, + SM_validity_period_expired = 0b0100_0110, + SM_deleted_by_originating_SME = 0b0100_0111, + SM_deleted_by_SC_administration = 0b0100_1000, + SM_does_not_exist = 0b0100_1001, + + // 1001010..1001111 Reserved + // 1010000..1011111 Values specific to each SC + + // Temporary error, SC is not making any more transfer attempts + Congestion_3 = 0b0110_0000, + SME_busy_3 = 0b0110_0001, + No_response_from_SME = 0b0110_0010, + Service_rejected_3 = 0b0110_0011, + Quality_of_service_not_available_3 = 0b0110_0100, + Error_in_SME_3 = 0b0110_0101, + + //1100110..1101001 Reserved + //1101010..1101111 Reserved + //1110000..1111111 Values specific to each SC + } +} diff --git a/src/HeboTech.ATLib/DTOs/SmsStatusReport.cs b/src/HeboTech.ATLib/DTOs/SmsStatusReport.cs new file mode 100644 index 0000000..0d17e13 --- /dev/null +++ b/src/HeboTech.ATLib/DTOs/SmsStatusReport.cs @@ -0,0 +1,29 @@ +using HeboTech.ATLib.PDU; +using System; + +namespace HeboTech.ATLib.DTOs +{ + public class SmsStatusReport : Sms + { + public SmsStatusReport(int messageReference, PhoneNumberDTO recipientAddress, PhoneNumberDTO serviceCenterAddress, DateTimeOffset serviceCenterTimestamp, DateTimeOffset dischargeTime, SmsDeliveryStatus status) + : base(MessageTypeIndicatorInbound.SMS_STATUS_REPORT, messageReference) + { + RecipientAddress = recipientAddress; + ServiceCenterAddress = serviceCenterAddress; + ServiceCenterTimestamp = serviceCenterTimestamp; + DischargeTime = dischargeTime; + Status = status; + } + + public PhoneNumberDTO RecipientAddress { get; } + public PhoneNumberDTO ServiceCenterAddress { get; } + public DateTimeOffset ServiceCenterTimestamp { get; } + public DateTimeOffset DischargeTime { get; } + public SmsDeliveryStatus Status { get; } + + public override string ToString() + { + return base.ToString() + $" Delivered with status {Status}. RA: {RecipientAddress}. SCTS: {ServiceCenterTimestamp}. DT: {DischargeTime}."; + } + } +} diff --git a/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs b/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs index a9e0590..ede6e5e 100644 --- a/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs +++ b/src/HeboTech.ATLib/DTOs/SmsSubmitRequest.cs @@ -34,38 +34,17 @@ public SmsSubmitRequest( PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - : this( - phoneNumber, - message, - codingScheme, - ValidityPeriod.NotPresent()) - { - } - - /// <summary> - /// Creates a data object for submitting an SMS in PDU format. - /// </summary> - /// <param name="phoneNumber">The receiver phone number</param> - /// <param name="message">The message to send</param> - /// <param name="codingScheme">The coding scheme to use</param> - /// <param name="validityPeriod">The validity period to use</param> - public SmsSubmitRequest( - PhoneNumber phoneNumber, - string message, - CharacterSet codingScheme, - ValidityPeriod validityPeriod) { PhoneNumber = phoneNumber; Message = message; CodingScheme = codingScheme; - ValidityPeriod = validityPeriod; } public PhoneNumber PhoneNumber { get; } public string Message { get; } public CharacterSet CodingScheme { get; } - public bool IncludeEmptySmscLength { get; set; } public byte MessageReferenceNumber { get; set; } public ValidityPeriod ValidityPeriod { get; set; } + public bool EnableStatusReportRequest { get; set; } } } diff --git a/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs b/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs index fee9dbe..ee387fa 100644 --- a/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs +++ b/src/HeboTech.ATLib/DTOs/SmsWithIndex.cs @@ -1,26 +1,14 @@ -using System; - -namespace HeboTech.ATLib.DTOs +namespace HeboTech.ATLib.DTOs { - public class SmsWithIndex : Sms + public class SmsWithIndex { - public SmsWithIndex(int index, SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message) - : base(status, sender, receiveTime, message) - { - Index = index; - } - - public SmsWithIndex(int index, SmsStatus status, PhoneNumberDTO sender, DateTimeOffset receiveTime, string message, int messageReferenceNumber, int totalNumberOfParts, int partNumber) - : base(status, sender, receiveTime, message, messageReferenceNumber, totalNumberOfParts, partNumber) + public SmsWithIndex(Sms sms, int index) { + Sms = sms; Index = index; } + public Sms Sms { get; } public int Index { get; } - - public override string ToString() - { - return $"Index:\t\t{Index}{Environment.NewLine}" + base.ToString(); - } } -} +} \ No newline at end of file diff --git a/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs b/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs index 3c64718..d20b9fc 100644 --- a/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs +++ b/src/HeboTech.ATLib/DTOs/ValidityPeriod.cs @@ -40,6 +40,8 @@ private ValidityPeriod(ValidityPeriodFormat format, byte[] value) /// <returns></returns> public static ValidityPeriod Relative(byte value) => new ValidityPeriod(ValidityPeriodFormat.Relative, new byte[] { value }); + public static ValidityPeriod Relative(RelativeValidityPeriods value) => Relative((byte)value); + /// <summary> /// An absolute validity period /// </summary> @@ -51,4 +53,264 @@ public static ValidityPeriod Absolute(DateTimeOffset value) return new ValidityPeriod(ValidityPeriodFormat.Absolute, encoded); } } + + public enum RelativeValidityPeriods + { + Minutes_5 = 0, + Minutes_10 = 1, + Minutes_15 = 2, + Minutes_20 = 3, + Minutes_25 = 4, + Minutes_30 = 5, + Minutes_35 = 6, + Minutes_40 = 7, + Minutes_45 = 8, + Minutes_50 = 9, + Minutes_55 = 10, + Hours_1 = 11, + Hours_1_Minutes_5 = 12, + Hours_1_Minutes_10 = 13, + Hours_1_Minutes_15 = 14, + Hours_1_Minutes_20 = 15, + Hours_1_Minutes_25 = 16, + Hours_1_Minutes_30 = 17, + Hours_1_Minutes_35 = 18, + Hours_1_Minutes_40 = 19, + Hours_1_Minutes_45 = 20, + Hours_1_Minutes_50 = 21, + Hours_1_Minutes_55 = 22, + Hours_2 = 23, + Hours_2_Minutes_5 = 24, + Hours_2_Minutes_10 = 25, + Hours_2_Minutes_15 = 26, + Hours_2_Minutes_20 = 27, + Hours_2_Minutes_25 = 28, + Hours_2_Minutes_30 = 29, + Hours_2_Minutes_35 = 30, + Hours_2_Minutes_40 = 31, + Hours_2_Minutes_45 = 32, + Hours_2_Minutes_50 = 33, + Hours_2_Minutes_55 = 34, + Hours_3 = 35, + Hours_3_Minutes_5 = 36, + Hours_3_Minutes_10 = 37, + Hours_3_Minutes_15 = 38, + Hours_3_Minutes_20 = 39, + Hours_3_Minutes_25 = 40, + Hours_3_Minutes_30 = 41, + Hours_3_Minutes_35 = 42, + Hours_3_Minutes_40 = 43, + Hours_3_Minutes_45 = 44, + Hours_3_Minutes_50 = 45, + Hours_3_Minutes_55 = 46, + Hours_4 = 47, + Hours_4_Minutes_5 = 48, + Hours_4_Minutes_10 = 49, + Hours_4_Minutes_15 = 50, + Hours_4_Minutes_20 = 51, + Hours_4_Minutes_25 = 52, + Hours_4_Minutes_30 = 53, + Hours_4_Minutes_35 = 54, + Hours_4_Minutes_40 = 55, + Hours_4_Minutes_45 = 56, + Hours_4_Minutes_50 = 57, + Hours_4_Minutes_55 = 58, + Hours_5 = 59, + Hours_5_Minutes_5 = 60, + Hours_5_Minutes_10 = 61, + Hours_5_Minutes_15 = 62, + Hours_5_Minutes_20 = 63, + Hours_5_Minutes_25 = 64, + Hours_5_Minutes_30 = 65, + Hours_5_Minutes_35 = 66, + Hours_5_Minutes_40 = 67, + Hours_5_Minutes_45 = 68, + Hours_5_Minutes_50 = 69, + Hours_5_Minutes_55 = 70, + Hours_6 = 71, + Hours_6_Minutes_5 = 72, + Hours_6_Minutes_10 = 73, + Hours_6_Minutes_15 = 74, + Hours_6_Minutes_20 = 75, + Hours_6_Minutes_25 = 76, + Hours_6_Minutes_30 = 77, + Hours_6_Minutes_35 = 78, + Hours_6_Minutes_40 = 79, + Hours_6_Minutes_45 = 80, + Hours_6_Minutes_50 = 81, + Hours_6_Minutes_55 = 82, + Hours_7 = 83, + Hours_7_Minutes_5 = 84, + Hours_7_Minutes_10 = 85, + Hours_7_Minutes_15 = 86, + Hours_7_Minutes_20 = 87, + Hours_7_Minutes_25 = 88, + Hours_7_Minutes_30 = 89, + Hours_7_Minutes_35 = 90, + Hours_7_Minutes_40 = 91, + Hours_7_Minutes_45 = 92, + Hours_7_Minutes_50 = 93, + Hours_7_Minutes_55 = 94, + Hours_8 = 95, + Hours_8_Minutes_5 = 96, + Hours_8_Minutes_10 = 97, + Hours_8_Minutes_15 = 98, + Hours_8_Minutes_20 = 99, + Hours_8_Minutes_25 = 100, + Hours_8_Minutes_30 = 101, + Hours_8_Minutes_35 = 102, + Hours_8_Minutes_40 = 103, + Hours_8_Minutes_45 = 104, + Hours_8_Minutes_50 = 105, + Hours_8_Minutes_55 = 106, + Hours_9 = 107, + Hours_9_Minutes_5 = 108, + Hours_9_Minutes_10 = 109, + Hours_9_Minutes_15 = 110, + Hours_9_Minutes_20 = 111, + Hours_9_Minutes_25 = 112, + Hours_9_Minutes_30 = 113, + Hours_9_Minutes_35 = 114, + Hours_9_Minutes_40 = 115, + Hours_9_Minutes_45 = 116, + Hours_9_Minutes_50 = 117, + Hours_9_Minutes_55 = 118, + Hours_10 = 119, + Hours_10_Minutes_5 = 120, + Hours_10_Minutes_10 = 121, + Hours_10_Minutes_15 = 122, + Hours_10_Minutes_20 = 123, + Hours_10_Minutes_25 = 124, + Hours_10_Minutes_30 = 125, + Hours_10_Minutes_35 = 126, + Hours_10_Minutes_40 = 127, + Hours_10_Minutes_45 = 128, + Hours_10_Minutes_50 = 129, + Hours_10_Minutes_55 = 130, + Hours_11 = 131, + Hours_11_Minutes_5 = 132, + Hours_11_Minutes_10 = 133, + Hours_11_Minutes_15 = 134, + Hours_11_Minutes_20 = 135, + Hours_11_Minutes_25 = 136, + Hours_11_Minutes_30 = 137, + Hours_11_Minutes_35 = 138, + Hours_11_Minutes_40 = 139, + Hours_11_Minutes_45 = 140, + Hours_11_Minutes_50 = 141, + Hours_11_Minutes_55 = 142, + Hours_12 = 143, + Hours_12_Minutes_30 = 144, + Hours_13 = 145, + Hours_13_Minutes_30 = 146, + Hours_14 = 147, + Hours_14_Minutes_30 = 148, + Hours_15 = 149, + Hours_15_Minutes_30 = 150, + Hours_16 = 151, + Hours_16_Minutes_30 = 152, + Hours_17 = 153, + Hours_17_Minutes_30 = 154, + Hours_18 = 155, + Hours_18_Minutes_30 = 156, + Hours_19 = 157, + Hours_19_Minutes_30 = 158, + Hours_20 = 159, + Hours_20_Minutes_30 = 160, + Hours_21 = 161, + Hours_21_Minutes_30 = 162, + Hours_22 = 163, + Hours_22_Minutes_30 = 164, + Hours_23 = 165, + Hours_23_Minutes_30 = 166, + Hours_24 = 167, + Days_2 = 168, + Days_3 = 169, + Days_4 = 170, + Days_5 = 171, + Days_6 = 172, + Days_7 = 173, + Days_8 = 174, + Days_9 = 175, + Days_10 = 176, + Days_11 = 177, + Days_12 = 178, + Days_13 = 179, + Days_14 = 180, + Days_15 = 181, + Days_16 = 182, + Days_17 = 183, + Days_18 = 184, + Days_19 = 185, + Days_20 = 186, + Days_21 = 187, + Days_22 = 188, + Days_23 = 189, + Days_24 = 190, + Days_25 = 191, + Days_26 = 192, + Days_27 = 193, + Days_28 = 194, + Days_29 = 195, + Days_30 = 196, + Weeks_5 = 197, + Weeks_6 = 198, + Weeks_7 = 199, + Weeks_8 = 200, + Weeks_9 = 201, + Weeks_10 = 202, + Weeks_11 = 203, + Weeks_12 = 204, + Weeks_13 = 205, + Weeks_14 = 206, + Weeks_15 = 207, + Weeks_16 = 208, + Weeks_17 = 209, + Weeks_18 = 210, + Weeks_19 = 211, + Weeks_20 = 212, + Weeks_21 = 213, + Weeks_22 = 214, + Weeks_23 = 215, + Weeks_24 = 216, + Weeks_25 = 217, + Weeks_26 = 218, + Weeks_27 = 219, + Weeks_28 = 220, + Weeks_29 = 221, + Weeks_30 = 222, + Weeks_31 = 223, + Weeks_32 = 224, + Weeks_33 = 225, + Weeks_34 = 226, + Weeks_35 = 227, + Weeks_36 = 228, + Weeks_37 = 229, + Weeks_38 = 230, + Weeks_39 = 231, + Weeks_40 = 232, + Weeks_41 = 233, + Weeks_42 = 234, + Weeks_43 = 235, + Weeks_44 = 236, + Weeks_45 = 237, + Weeks_46 = 238, + Weeks_47 = 239, + Weeks_48 = 240, + Weeks_49 = 241, + Weeks_50 = 242, + Weeks_51 = 243, + Weeks_52 = 244, + Weeks_53 = 245, + Weeks_54 = 246, + Weeks_55 = 247, + Weeks_56 = 248, + Weeks_57 = 249, + Weeks_58 = 250, + Weeks_59 = 251, + Weeks_60 = 252, + Weeks_61 = 253, + Weeks_62 = 254, + Weeks_63 = 255 + } } diff --git a/src/HeboTech.ATLib/Events/BreadcastMessageReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/BreadcastMessageReceivedEventArgs.cs new file mode 100644 index 0000000..cd7d50a --- /dev/null +++ b/src/HeboTech.ATLib/Events/BreadcastMessageReceivedEventArgs.cs @@ -0,0 +1,27 @@ +using HeboTech.ATLib.DTOs; +using System; +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class BreadcastMessageReceivedEventArgs + { + public BreadcastMessageReceivedEventArgs(BroadcastMessage broadcastMessage) + { + BroadcastMessage = broadcastMessage; + } + + public BroadcastMessage BroadcastMessage { get; } + + public static BreadcastMessageReceivedEventArgs CreateFromResponse(string line1, string line2) + { + var line1Match = Regex.Match(line1, @"\+CBM:\s(?<length>\d+)"); + if (line1Match.Success) + { + throw new NotImplementedException(); + } + + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/BreadcastMessageStorageReferenceReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/BreadcastMessageStorageReferenceReceivedEventArgs.cs new file mode 100644 index 0000000..08bcc8b --- /dev/null +++ b/src/HeboTech.ATLib/Events/BreadcastMessageStorageReferenceReceivedEventArgs.cs @@ -0,0 +1,28 @@ +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class BreadcastMessageStorageReferenceReceivedEventArgs + { + public BreadcastMessageStorageReferenceReceivedEventArgs(string storage, int index) + { + Storage = storage; + Index = index; + } + + public string Storage { get; } + public int Index { get; } + + public static BreadcastMessageStorageReferenceReceivedEventArgs CreateFromResponse(string response) + { + var match = Regex.Match(response, @"\+CBMI:\s""(?<storage>[A-Z]+)"",(?<index>\d+)"); + if (match.Success) + { + string storage = match.Groups["storage"].Value; + int index = int.Parse(match.Groups["index"].Value); + return new BreadcastMessageStorageReferenceReceivedEventArgs(storage, index); + } + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs index cbd4c19..613cac9 100644 --- a/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs +++ b/src/HeboTech.ATLib/Events/SmsReceivedEventArgs.cs @@ -1,27 +1,29 @@ -using System.Text.RegularExpressions; +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using HeboTech.ATLib.PDU; +using System.Text.RegularExpressions; namespace HeboTech.ATLib.Events { public class SmsReceivedEventArgs { - public SmsReceivedEventArgs(string storage, int index) + public SmsReceivedEventArgs(SmsDeliver smsDeliver) { - Storage = storage; - Index = index; + SmsDeliver = smsDeliver; } - public string Storage { get; } - public int Index { get; } + public SmsDeliver SmsDeliver { get; } - public static SmsReceivedEventArgs CreateFromResponse(string response) + public static SmsReceivedEventArgs CreateFromResponse(string line1, string line2) { - var match = Regex.Match(response, @"\+CMTI:\s""(?<storage>[A-Z]+)"",(?<index>\d+)"); - if (match.Success) + var line1Match = Regex.Match(line1, @"\+CMT:\s(""(?<alpha>[\+0-9]*)"")?,(?<length>\d+)"); + if (line1Match.Success) { - string storage = match.Groups["storage"].Value; - int index = int.Parse(match.Groups["index"].Value); - return new SmsReceivedEventArgs(storage, index); + byte length = byte.Parse(line1Match.Groups["length"].Value); + var smsDeliver = SmsDeliverDecoder.Decode(line2.ToByteArray()); + return new SmsReceivedEventArgs(smsDeliver); } + return default; } } diff --git a/src/HeboTech.ATLib/Events/SmsStatusReportEventArgs.cs b/src/HeboTech.ATLib/Events/SmsStatusReportEventArgs.cs new file mode 100644 index 0000000..ccdbd58 --- /dev/null +++ b/src/HeboTech.ATLib/Events/SmsStatusReportEventArgs.cs @@ -0,0 +1,30 @@ +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using HeboTech.ATLib.PDU; +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class SmsStatusReportEventArgs + { + public SmsStatusReportEventArgs(SmsStatusReport smsStatusReport) + { + SmsStatusReport = smsStatusReport; + } + + public SmsStatusReport SmsStatusReport { get; } + + public static SmsStatusReportEventArgs CreateFromResponse(string line1, string line2) + { + var line1Match = Regex.Match(line1, @"\+CDS:\s(?<length>\d+)"); + if (line1Match.Success) + { + byte length = byte.Parse(line1Match.Groups["length"].Value); + var report = SmsStatusReportDecoder.Decode(line2.ToByteArray(), length); + return new SmsStatusReportEventArgs(report); + } + + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/SmsStatusReportStorageReferenceEventArgs.cs b/src/HeboTech.ATLib/Events/SmsStatusReportStorageReferenceEventArgs.cs new file mode 100644 index 0000000..f3b0ec3 --- /dev/null +++ b/src/HeboTech.ATLib/Events/SmsStatusReportStorageReferenceEventArgs.cs @@ -0,0 +1,30 @@ +using HeboTech.ATLib.Modems.Generic; +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class SmsStatusReportStorageReferenceEventArgs + { + public SmsStatusReportStorageReferenceEventArgs(MessageStorage storage, int index) + { + Storage = storage; + Index = index; + } + + public MessageStorage Storage { get; } + public int Index { get; } + + public static SmsStatusReportStorageReferenceEventArgs CreateFromResponse(string line1) + { + var match = Regex.Match(line1, @"\+CDSI:\s""(?<storage>[a-zA-Z]+)"",(?<index>\d+)"); + if (match.Success) + { + string storage = match.Groups["storage"].Value; + int index = int.Parse(match.Groups["index"].Value); + return new SmsStatusReportStorageReferenceEventArgs((MessageStorage)storage, index); + } + + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Events/SmsStorageReferenceReceivedEventArgs.cs b/src/HeboTech.ATLib/Events/SmsStorageReferenceReceivedEventArgs.cs new file mode 100644 index 0000000..5b2a591 --- /dev/null +++ b/src/HeboTech.ATLib/Events/SmsStorageReferenceReceivedEventArgs.cs @@ -0,0 +1,28 @@ +using System.Text.RegularExpressions; + +namespace HeboTech.ATLib.Events +{ + public class SmsStorageReferenceReceivedEventArgs + { + public SmsStorageReferenceReceivedEventArgs(string storage, int index) + { + Storage = storage; + Index = index; + } + + public string Storage { get; } + public int Index { get; } + + public static SmsStorageReferenceReceivedEventArgs CreateFromResponse(string response) + { + var match = Regex.Match(response, @"\+CMTI:\s""(?<storage>[A-Z]+)"",(?<index>\d+)"); + if (match.Success) + { + string storage = match.Groups["storage"].Value; + int index = int.Parse(match.Groups["index"].Value); + return new SmsStorageReferenceReceivedEventArgs(storage, index); + } + return default; + } + } +} diff --git a/src/HeboTech.ATLib/Extensions/SmsDeliverExtensions.cs b/src/HeboTech.ATLib/Extensions/SmsDeliverExtensions.cs deleted file mode 100644 index 524cf1c..0000000 --- a/src/HeboTech.ATLib/Extensions/SmsDeliverExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using HeboTech.ATLib.DTOs; - -namespace HeboTech.ATLib.Extensions -{ - internal static class SmsDeliverExtensions - { - public static Sms ToSms(this SmsDeliver sms, SmsStatus status) - { - return new Sms(status, sms.SenderNumber, sms.Timestamp, sms.Message, sms.MessageReferenceNumber, sms.TotalNumberOfParts, sms.PartNumber); - } - - public static SmsWithIndex ToSmsWithIndex(this SmsDeliver sms, int index, SmsStatus status) - { - return new SmsWithIndex(index, status, sms.SenderNumber, sms.Timestamp, sms.Message, sms.MessageReferenceNumber, sms.TotalNumberOfParts, sms.PartNumber); - } - } -} diff --git a/src/HeboTech.ATLib/Extensions/SmsExtensions.cs b/src/HeboTech.ATLib/Extensions/SmsExtensions.cs new file mode 100644 index 0000000..f06d930 --- /dev/null +++ b/src/HeboTech.ATLib/Extensions/SmsExtensions.cs @@ -0,0 +1,12 @@ +using HeboTech.ATLib.DTOs; + +namespace HeboTech.ATLib.Extensions +{ + internal static class SmsExtensions + { + public static SmsWithIndex ToSmsWithIndex(this Sms sms, int index) + { + return new SmsWithIndex(sms, index); + } + } +} diff --git a/src/HeboTech.ATLib/HeboTech.ATLib.csproj b/src/HeboTech.ATLib/HeboTech.ATLib.csproj index 4eb7f4e..6059117 100644 --- a/src/HeboTech.ATLib/HeboTech.ATLib.csproj +++ b/src/HeboTech.ATLib/HeboTech.ATLib.csproj @@ -4,10 +4,10 @@ <TargetFramework>netstandard2.1</TargetFramework> <Authors>HeboTech</Authors> <Product>HeboTech ATLib</Product> - <Version>7.1.0-beta2</Version> - <PackageVersion>7.1.0-beta2</PackageVersion> - <AssemblyVersion>7.1.0.0</AssemblyVersion> - <FileVersion>7.1.0.0</FileVersion> + <Version>8.0.0-alpha1</Version> + <PackageVersion>8.0.0-alpha1</PackageVersion> + <AssemblyVersion>8.0.0.0</AssemblyVersion> + <FileVersion>8.0.0.0</FileVersion> <PackageId>HeboTech.ATLib</PackageId> <Title>AT command library that makes it easy to communicate with modems.</Title> <Description>AT command library that makes it easy to communicate with modems.</Description> diff --git a/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs b/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs index b3afccd..1d88ea0 100644 --- a/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs +++ b/src/HeboTech.ATLib/Modems/Cinterion/MC55i.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using HeboTech.ATLib.PDU; @@ -26,48 +25,12 @@ public MC55i(IAtChannel channel) { } - public async Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override async Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request) { - if (phoneNumber is null) - throw new ArgumentNullException(nameof(phoneNumber)); - if (message is null) - throw new ArgumentNullException(nameof(message)); + if (request is null) + throw new ArgumentNullException(nameof(request)); - IEnumerable<string> pdus = SmsSubmitEncoder.Encode(new SmsSubmitRequest(phoneNumber, message) { IncludeEmptySmscLength = true }); - List<ModemResponse<SmsReference>> references = new List<ModemResponse<SmsReference>>(); - foreach (string pdu in pdus) - { - string cmd1 = $"AT+CMGS={(pdu.Length - 2) / 2}"; // Subtract 2 (one octet) for SMSC. - string cmd2 = pdu; - AtResponse response = await channel.SendSmsAsync(cmd1, cmd2, "+CMGS:", TimeSpan.FromSeconds(30)); - - if (response.Success) - { - string line = response.Intermediates.First(); - var match = Regex.Match(line, @"\+CMGS:\s(?<mr>\d+)"); - if (match.Success) - { - int mr = int.Parse(match.Groups["mr"].Value); - references.Add(ModemResponse.IsResultSuccess(new SmsReference(mr))); - } - } - else - { - if (AtErrorParsers.TryGetError(response.FinalResponse, out Error error)) - references.Add(ModemResponse.HasResultError<SmsReference>(error)); - } - } - return references; - } - - public async Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - if (phoneNumber is null) - throw new ArgumentNullException(nameof(phoneNumber)); - if (message is null) - throw new ArgumentNullException(nameof(message)); - - IEnumerable<string> pdus = SmsSubmitEncoder.Encode(new SmsSubmitRequest(phoneNumber, message, codingScheme) { IncludeEmptySmscLength = true }); + IEnumerable<string> pdus = SmsSubmitEncoder.Encode(request, true); List<ModemResponse<SmsReference>> references = new List<ModemResponse<SmsReference>>(); foreach (string pdu in pdus) { @@ -134,5 +97,25 @@ public async Task<ModemResponse<MC55iBatteryStatus>> MC55i_GetBatteryStatusAsync AtErrorParsers.TryGetError(response.FinalResponse, out Error error); return ModemResponse.HasResultError<MC55iBatteryStatus>(error); } + + /// <summary> + /// Sets how receiving a new SMS is indicated + /// </summary> + /// <param name="mode">mode</param> + /// <param name="mt">mt</param> + /// <param name="bm">bm</param> + /// <param name="ds">ds</param> + /// <param name="bfr">Not in use</param> + /// <returns>Command status</returns> + public override async Task<ModemResponse> SetNewSmsIndicationAsync(int mode, int mt, int bm, int ds, int bfr) + { + AtResponse response = await channel.SendCommand($"AT+CNMI={mode},{mt},{bm},{ds}"); + + if (response.Success) + return ModemResponse.IsSuccess(); + + AtErrorParsers.TryGetError(response.FinalResponse, out Error error); + return ModemResponse.HasError(error); + } } } diff --git a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs index aa4b286..209f053 100644 --- a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs +++ b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs @@ -26,7 +26,7 @@ public ModemBase(IAtChannel channel) channel.UnsolicitedEvent += Channel_UnsolicitedEvent; } - private void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) + protected virtual void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) { if (e.Line1 == "RING") IncomingCall?.Invoke(this, new IncomingCallEventArgs()); @@ -36,12 +36,28 @@ private void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) CallEnded?.Invoke(this, CallEndedEventArgs.CreateFromResponse(e.Line1)); else if (e.Line1.StartsWith("MISSED_CALL: ")) MissedCall?.Invoke(this, MissedCallEventArgs.CreateFromResponse(e.Line1)); + + else if (e.Line1.StartsWith("+CMT: ")) + SmsReceived?.Invoke(this, SmsReceivedEventArgs.CreateFromResponse(e.Line1, e.Line2)); else if (e.Line1.StartsWith("+CMTI: ")) - SmsReceived?.Invoke(this, SmsReceivedEventArgs.CreateFromResponse(e.Line1)); + SmsStorageReferenceReceived?.Invoke(this, SmsStorageReferenceReceivedEventArgs.CreateFromResponse(e.Line1)); + + else if (e.Line1.StartsWith("+CBM: ")) + BroadcastMessageReceived?.Invoke(this, BreadcastMessageReceivedEventArgs.CreateFromResponse(e.Line1, e.Line2)); + else if (e.Line1.StartsWith("+CBMI: ")) + BroadcastMessageStorageReferenceReceived?.Invoke(this, BreadcastMessageStorageReferenceReceivedEventArgs.CreateFromResponse(e.Line1)); + + else if (e.Line1.StartsWith("+CDS: ")) + SmsStatusReportReceived?.Invoke(this, SmsStatusReportEventArgs.CreateFromResponse(e.Line1, e.Line2)); + else if (e.Line1.StartsWith("+CDSI: ")) + SmsStatusReportStorageReferenceReceived?.Invoke(this, SmsStatusReportStorageReferenceEventArgs.CreateFromResponse(e.Line1)); + else if (e.Line1.StartsWith("+CUSD: ")) UssdResponseReceived?.Invoke(this, UssdResponseEventArgs.CreateFromResponse(e.Line1)); + else if (AtErrorParsers.TryGetError(e.Line1, out Error error)) ErrorReceived?.Invoke(this, new ErrorEventArgs(error.ToString())); + else GenericEvent?.Invoke(this, new GenericEventArgs(e.Line1)); } @@ -49,6 +65,15 @@ private void Channel_UnsolicitedEvent(object sender, UnsolicitedEventArgs e) public event EventHandler<ErrorEventArgs> ErrorReceived; public event EventHandler<GenericEventArgs> GenericEvent; + public event EventHandler<SmsReceivedEventArgs> SmsReceived; + public event EventHandler<SmsStorageReferenceReceivedEventArgs> SmsStorageReferenceReceived; + + public event EventHandler<BreadcastMessageReceivedEventArgs> BroadcastMessageReceived; + public event EventHandler<BreadcastMessageStorageReferenceReceivedEventArgs> BroadcastMessageStorageReferenceReceived; + + public event EventHandler<SmsStatusReportEventArgs> SmsStatusReportReceived; + public event EventHandler<SmsStatusReportStorageReferenceEventArgs> SmsStatusReportStorageReferenceReceived; + #region _V_25TER public event EventHandler<IncomingCallEventArgs> IncomingCall; public event EventHandler<MissedCallEventArgs> MissedCall; @@ -186,8 +211,6 @@ public virtual async Task<ModemResponse> SetCharacterSetAsync(CharacterSet chara #endregion #region _3GPP_TS_27_005 - public event EventHandler<SmsReceivedEventArgs> SmsReceived; - public virtual async Task<ModemResponse<string>> GetSmsMessageFormatAsync() { AtResponse response = await channel.SendSingleLineCommandAsync($"AT+CMGF?", "+CMGF:"); @@ -218,19 +241,19 @@ public virtual async Task<ModemResponse> SetSmsMessageFormatAsync(SmsTextFormat return ModemResponse.HasError(error); } - public virtual async Task<ModemResponse> SetNewSmsIndicationAsync(int mode, int mt, int bm, int ds, int bfr) + public virtual async Task<ModemResponse> SetSelectMessageService(int service) { - if (mode < 0 || mode > 2) - throw new ArgumentOutOfRangeException(nameof(mode)); - if (mt < 0 || mt > 3) - throw new ArgumentOutOfRangeException(nameof(mt)); - if (!(bm == 0 || bm == 2)) - throw new ArgumentOutOfRangeException(nameof(bm)); - if (ds < 0 || ds > 2) - throw new ArgumentOutOfRangeException(nameof(ds)); - if (bfr < 0 || bfr > 1) - throw new ArgumentOutOfRangeException(nameof(bfr)); + AtResponse response = await channel.SendCommand($"AT+CSMS={service}"); + + if (response.Success) + return ModemResponse.IsSuccess(); + AtErrorParsers.TryGetError(response.FinalResponse, out Error error); + return ModemResponse.HasError(error); + } + + public virtual async Task<ModemResponse> SetNewSmsIndicationAsync(int mode, int mt, int bm, int ds, int bfr) + { AtResponse response = await channel.SendCommand($"AT+CNMI={mode},{mt},{bm},{ds},{bfr}"); if (response.Success) @@ -240,20 +263,17 @@ public virtual async Task<ModemResponse> SetNewSmsIndicationAsync(int mode, int return ModemResponse.HasError(error); } - protected virtual Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, bool includeEmptySmscLength) + public virtual Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request) { - CharacterSet characterSet = Gsm7.IsGsm7Compatible(message.ToCharArray()) ? CharacterSet.Gsm7 : CharacterSet.UCS2; - return SendSmsAsync(phoneNumber, message, characterSet, includeEmptySmscLength); + return SendSmsAsync(request, true); } - protected virtual async Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme, bool includeEmptySmscLength) + protected async Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request, bool includeEmptySmscLength) { - if (phoneNumber is null) - throw new ArgumentNullException(nameof(phoneNumber)); - if (message is null) - throw new ArgumentNullException(nameof(message)); + if (request is null) + throw new ArgumentNullException(nameof(request)); - IEnumerable<string> pdus = SmsSubmitEncoder.Encode(new SmsSubmitRequest(phoneNumber, message, codingScheme) { IncludeEmptySmscLength = includeEmptySmscLength }); + IEnumerable<string> pdus = SmsSubmitEncoder.Encode(request, includeEmptySmscLength); List<ModemResponse<SmsReference>> references = new List<ModemResponse<SmsReference>>(); foreach (string pdu in pdus) { @@ -375,13 +395,12 @@ public virtual async Task<ModemResponse<Sms>> ReadSmsAsync(int index) if (length > 0) { string line2 = pduResponse.Intermediates[1]; - var line2Match = Regex.Match(line2, @"(?<status>[0-9A-Z]*)"); + var line2Match = Regex.Match(line2, @"(?<pdu>[0-9A-Z]*)"); if (line2Match.Success) { - string pdu = line2Match.Groups["status"].Value; - SmsDeliver pduMessage = SmsDeliverDecoder.Decode(pdu.ToByteArray()); - - return ModemResponse.IsResultSuccess(pduMessage.ToSms(status)); + string pduString = line2Match.Groups["pdu"].Value; + Sms sms = SmsDecoder.Decode(pduString.ToByteArray(), status); + return ModemResponse.IsResultSuccess(sms); } } } @@ -416,8 +435,8 @@ public virtual async Task<ModemResponse<List<SmsWithIndex>>> ListSmssAsync(SmsSt // Sent when AT+CSDH=1 is set int length = int.Parse(match.Groups["length"].Value); - SmsDeliver sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); - smss.Add(sms.ToSmsWithIndex(index, status)); + Sms sms = SmsDecoder.Decode(messageLine.ToByteArray(), status); + smss.Add(sms.ToSmsWithIndex(index)); } } } diff --git a/src/HeboTech.ATLib/Modems/IModem.cs b/src/HeboTech.ATLib/Modems/IModem.cs index 49cb28a..20695d1 100644 --- a/src/HeboTech.ATLib/Modems/IModem.cs +++ b/src/HeboTech.ATLib/Modems/IModem.cs @@ -39,13 +39,23 @@ public interface IModem : IDisposable /// <summary> /// Indicates that an SMS is received /// </summary> - event EventHandler<SmsReceivedEventArgs> SmsReceived; + event EventHandler<SmsStorageReferenceReceivedEventArgs> SmsStorageReferenceReceived; /// <summary> /// Indicates that a USSD response is received /// </summary> event EventHandler<UssdResponseEventArgs> UssdResponseReceived; + + event EventHandler<SmsReceivedEventArgs> SmsReceived; + + event EventHandler<BreadcastMessageReceivedEventArgs> BroadcastMessageReceived; + event EventHandler<BreadcastMessageStorageReferenceReceivedEventArgs> BroadcastMessageStorageReferenceReceived; + + event EventHandler<SmsStatusReportEventArgs> SmsStatusReportReceived; + event EventHandler<SmsStatusReportStorageReferenceEventArgs> SmsStatusReportStorageReferenceReceived; + + /// <summary> /// Indicates that an event with no specific event handler is received /// </summary> @@ -188,22 +198,12 @@ public interface IModem : IDisposable /// <returns>Command status</returns> Task<ModemResponse> ResetToFactoryDefaultsAsync(); - /// <summary> - /// Sends an SMS in PDU format. This will automatically select the Data Coding Scheme that will result in the fewest messages being sent in case of a concatenated SMS based on the content of the message. - /// </summary> - /// <param name="phoneNumber">The number to send to</param> - /// <param name="message">The message body</param> - /// <returns>Command status with SMS reference</returns> - Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message); - /// <summary> /// Sends an SMS in PDU format /// </summary> - /// <param name="phoneNumber">The number to send to</param> - /// <param name="message">The message body</param> - /// <param name="codingScheme">Encoding to use</param> + /// <param name="request">The SMS request<param> /// <returns>Command status with SMS reference</returns> - Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme = CharacterSet.UCS2); + Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request); /// <summary> /// Sends an USSD code. Results in an UssdResponseReceived event @@ -234,6 +234,13 @@ public interface IModem : IDisposable /// <returns>Command status</returns> Task<ModemResponse> SetErrorFormatAsync(int errorFormat); + /// <summary> + /// Select Message Service + /// </summary> + /// <param name="service">Typical: 0, 1</param> + /// <returns>Command status</returns> + Task<ModemResponse> SetSelectMessageService(int service); + /// <summary> /// Sets how receiving a new SMS is indicated /// </summary> diff --git a/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs b/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs index af2d81e..6a85e7f 100644 --- a/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs +++ b/src/HeboTech.ATLib/Modems/Qualcomm/MDM9225.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System.Collections.Generic; @@ -14,14 +13,9 @@ public MDM9225(IAtChannel channel) { } - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } } } diff --git a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs index 62ba9cd..04c4328 100644 --- a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs +++ b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Extensions; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; @@ -42,14 +41,9 @@ public virtual async Task<RemainingPinPukAttempts> GetRemainingPinPukAttemptsAsy #region _3GPP_TS_27_005 - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } public override async Task<ModemResponse<List<SmsWithIndex>>> ListSmssAsync(SmsStatus smsStatus) @@ -77,8 +71,8 @@ public override async Task<ModemResponse<List<SmsWithIndex>>> ListSmssAsync(SmsS // Sent when AT+CSDH=1 is set int length = int.Parse(match.Groups["length"].Value); - SmsDeliver sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); - smss.Add(new SmsWithIndex(index, status, sms.SenderNumber, sms.Timestamp, sms.Message)); + Sms sms = SmsDeliverDecoder.Decode(messageLine.ToByteArray()); + smss.Add(sms.ToSmsWithIndex(index)); } } } diff --git a/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs b/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs index dd79989..803109a 100644 --- a/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs +++ b/src/HeboTech.ATLib/Modems/TP-LINK/MA260.cs @@ -1,5 +1,4 @@ -using HeboTech.ATLib.CodingSchemes; -using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.DTOs; using HeboTech.ATLib.Modems.Generic; using HeboTech.ATLib.Parsers; using System.Collections.Generic; @@ -20,14 +19,9 @@ public MA260(IAtChannel channel) { } - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } } } diff --git a/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs b/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs index f6b47f6..54d2708 100644 --- a/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs +++ b/src/HeboTech.ATLib/Modems/Telit/ME910C1.cs @@ -31,14 +31,9 @@ public override async Task<bool> SetRequiredSettingsAfterPinAsync() return currentCharacterSet.Success && smsMessageFormat.Success; } - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message) + public override Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(SmsSubmitRequest request) { - return base.SendSmsAsync(phoneNumber, message, false); - } - - public Task<IEnumerable<ModemResponse<SmsReference>>> SendSmsAsync(PhoneNumber phoneNumber, string message, CharacterSet codingScheme) - { - return base.SendSmsAsync(phoneNumber, message, codingScheme, false); + return SendSmsAsync(request, false); } } } diff --git a/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs b/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs index be1f389..782ff34 100644 --- a/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs +++ b/src/HeboTech.ATLib/PDU/MessageTypeIndicator.cs @@ -1,13 +1,38 @@ namespace HeboTech.ATLib.PDU { - internal enum MessageTypeIndicator : byte + /// <summary> + /// Inbound (SMSC/SC -> MS) + /// SMSC: Short Message Service Center + /// SC: SMS Center + /// MS: Mobile Station + /// </summary> + public enum MessageTypeIndicatorInbound : byte { - SMS_DELIVER_REPORT = 0x00, + // SC -> MS SMS_DELIVER = 0x00, - SMS_SUBMIT = 0x01, + // SC -> MS SMS_SUBMIT_REPORT = 0x01, - SMS_COMMAND = 0x10, - SMS_STATUS_REPORT = 0x10, - Reserved = 0x11 + // SC -> MS + SMS_STATUS_REPORT = 0x02, + + Reserved = 0x03 + } + + /// <summary> + /// Outbound (MS -> SMSC/SC) + /// SMSC: Short Message Service Center + /// SC: SMS Center + /// MS: Mobile Station + /// </summary> + internal enum MessageTypeIndicatorOutbound : byte + { + // MS -> SC + SMS_DELIVER_REPORT = 0x00, + // MS -> SC + SMS_SUBMIT = 0x01, + // MS -> SC + SMS_COMMAND = 0x02, + + Reserved = 0x03 } } diff --git a/src/HeboTech.ATLib/PDU/PhoneNumberDecoder.cs b/src/HeboTech.ATLib/PDU/PhoneNumberDecoder.cs new file mode 100644 index 0000000..8c32fd6 --- /dev/null +++ b/src/HeboTech.ATLib/PDU/PhoneNumberDecoder.cs @@ -0,0 +1,48 @@ +using HeboTech.ATLib.CodingSchemes; +using HeboTech.ATLib.DTOs; +using HeboTech.ATLib.Extensions; +using System; +using System.Linq; + +namespace HeboTech.ATLib.PDU +{ + internal class PhoneNumberDecoder + { + public static PhoneNumberDTO DecodePhoneNumber(ReadOnlySpan<byte> data) + { + byte ext_ton_npi = data[0]; + TypeOfNumber ton = (TypeOfNumber)((ext_ton_npi & 0b0111_0000) >> 4); + + string number = string.Empty; + switch (ton) + { + case TypeOfNumber.Unknown: + break; + case TypeOfNumber.International: + number = "+"; + break; + case TypeOfNumber.National: + break; + case TypeOfNumber.NetworkSpecific: + break; + case TypeOfNumber.Subscriber: + break; + case TypeOfNumber.AlphaNumeric: + var decoded = Gsm7.Decode(data[1..].ToArray()); + //var decoded = Gsm7.DecodeFromBytes(data[1..].ToArray()); + return new PhoneNumberDTO(decoded); + case TypeOfNumber.Abbreviated: + break; + case TypeOfNumber.ReservedForExtension: + break; + default: + throw new NotImplementedException($"TON {ton} is not supported"); + } + + number += string.Join("", data[1..].ToArray().Select(x => x.SwapNibbles().ToString("X2"))); + if (number[^1] == 'F') + number = number[..^1]; + return new PhoneNumberDTO(number); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/ReceivedMessageTypeParser.cs b/src/HeboTech.ATLib/PDU/ReceivedMessageTypeParser.cs new file mode 100644 index 0000000..0b06090 --- /dev/null +++ b/src/HeboTech.ATLib/PDU/ReceivedMessageTypeParser.cs @@ -0,0 +1,18 @@ +using System; + +namespace HeboTech.ATLib.PDU +{ + internal static class ReceivedMessageTypeParser + { + public static MessageTypeIndicatorInbound Parse(ReadOnlySpan<byte> bytes) + { + byte smsc_length = bytes[0]; // Get length of SMSC + byte headerByte = bytes[1 + smsc_length]; // Skip over SMSC length byte and SMSC to get to header + + byte mti = (byte)(headerByte & 0b0000_0011); + if (Enum.IsDefined(typeof(MessageTypeIndicatorInbound), mti)) + return (MessageTypeIndicatorInbound)mti; + throw new ArgumentOutOfRangeException(nameof(mti)); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/SmsDecoder.cs b/src/HeboTech.ATLib/PDU/SmsDecoder.cs new file mode 100644 index 0000000..d03b557 --- /dev/null +++ b/src/HeboTech.ATLib/PDU/SmsDecoder.cs @@ -0,0 +1,37 @@ +using HeboTech.ATLib.DTOs; +using System; + +namespace HeboTech.ATLib.PDU +{ + internal class SmsDecoder + { + public static Sms Decode(ReadOnlySpan<byte> bytes, SmsStatus status, int timestampYearOffset = 2000) + { + int offset = 0; + + // SMSC information + byte smsc_length = bytes[offset++]; + // Skip SMSC information - handle it in each individual parser. + offset += smsc_length; + // Header + byte headerByte = bytes[offset++]; + + MessageTypeIndicatorInbound mti = (MessageTypeIndicatorInbound)(headerByte & 0b0000_0011); + switch (mti) + { + case MessageTypeIndicatorInbound.SMS_DELIVER: + return SmsDeliverDecoder.Decode(bytes, timestampYearOffset); + case MessageTypeIndicatorInbound.SMS_SUBMIT_REPORT: + break; + case MessageTypeIndicatorInbound.SMS_STATUS_REPORT: + return SmsStatusReportDecoder.Decode(bytes, timestampYearOffset); + case MessageTypeIndicatorInbound.Reserved: + break; + default: + break; + } + + throw new NotImplementedException(); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs b/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs index 56c102f..bf3615a 100644 --- a/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs +++ b/src/HeboTech.ATLib/PDU/SmsDeliverDecoder.cs @@ -1,9 +1,7 @@ using HeboTech.ATLib.CodingSchemes; using HeboTech.ATLib.DTOs; -using HeboTech.ATLib.Extensions; using System; using System.Linq; -using System.Threading; namespace HeboTech.ATLib.PDU { @@ -15,7 +13,7 @@ private SmsDeliverHeader() { } - public SmsDeliverHeader(MessageTypeIndicator mti, bool mms, bool lp, bool sri, bool udhi, bool rp) + public SmsDeliverHeader(MessageTypeIndicatorInbound mti, bool mms, bool lp, bool sri, bool udhi, bool rp) { MTI = mti; MMS = mms; @@ -25,7 +23,7 @@ public SmsDeliverHeader(MessageTypeIndicator mti, bool mms, bool lp, bool sri, b RP = rp; } - public MessageTypeIndicator MTI { get; private set; } + public MessageTypeIndicatorInbound MTI { get; private set; } public bool MMS { get; private set; } public bool LP { get; private set; } public bool SRI { get; private set; } @@ -36,14 +34,14 @@ public static SmsDeliverHeader Parse(byte header) { SmsDeliverHeader parsedHeader = new SmsDeliverHeader(); - parsedHeader.MTI = (MessageTypeIndicator)(header & 0b0000_0011); - if (parsedHeader.MTI != (byte)MessageTypeIndicator.SMS_DELIVER) + parsedHeader.MTI = (MessageTypeIndicatorInbound)(header & 0b0000_0011); + if (parsedHeader.MTI != (byte)MessageTypeIndicatorInbound.SMS_DELIVER) throw new ArgumentException("Invalid SMS-DELIVER data"); - parsedHeader.MMS = (header & 0b0000_0100) != 0; - parsedHeader.SRI = (header & 0b0000_1000) != 0; - parsedHeader.UDHI = (header & 0b0100_0000) != 0; - parsedHeader.RP = (header & 0b1000_0000) != 0; + parsedHeader.MMS = (header & (1 << 2)) == 0; + parsedHeader.SRI = (header & (1 << 3)) != 0; + parsedHeader.UDHI = (header & (1 << 6)) != 0; + parsedHeader.RP = (header & (1 << 7)) != 0; return parsedHeader; } @@ -65,7 +63,7 @@ public static SmsDeliver Decode(ReadOnlySpan<byte> bytes, int timestampYearOffse PhoneNumberDTO serviceCenterNumber = null; if (smsc_length > 0) { - serviceCenterNumber = DecodePhoneNumber(bytes[offset..(offset += smsc_length)]); + serviceCenterNumber = PhoneNumberDecoder.DecodePhoneNumber(bytes[offset..(offset += smsc_length)]); } // SMS-DELIVER start @@ -78,7 +76,7 @@ public static SmsDeliver Decode(ReadOnlySpan<byte> bytes, int timestampYearOffse PhoneNumberDTO oa = null; if (tp_oa_bytes_length > 0) { - oa = DecodePhoneNumber(bytes[offset..(offset += tp_oa_bytes_length)]); + oa = PhoneNumberDecoder.DecodePhoneNumber(bytes[offset..(offset += tp_oa_bytes_length)]); } byte tp_pid = bytes[offset++]; @@ -125,10 +123,8 @@ public static SmsDeliver Decode(ReadOnlySpan<byte> bytes, int timestampYearOffse case CharacterSet.Gsm7: int fillBits = 0; if (header.UDHI) - fillBits = 7 - (((1 + udh.Length) * 8) % 7); - - var unpacked = Gsm7.Unpack(payload.ToArray(), fillBits); - message = Gsm7.DecodeFromBytes(unpacked); + fillBits = (1 + udh.Length) % 7 == 0 ? 0 : 7 - (((1 + udh.Length) * 8) % 7); // Add 1 to the udh length because the length byte isn't included in the udh length itself. If fillbits == 7 -> use 0 fillbits. + message = Gsm7.Decode(payload.ToArray(), fillBits); break; case CharacterSet.UCS2: message = UCS2.Decode(payload.ToArray()); @@ -143,42 +139,5 @@ public static SmsDeliver Decode(ReadOnlySpan<byte> bytes, int timestampYearOffse else return new SmsDeliver(serviceCenterNumber, oa, message, scts); } - - private static PhoneNumberDTO DecodePhoneNumber(ReadOnlySpan<byte> data) - { - byte ext_ton_npi = data[0]; - TypeOfNumber ton = (TypeOfNumber)((ext_ton_npi & 0b0111_0000) >> 4); - - string number = string.Empty; - switch (ton) - { - case TypeOfNumber.Unknown: - break; - case TypeOfNumber.International: - number = "+"; - break; - case TypeOfNumber.National: - break; - case TypeOfNumber.NetworkSpecific: - break; - case TypeOfNumber.Subscriber: - break; - case TypeOfNumber.AlphaNumeric: - var unpacked = Gsm7.Unpack(data[1..].ToArray()); - var decoded = Gsm7.DecodeFromBytes(unpacked); - return new PhoneNumberDTO(decoded); - case TypeOfNumber.Abbreviated: - break; - case TypeOfNumber.ReservedForExtension: - break; - default: - throw new NotImplementedException($"TON {ton} is not supported"); - } - - number += string.Join("", data[1..].ToArray().Select(x => x.SwapNibbles().ToString("X2"))); - if (number[^1] == 'F') - number = number[..^1]; - return new PhoneNumberDTO(number); - } } } diff --git a/src/HeboTech.ATLib/PDU/SmsStatusReportDecoder.cs b/src/HeboTech.ATLib/PDU/SmsStatusReportDecoder.cs new file mode 100644 index 0000000..11cf4b5 --- /dev/null +++ b/src/HeboTech.ATLib/PDU/SmsStatusReportDecoder.cs @@ -0,0 +1,76 @@ +using HeboTech.ATLib.DTOs; +using System; + +namespace HeboTech.ATLib.PDU +{ + public static class SmsStatusReportDecoder + { + private class SmsStatusReportHeader + { + private SmsStatusReportHeader() + { + } + + public SmsStatusReportHeader(MessageTypeIndicatorInbound mti, bool mms, bool lp, bool sri, bool udhi) + { + MTI = mti; + MMS = mms; + LP = lp; + SRQ = sri; + UDHI = udhi; + } + + public MessageTypeIndicatorInbound MTI { get; private set; } + public bool MMS { get; private set; } + public bool LP { get; private set; } + /// <summary> + /// If set - this is a result of an SMS-COMMAND; otherwise it is a result of an SMS-SUBMIT + /// 9 2 2 3 + /// </summary> + public bool SRQ { get; private set; } + public bool UDHI { get; private set; } + + public static SmsStatusReportHeader Parse(byte header) + { + SmsStatusReportHeader parsedHeader = new SmsStatusReportHeader(); + + parsedHeader.MTI = (MessageTypeIndicatorInbound)(header & (3 << 0)); + if (parsedHeader.MTI != MessageTypeIndicatorInbound.SMS_STATUS_REPORT) + throw new ArgumentException("Invalid SMS_STATUS_REPORT data"); + + parsedHeader.MMS = (header & (1 << 2)) != 0; + parsedHeader.LP = (header & (1 << 3)) != 0; + parsedHeader.SRQ = (header & (1 << 5)) != 0; + parsedHeader.UDHI = (header & (1 << 6)) != 0; + + return parsedHeader; + } + } + + public static SmsStatusReport Decode(ReadOnlySpan<byte> bytes, int timestampYearOffset = 2000) + { + int offset = 0; + + // SMSC information + byte smsc_length = bytes[offset++]; + PhoneNumberDTO serviceCenterNumber = null; + if (smsc_length > 0) + { + serviceCenterNumber = PhoneNumberDecoder.DecodePhoneNumber(bytes[offset..(offset += smsc_length)]); + } + + // SMS-STATUS-REPORT start + byte headerByte = bytes[offset++]; + SmsStatusReportHeader header = SmsStatusReportHeader.Parse(headerByte); + + byte tp_mr = bytes[offset++]; + byte tp_ra_length = (byte)((bytes[offset++] / 2) + 1); + ReadOnlySpan<byte> tp_ra = bytes[offset..(offset += tp_ra_length)]; + ReadOnlySpan<byte> tp_scts = bytes[offset..(offset += 7)]; + ReadOnlySpan<byte> tp_dt = bytes[offset..(offset += 7)]; + byte tp_st = bytes[offset++]; + + return new SmsStatusReport(tp_mr, PhoneNumberDecoder.DecodePhoneNumber(tp_ra), serviceCenterNumber, TpduTime.DecodeTimestamp(tp_scts, timestampYearOffset), TpduTime.DecodeTimestamp(tp_dt, timestampYearOffset), (SmsDeliveryStatus) tp_st); + } + } +} diff --git a/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs b/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs index 1110d9d..615831f 100644 --- a/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs +++ b/src/HeboTech.ATLib/PDU/SmsSubmitEncoder.cs @@ -29,17 +29,17 @@ internal class SmsSubmitEncoder // Phone number in semi octets. 12345678 is represented as 21436587 protected string daNumber = string.Empty; // TP-PID Protocol identifier - protected byte pi; + protected byte pid; // TP-DCS Data Coding Scheme. '00'-7bit default alphabet. '04'-8bit protected CharacterSet dcs; - // TP-Validity-Period. 'AA'-4 days - protected List<byte> vp = new List<byte>(); + // TP-Validity-Period + protected ValidityPeriod validityPeriod = null; // Message protected Message partitionedMessage; protected SmsSubmitEncoder() { - header = (byte)MessageTypeIndicator.SMS_SUBMIT; + header = (byte)MessageTypeIndicatorOutbound.SMS_SUBMIT; } protected static SmsSubmitEncoder Initialize() @@ -52,13 +52,14 @@ protected static SmsSubmitEncoder Initialize() /// </summary> /// <param name="smsSubmit">Data object</param> /// <returns>PDUs</returns> - public static IEnumerable<string> Encode(SmsSubmitRequest smsSubmit) + public static IEnumerable<string> Encode(SmsSubmitRequest smsSubmit, bool includeEmptySmscLength) { // Build TPDU var messageParts = SmsSubmitEncoder .Initialize() .DestinationAddress(smsSubmit.PhoneNumber) .ValidityPeriod(smsSubmit.ValidityPeriod) + .EnableStatusReportRequest(smsSubmit.EnableStatusReportRequest) .Message(smsSubmit.Message, smsSubmit.CodingScheme, smsSubmit.MessageReferenceNumber) .Build(); @@ -67,7 +68,7 @@ public static IEnumerable<string> Encode(SmsSubmitRequest smsSubmit) StringBuilder sb = new StringBuilder(); // Length of SMSC information - if (smsSubmit.IncludeEmptySmscLength) + if (includeEmptySmscLength) sb.Append("00"); sb.Append(messagePart); @@ -80,7 +81,7 @@ public static IEnumerable<string> Encode(SmsSubmitRequest smsSubmit) protected SmsSubmitEncoder EnableUserDataHeaderIndicator() { - header |= 0b0100_0000; + header |= (1 << 6); return this; } @@ -90,13 +91,13 @@ protected SmsSubmitEncoder EnableUserDataHeaderIndicator() /// <returns></returns> protected SmsSubmitEncoder EnableReplyPath() { - header |= 0b1000_0000; + header |= (1 << 7); return this; } protected static byte GetAddressType(PhoneNumber phoneNumber) { - return (byte)(0b1000_0000 + ((byte)phoneNumber.GetTypeOfNumber() << 4) + (byte)phoneNumber.GetNumberPlanIdentification()); + return (byte)((1 << 7) + ((byte)phoneNumber.GetTypeOfNumber() << 4) + (byte)phoneNumber.GetNumberPlanIdentification()); } protected static string SwapPhoneNumberDigits(string data) @@ -120,7 +121,7 @@ protected static string SwapPhoneNumberDigits(string data) /// <returns></returns> protected SmsSubmitEncoder RejectDuplicates() { - header |= 0b0000_0100; + header |= (1 << 2); return this; } @@ -131,20 +132,22 @@ protected SmsSubmitEncoder RejectDuplicates() /// <returns></returns> protected SmsSubmitEncoder ValidityPeriod(ValidityPeriod validityPeriod) { + if (validityPeriod == null) + return this; + // Set format - byte mask = 0b0001_1000; - header = (byte)((header & ~mask) | ((byte)validityPeriod.Format & mask)); + header |= (byte)((byte)validityPeriod.Format << 3); // Set value - vp.Clear(); - vp.AddRange(validityPeriod.Value); + this.validityPeriod = validityPeriod; return this; } - protected SmsSubmitEncoder EnableStatusReportRequest() + protected SmsSubmitEncoder EnableStatusReportRequest(bool enable) { - header |= 0b0010_0000; + if (enable) + header |= (1 << 5); return this; } @@ -182,7 +185,7 @@ protected SmsSubmitEncoder DestinationAddress(PhoneNumber phoneNumber) /// <returns></returns> protected SmsSubmitEncoder ProtocolIdentifier(byte value) { - pi = value; + pid = value; return this; } @@ -212,27 +215,26 @@ protected IEnumerable<string> Build() sb.Append(daLength.ToString("X2")); sb.Append(daType.ToString("X2")); sb.Append(daNumber); - sb.Append(pi.ToString("X2")); + sb.Append(pid.ToString("X2")); sb.Append(((byte)dcs).ToString("X2")); - if (vp.Count > 0) - sb.Append(String.Join("", vp.Select(x => x.ToString("X2")))); + if (validityPeriod != null) + sb.Append(String.Join("", validityPeriod.Value.Select(x => x.ToString("X2")))); switch (dcs) { case CharacterSet.Gsm7: int fillBits = 0; if (UserDataHeaderIndicatorIsSet) - fillBits = 7 - ((part.Header.Length * 8) % 7); + fillBits = (part.Header.Length * 8) % 7 == 0 ? 0 : 7 - ((part.Header.Length * 8) % 7); - var gsm7 = Gsm7.EncodeToBytes(part.Data); - var encoded = Gsm7.Pack(gsm7, fillBits); + var gsm7 = Gsm7.Encode(part.Data, fillBits); - int udlBits = part.Header.Length * 8 + gsm7.Length * 7 + fillBits; + int udlBits = part.Header.Length * 8 + part.Data.Length * 7 + fillBits; int udlSeptets = udlBits / 7; sb.Append((udlSeptets).ToString("X2")); sb.Append(string.Join("", part.Header.Select(x => x.ToString("X2")))); - sb.Append(string.Join("", encoded.Select(x => x.ToString("X2")))); + sb.Append(string.Join("", gsm7.Select(x => x.ToString("X2")))); break; case CharacterSet.UCS2: var ucs2Bytes = UCS2.EncodeToBytes(part.Data.ToArray()); @@ -300,7 +302,7 @@ protected static Message CreateMessageParts(string message, CharacterSet dcs, by // Each part of the total message message.Skip(i * maxMessagePartSize).Take(maxMessagePartSize).ToArray()); } - + return new Message(messageReferenceNumber, (byte)numberOfParts, parts); } } diff --git a/src/HeboTech.ATLib/Parsers/AtChannel.cs b/src/HeboTech.ATLib/Parsers/AtChannel.cs index a1df47d..0e64219 100644 --- a/src/HeboTech.ATLib/Parsers/AtChannel.cs +++ b/src/HeboTech.ATLib/Parsers/AtChannel.cs @@ -76,12 +76,14 @@ public void EnableDebug(Action<string> debugAction) { this.debugAction = debugAction ?? throw new ArgumentNullException(nameof(debugAction)); debugEnabled = true; + debugAction($"##### DEBUG ENABLED #####"); } public void DisableDebug() { debugEnabled = false; debugAction = default; + debugAction($"##### DEBUG DISABLED #####"); } /// <summary> @@ -181,7 +183,7 @@ private async Task ReaderLoopAsync(CancellationToken cancellationToken = default { line1 = await atReader.ReadAsync(cancellationToken); if (debugEnabled) - debugAction($"In: {line1}"); + debugAction($"In (line1): {line1}"); } catch (OperationCanceledException) { @@ -198,7 +200,7 @@ private async Task ReaderLoopAsync(CancellationToken cancellationToken = default { line2 = await atReader.ReadAsync(cancellationToken); if (debugEnabled) - debugAction($"In: {line2}"); + debugAction($"In (line2): {line2}"); } catch (OperationCanceledException) {