diff --git a/src/HeboTech.ATLib.TestConsole/HeboTech.ATLib.TestConsole.csproj b/src/HeboTech.ATLib.TestConsole/HeboTech.ATLib.TestConsole.csproj index 04aa4d7..0e37a85 100644 --- a/src/HeboTech.ATLib.TestConsole/HeboTech.ATLib.TestConsole.csproj +++ b/src/HeboTech.ATLib.TestConsole/HeboTech.ATLib.TestConsole.csproj @@ -1,16 +1,20 @@ - - Exe - net6.0 - + + Exe + net6.0;net48 + - - - + + 9.0 + - - - + + + + + + + diff --git a/src/HeboTech.ATLib.TestConsole/Program.cs b/src/HeboTech.ATLib.TestConsole/Program.cs index 05c7960..72f1ca8 100644 --- a/src/HeboTech.ATLib.TestConsole/Program.cs +++ b/src/HeboTech.ATLib.TestConsole/Program.cs @@ -1,7 +1,10 @@ using System; using System.IO; using System.IO.Ports; +using System.Linq; using System.Net.Sockets; +using System.Reflection; +using System.Runtime.Versioning; using System.Threading.Tasks; namespace HeboTech.ATLib.TestConsole @@ -10,6 +13,14 @@ class Program { static async Task Main(string[] args) { + // Because of multi targeting, print out current framework target for information + var targetFrameworkAttribute = Assembly.GetExecutingAssembly() + .GetCustomAttributes(typeof(TargetFrameworkAttribute), false) + .SingleOrDefault() as TargetFrameworkAttribute; + Console.WriteLine($"Current target: {targetFrameworkAttribute.FrameworkName}"); + + + Console.OutputEncoding = System.Text.Encoding.UTF8; string pin = args[0]; diff --git a/src/HeboTech.ATLib.Tests/HeboTech.ATLib.Tests.csproj b/src/HeboTech.ATLib.Tests/HeboTech.ATLib.Tests.csproj index 7692637..f41927f 100644 --- a/src/HeboTech.ATLib.Tests/HeboTech.ATLib.Tests.csproj +++ b/src/HeboTech.ATLib.Tests/HeboTech.ATLib.Tests.csproj @@ -1,23 +1,26 @@ - + - - net6.0 + + net6.0;net48 + false + - false - + + 9.0 + - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + diff --git a/src/HeboTech.ATLib.Tests/PDU/PduTests.cs b/src/HeboTech.ATLib.Tests/PDU/PduTests.cs index 4ab5c8c..14d2691 100644 --- a/src/HeboTech.ATLib.Tests/PDU/PduTests.cs +++ b/src/HeboTech.ATLib.Tests/PDU/PduTests.cs @@ -1,6 +1,7 @@ using HeboTech.ATLib.CodingSchemes; using HeboTech.ATLib.DTOs; using HeboTech.ATLib.PDU; +using System; using Xunit; namespace HeboTech.ATLib.Tests.PDU @@ -23,7 +24,11 @@ public void Encode_SmsSubmit_test(string phoneNumber, string encodedMessage, byt [InlineData("07911326040000F0040B911346610089F60000208062917314800CC8F71D14969741F977FD07", "31624000000", "31641600986", "02-08-26-19-37-41-+02", "How are you?")] public void Decode_SmsDeliver_tests(string data, string serviceCenterNumber, string senderNumber, string timestamp, string message) { +#if NETFRAMEWORK + SmsDeliver pduMessage = Pdu.DecodeSmsDeliver(data.AsSpan()); +#else SmsDeliver pduMessage = Pdu.DecodeSmsDeliver(data); +#endif Assert.NotNull(pduMessage); Assert.Equal(TypeOfNumber.International, pduMessage.ServiceCenterNumber.Ton); @@ -39,7 +44,11 @@ public void Decode_SmsDeliver_tests(string data, string serviceCenterNumber, str [InlineData("0011000802231537180000AA0D5062154403D1CB68D03DED06", "", "32517381", "PDU 4 teh win")] public void Decode_SmsSubmit_tests(string data, string serviceCenterNumber, string senderNumber, string message) { +#if NETFRAMEWORK + SmsSubmit pduMessage = Pdu.DecodeSmsSubmit(data.AsSpan()); +#else SmsSubmit pduMessage = Pdu.DecodeSmsSubmit(data); +#endif Assert.NotNull(pduMessage); Assert.Equal(serviceCenterNumber, pduMessage.ServiceCenterNumber?.Number ?? ""); diff --git a/src/HeboTech.ATLib.Tests/Parsers/AtReaderTests.cs b/src/HeboTech.ATLib.Tests/Parsers/AtReaderTests.cs index 229735f..322541a 100644 --- a/src/HeboTech.ATLib.Tests/Parsers/AtReaderTests.cs +++ b/src/HeboTech.ATLib.Tests/Parsers/AtReaderTests.cs @@ -16,7 +16,11 @@ public async Task Lines_are_readAsync() string input = "Line1\r\nLine2\r\nLine3\r\n"; byte[] buffer = Encoding.UTF8.GetBytes(input); +#if NETFRAMEWORK + stream.Write(buffer, 0, buffer.Length); +#else stream.Write(buffer); +#endif stream.Position = 0; dut.Open(); @@ -40,7 +44,11 @@ public async Task Lines_and_sms_prompts_are_readAsync() string input = "Line1\r\nLine2\r\n> Line3\r\n"; byte[] buffer = Encoding.UTF8.GetBytes(input); +#if NETFRAMEWORK + stream.Write(buffer, 0, buffer.Length); +#else stream.Write(buffer); +#endif stream.Position = 0; dut.Open(); @@ -66,7 +74,11 @@ public async Task Empty_lines_are_readAsync() string input = "\r\n\r\n\r\n"; byte[] buffer = Encoding.UTF8.GetBytes(input); +#if NETFRAMEWORK + stream.Write(buffer, 0, buffer.Length); +#else stream.Write(buffer); +#endif stream.Position = 0; dut.Open(); @@ -90,7 +102,11 @@ public async Task Cme_Error_is_readAsync() string input = "+CME ERROR: ErrorMessage\r\n"; byte[] buffer = Encoding.UTF8.GetBytes(input); +#if NETFRAMEWORK + stream.Write(buffer, 0, buffer.Length); +#else stream.Write(buffer); +#endif stream.Position = 0; dut.Open(); @@ -110,7 +126,11 @@ public async Task Ring_is_readAsync() string input = "RING\r\n\r\nRING\r\n\r\nMISSED_CALL: 01:23PM 12345678\r\n"; byte[] buffer = Encoding.UTF8.GetBytes(input); +#if NETFRAMEWORK + stream.Write(buffer, 0, buffer.Length); +#else stream.Write(buffer); +#endif stream.Position = 0; dut.Open(); diff --git a/src/HeboTech.ATLib.sln b/src/HeboTech.ATLib.sln index b4d5034..4589cc3 100644 --- a/src/HeboTech.ATLib.sln +++ b/src/HeboTech.ATLib.sln @@ -1,13 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HeboTech.ATLib", "HeboTech.ATLib\HeboTech.ATLib.csproj", "{F919890A-9835-4D57-80F1-8F6BF2CC2085}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HeboTech.ATLib.TestConsole", "HeboTech.ATLib.TestConsole\HeboTech.ATLib.TestConsole.csproj", "{82A5A7D4-9AD3-4B95-AD72-CD1B48017AC3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeboTech.ATLib.Tests", "HeboTech.ATLib.Tests\HeboTech.ATLib.Tests.csproj", "{97A95146-06D3-436E-AE16-8F0A6D86B26D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HeboTech.ATLib.Tests", "HeboTech.ATLib.Tests\HeboTech.ATLib.Tests.csproj", "{97A95146-06D3-436E-AE16-8F0A6D86B26D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/HeboTech.ATLib/DTOs/PhoneNumber.cs b/src/HeboTech.ATLib/DTOs/PhoneNumber.cs index 6fe4917..3f388e2 100644 --- a/src/HeboTech.ATLib/DTOs/PhoneNumber.cs +++ b/src/HeboTech.ATLib/DTOs/PhoneNumber.cs @@ -4,7 +4,11 @@ public class PhoneNumber { public PhoneNumber(string number) { +#if NETSTANDARD2_0 + if (number.StartsWith("+")) +#else if (number.StartsWith('+')) +#endif { Ton = TypeOfNumber.International; Npi = NumberPlanIdentification.ISDN; diff --git a/src/HeboTech.ATLib/DTOs/SupportedPreferredMessageStorages.cs b/src/HeboTech.ATLib/DTOs/SupportedPreferredMessageStorages.cs index 2b11c40..39a921a 100644 --- a/src/HeboTech.ATLib/DTOs/SupportedPreferredMessageStorages.cs +++ b/src/HeboTech.ATLib/DTOs/SupportedPreferredMessageStorages.cs @@ -18,10 +18,17 @@ public SupportedPreferredMessageStorages(IEnumerable storage1, IEnumerab public override string ToString() { +#if NETSTANDARD2_0 + return + $"Storage1: {string.Join(",", Storage1)}{Environment.NewLine}" + + $"Storage2: {string.Join(",", Storage2)}{Environment.NewLine}" + + $"Storage3: {string.Join(",", Storage3)}"; +#elif NETSTANDARD2_1_OR_GREATER return $"Storage1: {string.Join(',', Storage1)}{Environment.NewLine}" + $"Storage2: {string.Join(',', Storage2)}{Environment.NewLine}" + $"Storage3: {string.Join(',', Storage3)}"; +#endif } } } diff --git a/src/HeboTech.ATLib/Events/MissedCallEventArgs.cs b/src/HeboTech.ATLib/Events/MissedCallEventArgs.cs index 380a478..64b76a3 100644 --- a/src/HeboTech.ATLib/Events/MissedCallEventArgs.cs +++ b/src/HeboTech.ATLib/Events/MissedCallEventArgs.cs @@ -13,7 +13,11 @@ public MissedCallEventArgs(string time, string phoneNumber) public static MissedCallEventArgs CreateFromResponse(string response) { +#if NETSTANDARD2_0 + string[] split = response.Split(new char[] { ' ' }, 3); +#elif NETSTANDARD2_1_OR_GREATER string[] split = response.Split(' ', 3); +#endif return new MissedCallEventArgs(split[1], split[2]); } } diff --git a/src/HeboTech.ATLib/HeboTech.ATLib.csproj b/src/HeboTech.ATLib/HeboTech.ATLib.csproj index 7422255..a2b160d 100644 --- a/src/HeboTech.ATLib/HeboTech.ATLib.csproj +++ b/src/HeboTech.ATLib/HeboTech.ATLib.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + netstandard2.1;netstandard2.0 HeboTech HeboTech ATLib 4.1.0 @@ -15,12 +15,16 @@ https://github.com/hbjorgo/ATLib/releases 4.1.0.0 4.1.0.0 + b8328b1a-795d-4e26-9238-43eee2160ffc - - + + + + + diff --git a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs index 4d6f685..254167e 100644 --- a/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs +++ b/src/HeboTech.ATLib/Modems/Generic/ModemBase.cs @@ -122,7 +122,11 @@ public virtual async Task>> GetAvailableCharac var match = Regex.Match(line, @"\+CSCS:\s\((?:""(?\w+)"",*)+\)"); if (match.Success) { +#if NETSTANDARD2_0 + return ModemResponse.ResultSuccess(match.Groups["characterSet"].Captures.Cast().Select(x => x.Value)); +#elif NETSTANDARD2_1_OR_GREATER return ModemResponse.ResultSuccess(match.Groups["characterSet"].Captures.Select(x => x.Value)); +#endif } } return ModemResponse.ResultError>(); @@ -150,9 +154,9 @@ public virtual async Task SetCharacterSetAsync(string characterSe AtResponse response = await channel.SendCommand($"AT+CSCS=\"{characterSet}\""); return ModemResponse.Success(response.Success); } - #endregion +#endregion - #region _3GPP_TS_27_005 +#region _3GPP_TS_27_005 public event EventHandler SmsReceived; public virtual async Task SetSmsMessageFormatAsync(SmsTextFormat format) @@ -317,7 +321,11 @@ public virtual async Task> ReadSmsAsync(int index, SmsTextFor SmsStatus status = SmsStatusHelpers.ToSmsStatus(statusCode); string pdu = line2Match.Groups["status"].Value; +#if NETSTANDARD2_0 + SmsDeliver pduMessage = Pdu.DecodeSmsDeliver(pdu.AsSpan()); +#elif NETSTANDARD2_1_OR_GREATER SmsDeliver pduMessage = Pdu.DecodeSmsDeliver(pdu); +#endif return ModemResponse.ResultSuccess(new Sms(status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); } @@ -408,9 +416,9 @@ public virtual async Task DeleteSmsAsync(int index) AtResponse response = await channel.SendCommand($"AT+CMGD={index}"); return ModemResponse.Success(response.Success); } - #endregion +#endregion - #region _3GPP_TS_27_007 +#region _3GPP_TS_27_007 public event EventHandler UssdResponseReceived; public virtual async Task> GetSimStatusAsync() @@ -429,6 +437,16 @@ public virtual async Task> GetSimStatusAsync() if (match.Success) { string cpinResult = match.Groups["pinresult"].Value; +#if NETSTANDARD2_0 + switch(cpinResult) + { + case "SIM PIN": return ModemResponse.ResultSuccess(SimStatus.SIM_PIN); + case "SIM PUK": return ModemResponse.ResultSuccess(SimStatus.SIM_PUK); + case "PH-NET PIN": return ModemResponse.ResultSuccess(SimStatus.SIM_NETWORK_PERSONALIZATION); + case "READY": return ModemResponse.ResultSuccess(SimStatus.SIM_READY); + default: return ModemResponse.ResultSuccess(SimStatus.SIM_ABSENT);// Treat unsupported lock types as "sim absent" + }; +#elif NETSTANDARD2_1_OR_GREATER return cpinResult switch { "SIM PIN" => ModemResponse.ResultSuccess(SimStatus.SIM_PIN), @@ -437,6 +455,7 @@ public virtual async Task> GetSimStatusAsync() "READY" => ModemResponse.ResultSuccess(SimStatus.SIM_READY), _ => ModemResponse.ResultSuccess(SimStatus.SIM_ABSENT),// Treat unsupported lock types as "sim absent" }; +#endif } return ModemResponse.ResultError(); @@ -530,7 +549,7 @@ public virtual async Task SendUssdAsync(string code, int codingSc AtResponse response = await channel.SendCommand($"AT+CUSD=1,\"{code}\",{codingScheme}"); return ModemResponse.Success(response.Success); } - #endregion +#endregion public virtual async Task SetErrorFormat(int errorFormat) { @@ -543,7 +562,7 @@ public void Close() Dispose(); } - #region Dispose +#region Dispose protected virtual void Dispose(bool disposing) { if (!disposed) @@ -573,6 +592,6 @@ public void Dispose() Dispose(disposing: true); GC.SuppressFinalize(this); } - #endregion +#endregion } } diff --git a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs index fca216a..80334be 100644 --- a/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs +++ b/src/HeboTech.ATLib/Modems/SIMCOM/SIM5320.cs @@ -65,7 +65,11 @@ public override async Task> ReadSmsAsync(int index, SmsTextFo string alphabet = line1Match.Groups["alphabet"].Value; int length = int.Parse(line1Match.Groups["length"].Value); string pdu = line2Match.Groups["pdu"].Value; +#if NETSTANDARD2_0 + SmsDeliver pduMessage = Pdu.DecodeSmsDeliver(pdu.AsSpan()); +#elif NETSTANDARD2_1_OR_GREATER SmsDeliver pduMessage = Pdu.DecodeSmsDeliver(pdu); +#endif return ModemResponse.ResultSuccess(new Sms((SmsStatus)status, pduMessage.SenderNumber, pduMessage.Timestamp, pduMessage.Message)); } } @@ -130,6 +134,6 @@ public override async Task>> ListSmssAsync(SmsS } return ModemResponse.ResultSuccess(smss); } - #endregion +#endregion } } diff --git a/src/HeboTech.ATLib/PDU/Pdu.cs b/src/HeboTech.ATLib/PDU/Pdu.cs index 43c178b..2f45799 100644 --- a/src/HeboTech.ATLib/PDU/Pdu.cs +++ b/src/HeboTech.ATLib/PDU/Pdu.cs @@ -6,6 +6,227 @@ namespace HeboTech.ATLib.PDU { +#if NETSTANDARD2_0 + public class Pdu + { + public static string EncodeSmsSubmit(PhoneNumber phoneNumber, string encodedMessage, byte dataCodingScheme, bool includeEmptySmscLength = true) + { + StringBuilder sb = new StringBuilder(); + // Length of SMSC information + if (includeEmptySmscLength) + sb.Append("00"); + // First octed of the SMS-SUBMIT message + sb.Append("11"); + // TP-Message-Reference. '00' lets the phone set the message reference number itself + sb.Append("00"); + // Address length. Length of phone number (number of digits) + sb.Append((phoneNumber.ToString().Length).ToString("X2")); + // Type-of-Address + sb.Append(GetAddressType(phoneNumber).ToString("X2")); + // Phone number in semi octets. 12345678 is represented as 21436587 + sb.Append(SwapPhoneNumberDigits(phoneNumber.ToString())); + // TP-PID Protocol identifier + sb.Append("00"); + // TP-DCS Data Coding Scheme. '00'-7bit default alphabet. '04'-8bit + sb.Append((dataCodingScheme).ToString("X2")); + // TP-Validity-Period. 'AA'-4 days + sb.Append("AA"); + // TP-User-Data-Length. If TP-DCS field indicates 7-bit data, the length is the number of septets. + // If TP-DCS indicates 8-bit data or Unicode, the length is the number of octets. + if (dataCodingScheme == 0) + { + int messageBitLength = encodedMessage.Length / 2 * 7; + int messageLength = messageBitLength % 8 == 0 ? messageBitLength / 8 : (messageBitLength / 8) + 1; + sb.Append((messageLength).ToString("X2")); + } + else + sb.Append((encodedMessage.Length / 2 * 8 / 7).ToString("X2")); + sb.Append(encodedMessage); + + return sb.ToString(); + } + + public static SmsDeliver DecodeSmsDeliver(ReadOnlySpan text, int timestampYearOffset = 2000) + { + int offset = 0; + + // SMSC information + byte smsc_length = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + PhoneNumber serviceCenterNumber = null; + if (smsc_length > 0) + { + serviceCenterNumber = DecodePhoneNumber(text.SliceOnIndex(offset,(offset += smsc_length * 2))); + } + + // SMS-DELIVER start + byte header = HexToByte(text.SliceOnIndex(offset,(offset += 2))); + + int tp_mti = header & 0b0000_0011; + if (tp_mti != (byte)PduType.SMS_DELIVER) + throw new ArgumentException("Invalid SMS-DELIVER data"); + + int tp_mms = header & 0b0000_0100; + int tp_rp = header & 0b1000_0000; + + byte tp_oa_length = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + tp_oa_length = (byte)(tp_oa_length % 2 == 0 ? tp_oa_length : tp_oa_length + 1); + PhoneNumber oa = null; + if (tp_oa_length > 0) + { + int oa_digits = tp_oa_length + 2; // Add 2 for TON + oa = DecodePhoneNumber(text.SliceOnIndex(offset, (offset += oa_digits))); + } + byte tp_pid = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + byte tp_dcs = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + ReadOnlySpan tp_scts = text.SliceOnIndex(offset, (offset += 14)); + byte tp_udl = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + int udlBytes = (int)Math.Ceiling(tp_udl * 7 / 8.0); + + ReadOnlySpan tp_ud = text.SliceOnIndex(offset, (offset += ((udlBytes) * 2))); + string message = null; + switch (tp_dcs) + { + case 0x00: + message = Gsm7.Decode(tp_ud.ToString()); + break; + default: + break; + } + DateTimeOffset scts = DecodeTimestamp(tp_scts, timestampYearOffset); + return new SmsDeliver(serviceCenterNumber, oa, message, scts); + } + + public static SmsSubmit DecodeSmsSubmit(ReadOnlySpan text, int timestampYearOffset = 2000) + { + int offset = 0; + + // SMSC information + byte smsc_length = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + PhoneNumber serviceCenterNumber = null; + if (smsc_length > 0) + { + serviceCenterNumber = DecodePhoneNumber(text.SliceOnIndex(offset, (offset += smsc_length * 2))); + } + + // SMS-DELIVER start + byte header = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + + int tp_mti = header & 0b0000_0011; + if (tp_mti != (byte)PduType.SMS_SUBMIT) + throw new ArgumentException("Invalid SMS-SUBMIT data"); + + int tp_rd = header & 0b0000_0100; + int tp_vpf = header & 0b0001_1000; + int tp_rp = header & 0b1000_0000; + + byte tp_mr = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + byte tp_oa_length = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + tp_oa_length = (byte)(tp_oa_length % 2 == 0 ? tp_oa_length : tp_oa_length + 1); + PhoneNumber oa = null; + if (tp_oa_length > 0) + { + int oa_digits = tp_oa_length + 2; // Add 2 for TON + oa = DecodePhoneNumber(text.SliceOnIndex(offset, (offset += oa_digits))); + } + byte tp_pid = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + byte tp_dcs = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + byte tp_vp = 0; + if (tp_vpf == 0x00) + tp_vp = HexToByte(text.SliceOnIndex(offset, (offset += 0))); + else if (tp_vpf == 0x01) + tp_vp = HexToByte(text.SliceOnIndex(offset, (offset += 14))); + else if (tp_vpf == 0x10) + tp_vp = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + else if (tp_vpf == 0x11) + tp_vp = HexToByte(text.SliceOnIndex(offset, (offset += 14))); + byte tp_udl = HexToByte(text.SliceOnIndex(offset, (offset += 2))); + + string message = null; + switch (tp_dcs) + { + case 0x00: + int length = (tp_udl * 7 / 8) + 1; + ReadOnlySpan tp_ud = text.SliceOnIndex(offset, (offset += ((length) * 2))); + message = Gsm7.Decode(tp_ud.ToString()); + break; + default: + break; + } + return new SmsSubmit(serviceCenterNumber, oa, message); + } + + private static byte HexToByte(ReadOnlySpan text) + { + byte retVal = (byte)int.Parse(text.ToString(), NumberStyles.HexNumber); + return retVal; + } + + private static char[] SwapPhoneNumberDigits(string data) + { + char[] swappedData = new char[data.Length]; + for (int i = 0; i < data.Length; i += 2) + { + swappedData[i] = data[i + 1]; + swappedData[i + 1] = data[i]; + } + if (swappedData[swappedData.Length - 1] == 'F') + { + char[] subArray = new char[swappedData.Length - 1]; + Array.Copy(swappedData, subArray, subArray.Length); + return subArray; + } + return swappedData; + } + + private static byte GetAddressType(PhoneNumber phoneNumber) + { + return (byte)(0b1000_0000 + (byte)phoneNumber.Ton + (byte)phoneNumber.Npi); + } + + private static PhoneNumber DecodePhoneNumber(ReadOnlySpan data) + { + if (data.Length < 4) + return default; + TypeOfNumber ton = (TypeOfNumber)((HexToByte(data.Slice(0, 2)) & 0b0111_0000) >> 4); + string number = new String(SwapPhoneNumberDigits(data.Slice(2).ToString())); + return new PhoneNumber( + number, + ton, + ton == TypeOfNumber.International ? NumberPlanIdentification.ISDN : NumberPlanIdentification.Unknown); + } + + private static DateTimeOffset DecodeTimestamp(ReadOnlySpan data, int timestampYearOffset = 2000) + { + char[] swappedData = new char[data.Length]; + for (int i = 0; i < swappedData.Length; i += 2) + { + swappedData[i] = data[i + 1]; + swappedData[i + 1] = data[i]; + } + ReadOnlySpan swappedSpan = swappedData; + + byte offset = DecimalToByte(swappedSpan.SliceOnIndex(12, 14)); + bool positive = (offset & (1 << 7)) == 0; + byte offsetQuarters = (byte)(offset & 0b0111_1111); + + DateTimeOffset timestamp = new DateTimeOffset( + DecimalToByte(swappedSpan.SliceOnIndex(0, 2)) + timestampYearOffset, + DecimalToByte(swappedSpan.SliceOnIndex(2, 4)), + DecimalToByte(swappedSpan.SliceOnIndex(4, 6)), + DecimalToByte(swappedSpan.SliceOnIndex(6, 8)), + DecimalToByte(swappedSpan.SliceOnIndex(8, 10)), + DecimalToByte(swappedSpan.SliceOnIndex(10, 12)), + TimeSpan.FromMinutes(offsetQuarters * 15)); // Offset in quarter of hours + return timestamp; + } + + private static byte DecimalToByte(ReadOnlySpan text) + { + return (byte)int.Parse(text.ToString(), NumberStyles.Integer); + } + } + +#elif NETSTANDARD2_1_OR_GREATER public class Pdu { public static string EncodeSmsSubmit(PhoneNumber phoneNumber, string encodedMessage, byte dataCodingScheme, bool includeEmptySmscLength = true) @@ -220,4 +441,5 @@ static byte DecimalToByte(ReadOnlySpan text) } } } +#endif } diff --git a/src/HeboTech.ATLib/PDU/ReadOnlySpanExtensions.cs b/src/HeboTech.ATLib/PDU/ReadOnlySpanExtensions.cs new file mode 100644 index 0000000..7354ef4 --- /dev/null +++ b/src/HeboTech.ATLib/PDU/ReadOnlySpanExtensions.cs @@ -0,0 +1,14 @@ +#if NETSTANDARD2_0 +using System; + +namespace HeboTech.ATLib.PDU +{ + internal static class ReadOnlySpanExtensions + { + public static ReadOnlySpan SliceOnIndex(this ReadOnlySpan span, int start, int end) + { + return span.Slice(start, end - start); + } + } +} +#endif