From b86ee8354577cf407585325a2cdd3e6675a3d47c Mon Sep 17 00:00:00 2001 From: Welly Date: Wed, 2 Jan 2013 10:54:46 +0700 Subject: [PATCH] adding reverse engineering result for gsmcom use JustDecompile --- .gitattributes | 22 + .gitignore | 163 + GSMComm.sln | 38 + GSMCommServer/GSMCommServer.csproj | 82 + .../GSMCommServerReferences/GSMCommShared.dll | Bin 0 -> 16384 bytes .../GSMCommunication.dll | Bin 0 -> 98304 bytes .../GSMCommServerReferences/PDUConverter.dll | Bin 0 -> 65536 bytes GSMCommServer/Properties/AssemblyInfo.cs | 17 + GSMCommServer/Server/AuthorizationModule.cs | 53 + .../Server/MessageSendErrorEventArgs.cs | 46 + .../Server/MessageSendErrorEventHandler.cs | 11 + GSMCommServer/Server/MessageSendEventArgs.cs | 74 + .../Server/MessageSendEventHandler.cs | 12 + GSMCommServer/Server/SmsSender.cs | 193 + GSMCommServer/Server/SmsServer.cs | 360 ++ GSMCommShared/CommException.cs | 75 + GSMCommShared/GSMCommShared.csproj | 57 + .../GsmCommunication/CommException.cs | 75 + .../MessageServiceErrorException.cs | 61 + .../MobileEquipmentErrorException.cs | 61 + GSMCommShared/Interfaces/ISmsSender.cs | 25 + GSMCommShared/MessageServiceErrorException.cs | 61 + .../MobileEquipmentErrorException.cs | 61 + GSMCommShared/Properties/AssemblyInfo.cs | 17 + GSMCommunication/GSMCommunication.csproj | 250 ++ .../GSMCommShared.dll | Bin 0 -> 16384 bytes .../PDUConverter.dll | Bin 0 -> 65536 bytes .../GsmCommunication/AddressData.cs | 47 + .../GsmCommunication/BatteryChargeInfo.cs | 53 + .../GsmCommunication/CbmIndicationStyle.cs | 30 + GSMCommunication/GsmCommunication/Charset.cs | 35 + .../GsmCommunication/DecodedShortMessage.cs | 78 + .../GsmCommunication/DeleteFlag.cs | 30 + .../GsmCommunication/DeleteScope.cs | 17 + .../GsmCommunication/GsmCommMain.cs | 1543 ++++++++ GSMCommunication/GsmCommunication/GsmPhone.cs | 3262 +++++++++++++++++ .../IMessageIndicationObject.cs | 10 + .../GsmCommunication/IProtocol.cs | 91 + .../GsmCommunication/IdentificationInfo.cs | 79 + .../IndicationBufferSetting.cs | 19 + GSMCommunication/GsmCommunication/LogLevel.cs | 25 + .../GsmCommunication/LoglineAddedEventArgs.cs | 62 + .../LoglineAddedEventHandler.cs | 11 + .../GsmCommunication/MemoryLocation.cs | 47 + .../GsmCommunication/MemoryStatus.cs | 47 + .../MemoryStatusWithStorage.cs | 34 + .../GsmCommunication/MessageErrorEventArgs.cs | 34 + .../GsmCommunication/MessageEventArgs.cs | 33 + .../MessageIndicationHandlers.cs | 349 ++ .../GsmCommunication/MessageIndicationMode.cs | 30 + .../MessageIndicationSettings.cs | 142 + .../MessageIndicationSupport.cs | 225 ++ .../GsmCommunication/MessageMemoryStatus.cs | 79 + .../MessageReceivedEventArgs.cs | 30 + .../MessageReceivedEventHandler.cs | 11 + .../GsmCommunication/MessageStorageInfo.cs | 19 + .../GsmCommunication/MoreMessagesMode.cs | 23 + .../GsmCommunication/OperatorFormat.cs | 18 + .../GsmCommunication/OperatorInfo.cs | 80 + .../GsmCommunication/OperatorInfo2.cs | 121 + .../GsmCommunication/OperatorSelectionMode.cs | 25 + .../GsmCommunication/OperatorStatus.cs | 17 + .../GsmCommunication/PhoneMessageStatus.cs | 19 + .../GsmCommunication/PhoneNumberService.cs | 21 + .../GsmCommunication/PhoneStorageType.cs | 20 + .../GsmCommunication/PhonebookEntry.cs | 119 + .../PhonebookEntryWithStorage.cs | 47 + .../GsmCommunication/PinStatus.cs | 51 + .../GsmCommunication/ProgressEventArgs.cs | 32 + .../GsmCommunication/ProgressEventHandler.cs | 11 + .../GsmCommunication/SerialPortFixer.cs | 213 ++ .../GsmCommunication/ShortMessage.cs | 90 + .../GsmCommunication/ShortMessageFromPhone.cs | 73 + .../GsmCommunication/SignalQualityInfo.cs | 51 + .../SmsDeliverIndicationStyle.cs | 30 + .../SmsStatusReportIndicationStyle.cs | 22 + .../GsmCommunication/SubscriberInfo.cs | 133 + GSMCommunication/Properties/AssemblyInfo.cs | 17 + .../GsmComm.PduConverter/AddressType.cs | 155 + .../GsmComm.PduConverter/BcdWorker.cs | 151 + PDUConverter/GsmComm.PduConverter/Calc.cs | 173 + .../GsmComm.PduConverter/DataCodingScheme.cs | 268 ++ .../GsmComm.PduConverter/GeneralDataCoding.cs | 75 + .../GsmComm.PduConverter/ITimestamp.cs | 15 + .../IncomingMessageFlags.cs | 49 + .../IncomingMessageType.cs | 15 + .../GsmComm.PduConverter/IncomingSmsPdu.cs | 131 + .../KnownMessageStatus.cs | 63 + .../GsmComm.PduConverter/MessageCoding.cs | 70 + .../GsmComm.PduConverter/MessageStatus.cs | 141 + .../MessageWaitingDiscard.cs | 29 + .../MessageWaitingIndication.cs | 50 + .../MessageWaitingStore.cs | 29 + .../MessageWaitingStoreUcs2.cs | 29 + .../OutgoingMessageFlags.cs | 49 + .../OutgoingMessageType.cs | 15 + .../GsmComm.PduConverter/OutgoingSmsPdu.cs | 155 + .../ParameterIndicator.cs | 235 ++ PDUConverter/GsmComm.PduConverter/PduParts.cs | 264 ++ .../GsmComm.PduConverter/ProtocolID.cs | 213 ++ .../RelativeValidityPeriod.cs | 174 + .../ReservedCodingGroup.cs | 32 + .../SmartMessaging/ConcatInfoComparer.cs | 83 + .../SmartMessaging/ConcatMessageElement16.cs | 157 + .../SmartMessaging/ConcatMessageElement8.cs | 151 + .../SmartMessaging/IConcatenationInfo.cs | 35 + .../SmartMessaging/InformationElement.cs | 33 + .../SmartMessaging/OtaBitmap.cs | 312 ++ .../SmartMessaging/PortAddressElement16.cs | 111 + .../SmartMessaging/SmartMessageDecoder.cs | 470 +++ .../SmartMessaging/SmartMessageFactory.cs | 455 +++ .../UnknownInformationElement.cs | 114 + .../SmsDeliverMessageFlags.cs | 173 + .../GsmComm.PduConverter/SmsDeliverPdu.cs | 253 ++ PDUConverter/GsmComm.PduConverter/SmsPdu.cs | 628 ++++ .../SmsStatusReportMessageFlags.cs | 151 + .../SmsStatusReportPdu.cs | 380 ++ .../SmsSubmitMessageFlags.cs | 259 ++ .../GsmComm.PduConverter/SmsSubmitPdu.cs | 476 +++ .../GsmComm.PduConverter/SmsTimestamp.cs | 407 ++ .../GsmComm.PduConverter/StatusCategory.cs | 19 + .../GsmComm.PduConverter/TextDataConverter.cs | 464 +++ .../GsmComm.PduConverter/ValidityPeriod.cs | 17 + .../ValidityPeriodFormat.cs | 17 + PDUConverter/PDUConverter.csproj | 227 ++ PDUConverter/Properties/AssemblyInfo.cs | 17 + 126 files changed, 17508 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 GSMComm.sln create mode 100644 GSMCommServer/GSMCommServer.csproj create mode 100644 GSMCommServer/GSMCommServerReferences/GSMCommShared.dll create mode 100644 GSMCommServer/GSMCommServerReferences/GSMCommunication.dll create mode 100644 GSMCommServer/GSMCommServerReferences/PDUConverter.dll create mode 100644 GSMCommServer/Properties/AssemblyInfo.cs create mode 100644 GSMCommServer/Server/AuthorizationModule.cs create mode 100644 GSMCommServer/Server/MessageSendErrorEventArgs.cs create mode 100644 GSMCommServer/Server/MessageSendErrorEventHandler.cs create mode 100644 GSMCommServer/Server/MessageSendEventArgs.cs create mode 100644 GSMCommServer/Server/MessageSendEventHandler.cs create mode 100644 GSMCommServer/Server/SmsSender.cs create mode 100644 GSMCommServer/Server/SmsServer.cs create mode 100644 GSMCommShared/CommException.cs create mode 100644 GSMCommShared/GSMCommShared.csproj create mode 100644 GSMCommShared/GsmCommunication/CommException.cs create mode 100644 GSMCommShared/GsmCommunication/MessageServiceErrorException.cs create mode 100644 GSMCommShared/GsmCommunication/MobileEquipmentErrorException.cs create mode 100644 GSMCommShared/Interfaces/ISmsSender.cs create mode 100644 GSMCommShared/MessageServiceErrorException.cs create mode 100644 GSMCommShared/MobileEquipmentErrorException.cs create mode 100644 GSMCommShared/Properties/AssemblyInfo.cs create mode 100644 GSMCommunication/GSMCommunication.csproj create mode 100644 GSMCommunication/GSMCommunicationReferences/GSMCommShared.dll create mode 100644 GSMCommunication/GSMCommunicationReferences/PDUConverter.dll create mode 100644 GSMCommunication/GsmCommunication/AddressData.cs create mode 100644 GSMCommunication/GsmCommunication/BatteryChargeInfo.cs create mode 100644 GSMCommunication/GsmCommunication/CbmIndicationStyle.cs create mode 100644 GSMCommunication/GsmCommunication/Charset.cs create mode 100644 GSMCommunication/GsmCommunication/DecodedShortMessage.cs create mode 100644 GSMCommunication/GsmCommunication/DeleteFlag.cs create mode 100644 GSMCommunication/GsmCommunication/DeleteScope.cs create mode 100644 GSMCommunication/GsmCommunication/GsmCommMain.cs create mode 100644 GSMCommunication/GsmCommunication/GsmPhone.cs create mode 100644 GSMCommunication/GsmCommunication/IMessageIndicationObject.cs create mode 100644 GSMCommunication/GsmCommunication/IProtocol.cs create mode 100644 GSMCommunication/GsmCommunication/IdentificationInfo.cs create mode 100644 GSMCommunication/GsmCommunication/IndicationBufferSetting.cs create mode 100644 GSMCommunication/GsmCommunication/LogLevel.cs create mode 100644 GSMCommunication/GsmCommunication/LoglineAddedEventArgs.cs create mode 100644 GSMCommunication/GsmCommunication/LoglineAddedEventHandler.cs create mode 100644 GSMCommunication/GsmCommunication/MemoryLocation.cs create mode 100644 GSMCommunication/GsmCommunication/MemoryStatus.cs create mode 100644 GSMCommunication/GsmCommunication/MemoryStatusWithStorage.cs create mode 100644 GSMCommunication/GsmCommunication/MessageErrorEventArgs.cs create mode 100644 GSMCommunication/GsmCommunication/MessageEventArgs.cs create mode 100644 GSMCommunication/GsmCommunication/MessageIndicationHandlers.cs create mode 100644 GSMCommunication/GsmCommunication/MessageIndicationMode.cs create mode 100644 GSMCommunication/GsmCommunication/MessageIndicationSettings.cs create mode 100644 GSMCommunication/GsmCommunication/MessageIndicationSupport.cs create mode 100644 GSMCommunication/GsmCommunication/MessageMemoryStatus.cs create mode 100644 GSMCommunication/GsmCommunication/MessageReceivedEventArgs.cs create mode 100644 GSMCommunication/GsmCommunication/MessageReceivedEventHandler.cs create mode 100644 GSMCommunication/GsmCommunication/MessageStorageInfo.cs create mode 100644 GSMCommunication/GsmCommunication/MoreMessagesMode.cs create mode 100644 GSMCommunication/GsmCommunication/OperatorFormat.cs create mode 100644 GSMCommunication/GsmCommunication/OperatorInfo.cs create mode 100644 GSMCommunication/GsmCommunication/OperatorInfo2.cs create mode 100644 GSMCommunication/GsmCommunication/OperatorSelectionMode.cs create mode 100644 GSMCommunication/GsmCommunication/OperatorStatus.cs create mode 100644 GSMCommunication/GsmCommunication/PhoneMessageStatus.cs create mode 100644 GSMCommunication/GsmCommunication/PhoneNumberService.cs create mode 100644 GSMCommunication/GsmCommunication/PhoneStorageType.cs create mode 100644 GSMCommunication/GsmCommunication/PhonebookEntry.cs create mode 100644 GSMCommunication/GsmCommunication/PhonebookEntryWithStorage.cs create mode 100644 GSMCommunication/GsmCommunication/PinStatus.cs create mode 100644 GSMCommunication/GsmCommunication/ProgressEventArgs.cs create mode 100644 GSMCommunication/GsmCommunication/ProgressEventHandler.cs create mode 100644 GSMCommunication/GsmCommunication/SerialPortFixer.cs create mode 100644 GSMCommunication/GsmCommunication/ShortMessage.cs create mode 100644 GSMCommunication/GsmCommunication/ShortMessageFromPhone.cs create mode 100644 GSMCommunication/GsmCommunication/SignalQualityInfo.cs create mode 100644 GSMCommunication/GsmCommunication/SmsDeliverIndicationStyle.cs create mode 100644 GSMCommunication/GsmCommunication/SmsStatusReportIndicationStyle.cs create mode 100644 GSMCommunication/GsmCommunication/SubscriberInfo.cs create mode 100644 GSMCommunication/Properties/AssemblyInfo.cs create mode 100644 PDUConverter/GsmComm.PduConverter/AddressType.cs create mode 100644 PDUConverter/GsmComm.PduConverter/BcdWorker.cs create mode 100644 PDUConverter/GsmComm.PduConverter/Calc.cs create mode 100644 PDUConverter/GsmComm.PduConverter/DataCodingScheme.cs create mode 100644 PDUConverter/GsmComm.PduConverter/GeneralDataCoding.cs create mode 100644 PDUConverter/GsmComm.PduConverter/ITimestamp.cs create mode 100644 PDUConverter/GsmComm.PduConverter/IncomingMessageFlags.cs create mode 100644 PDUConverter/GsmComm.PduConverter/IncomingMessageType.cs create mode 100644 PDUConverter/GsmComm.PduConverter/IncomingSmsPdu.cs create mode 100644 PDUConverter/GsmComm.PduConverter/KnownMessageStatus.cs create mode 100644 PDUConverter/GsmComm.PduConverter/MessageCoding.cs create mode 100644 PDUConverter/GsmComm.PduConverter/MessageStatus.cs create mode 100644 PDUConverter/GsmComm.PduConverter/MessageWaitingDiscard.cs create mode 100644 PDUConverter/GsmComm.PduConverter/MessageWaitingIndication.cs create mode 100644 PDUConverter/GsmComm.PduConverter/MessageWaitingStore.cs create mode 100644 PDUConverter/GsmComm.PduConverter/MessageWaitingStoreUcs2.cs create mode 100644 PDUConverter/GsmComm.PduConverter/OutgoingMessageFlags.cs create mode 100644 PDUConverter/GsmComm.PduConverter/OutgoingMessageType.cs create mode 100644 PDUConverter/GsmComm.PduConverter/OutgoingSmsPdu.cs create mode 100644 PDUConverter/GsmComm.PduConverter/ParameterIndicator.cs create mode 100644 PDUConverter/GsmComm.PduConverter/PduParts.cs create mode 100644 PDUConverter/GsmComm.PduConverter/ProtocolID.cs create mode 100644 PDUConverter/GsmComm.PduConverter/RelativeValidityPeriod.cs create mode 100644 PDUConverter/GsmComm.PduConverter/ReservedCodingGroup.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatInfoComparer.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement16.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement8.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/IConcatenationInfo.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/InformationElement.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/OtaBitmap.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/PortAddressElement16.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageDecoder.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageFactory.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmartMessaging/UnknownInformationElement.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsDeliverMessageFlags.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsDeliverPdu.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsPdu.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsStatusReportMessageFlags.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsStatusReportPdu.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsSubmitMessageFlags.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsSubmitPdu.cs create mode 100644 PDUConverter/GsmComm.PduConverter/SmsTimestamp.cs create mode 100644 PDUConverter/GsmComm.PduConverter/StatusCategory.cs create mode 100644 PDUConverter/GsmComm.PduConverter/TextDataConverter.cs create mode 100644 PDUConverter/GsmComm.PduConverter/ValidityPeriod.cs create mode 100644 PDUConverter/GsmComm.PduConverter/ValidityPeriodFormat.cs create mode 100644 PDUConverter/PDUConverter.csproj create mode 100644 PDUConverter/Properties/AssemblyInfo.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ebd21a --- /dev/null +++ b/.gitignore @@ -0,0 +1,163 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.vspscc +.builds +*.dotCover + +## TODO: If you have NuGet Package Restore enabled, uncomment this +#packages/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp + +# ReSharper is a .NET coding add-in +_ReSharper* + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Others +[Bb]in +[Oo]bj +sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML + + + +############ +## Windows +############ + +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg + +# Mac crap +.DS_Store diff --git a/GSMComm.sln b/GSMComm.sln new file mode 100644 index 0000000..bc3c4bc --- /dev/null +++ b/GSMComm.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PDUConverter", "PDUConverter\PDUConverter.csproj", "{5E657EFE-0A30-466D-B025-FF177E23A727}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSMCommShared", "GSMCommShared\GSMCommShared.csproj", "{71EA4054-A98A-46BA-84FA-81652DB72B72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSMCommunication", "GSMCommunication\GSMCommunication.csproj", "{32B76F1E-B022-47C6-86EC-28639D348067}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSMCommServer", "GSMCommServer\GSMCommServer.csproj", "{67CB92ED-436B-4005-A6D6-092503D6E496}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E657EFE-0A30-466D-B025-FF177E23A727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E657EFE-0A30-466D-B025-FF177E23A727}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E657EFE-0A30-466D-B025-FF177E23A727}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E657EFE-0A30-466D-B025-FF177E23A727}.Release|Any CPU.Build.0 = Release|Any CPU + {71EA4054-A98A-46BA-84FA-81652DB72B72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71EA4054-A98A-46BA-84FA-81652DB72B72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71EA4054-A98A-46BA-84FA-81652DB72B72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71EA4054-A98A-46BA-84FA-81652DB72B72}.Release|Any CPU.Build.0 = Release|Any CPU + {32B76F1E-B022-47C6-86EC-28639D348067}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32B76F1E-B022-47C6-86EC-28639D348067}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32B76F1E-B022-47C6-86EC-28639D348067}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32B76F1E-B022-47C6-86EC-28639D348067}.Release|Any CPU.Build.0 = Release|Any CPU + {67CB92ED-436B-4005-A6D6-092503D6E496}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67CB92ED-436B-4005-A6D6-092503D6E496}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67CB92ED-436B-4005-A6D6-092503D6E496}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67CB92ED-436B-4005-A6D6-092503D6E496}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/GSMCommServer/GSMCommServer.csproj b/GSMCommServer/GSMCommServer.csproj new file mode 100644 index 0000000..a2bea1a --- /dev/null +++ b/GSMCommServer/GSMCommServer.csproj @@ -0,0 +1,82 @@ + + + + {67CB92ED-436B-4005-A6D6-092503D6E496} + 2 + Debug + AnyCPU + GSMCommServer + Library + GsmComm + v4.0 + + + bin\Debug\ + true + DEBUG;TRACE + false + 4 + full + prompt + AnyCPU + true + + + bin\Release\ + false + TRACE + true + 4 + pdbonly + prompt + AnyCPU + + + + + .\GSMCommServerReferences\GSMCommShared.dll + + + + .\GSMCommServerReferences\GSMCommunication.dll + + + .\GSMCommServerReferences\PDUConverter.dll + + + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + + \ No newline at end of file diff --git a/GSMCommServer/GSMCommServerReferences/GSMCommShared.dll b/GSMCommServer/GSMCommServerReferences/GSMCommShared.dll new file mode 100644 index 0000000000000000000000000000000000000000..09feab41b0a4751223d1c2068a9486088c30699f GIT binary patch literal 16384 zcmeHNYitzP6+W}OUe*S-wrd~|M8?>>G|4=PC!K zj&qeg{hB4GO{33Ll5$*0r3_n+sj`_#$y!Qo-4K9v%!oKb$oJe$2bax3+F-!AgqfC6X&PyilP1<*9;LTUJUR?jxk7u91 ze(kws>0aZ5Yxg$}wSV+s$y0|nul&)HPoJ&!UrLuXZaW>De)3Yq&))9dKK$I!*{A;a z>?_Cb8a!71&-s0q*Zpwk^TXA<4t)CVleO#ZufKcb_4js%Jx90ozjc0S#!F|;l`PS5tS@n>2y54MZ#QA+!L#Q@XUt1q;6p|v+L9fELYClmAvU*YUqqeD~`Yd4~J~W7L4ON??6hKED z{j1tHM_O^e1AGOtt6HraG4P^+m9|cMM&f7u$-H`+4gus9xQcKgJjbZS9k9?;M<(Rr z61LfFOPsr(UZj)cr#~?DNmtOH&h*F93CT~Fq&GnS!L-~X`eCN)n8umzXL^F^Z#^%2 z{Pbt0!z`KN6?oV2>iYeZZ<7o8F;yrmSbKQLE~jk0XGHJ7c;k? zIWMY{=p4-eT_vq_C_g2s5>i9*ZO*E4_a+b~D9?WrT zfkV2$Po_NueshtBM2aM4f(BaH_fjS&8IhkWuoo{=q1;PZ$x)7WtU$X8eJ_yB+vg>| z|B2Tk63-yaw2^5u(+JZ|Oz&f=Fx8mu1ig<2xc?Z_BTSEheu++i#xauwEoQl(;%SQK z7Q%BZmO^+cn?UabRw1flx`g(kU(fw7&{6bTxF2P@olZmYFy4a@S@c)Xy?7fiEAib@ zth(Z@S}H9E-70N%s0Z(~7uqS%doU{|k?)|uavJpFPSi(Vfg=`#T$AX#BmEVfMk1rD zE2%x&*GBc|zU+s=%ZPO2$;j5cY-4O7=*6dkZ^HA&6s zwC;q>hY$%mV^AC^^_a+xLUG$LslbXOgt{{+TT7}TWTPp%wkMn7C@zxfH3*BQswA;) zwiqZFA7J}b`yo+qHhR9Nih5e`t(q-3f9CSNWVU`vsFtmzvI7cqgpZa=sRd|gV(7Rt zVz%a7##>a=0?tT@dK44stih*q^%28-xY@=EAIsRP15>D5?bX#dgWqgfYBHt|^k}xO z<_$;0t*R9_HGY8Ol_&uv&@bJp(d5W zVn%Xo$>?^*EJ%I~FgzoTv@^eWT5i!SEml}>dqzu)&&PpuykZUJp<=GaD`+DUv->FzA=PpdODu=#wQ0EQIC7lUOS59;tk4Ss8)_y$dPm@dwx; z5DdsZDHwn$Q~{_0!rdcC021loGgOUb zhU!)|%{X5C?d~awt*?8s`*OM>({9m)4pEm7RZ50n*ee(CS8miY{JAPsF zJtgm5w12Yc;^xqi@{k`TO~gei{Fy zJoD?p;)<@{b!=VoK-fH8mfHTctA9B&_?kXE)GpDiP*+<|?%lZCd5m8aTP2Blf)zPc zt7fHjWuOahQK|5ea~ip!N}@T=6Ops)p&Uu3byciU+2@K4kXTu%RHDgITkTa+a;Gw2 z5-pHuZf>H>ue~7G)z&uD)YXQ=@+iAS5>>|=>SIc6EL>CH+pE+x#A@qmmi5LGHFb%l zjg9xJjj^TirPx(T6bOgv!r1HKcbWh=F)+;$fcJQ&Z#9*4myyaXbv^y2@rWh82=YUMQ?l?+1C?ceKT8iy;IRr#4E3=@?Qbrb8{a2v0ZkXi-3!Oi-3fECeDoGj{0Iv z;-8Zbj>d@BN}QvJwl(4GCAP6no9%5tZwp1~A<#DJMlV7es0(}qbPf8V4f#I!EW4}C z#+t?>5v^ivc6TfOA{Dk)FcP9FWFn4+^RbGUDe5JI&8m4%+ykuwsRg}4HtY;=*>=je z4?!j5WW!2>yl;X?+t{WSKci>=HBgLyQeTEWxrF__K}kyU7z3zC0jF|!5miMtVsx20 zaUZ}aMEy8NNV#!ZVPoNxA;wRS%C8-Hbvp5rh!BORs8yu|xCoCbY8K HO$hu41B{St literal 0 HcmV?d00001 diff --git a/GSMCommServer/GSMCommServerReferences/GSMCommunication.dll b/GSMCommServer/GSMCommServerReferences/GSMCommunication.dll new file mode 100644 index 0000000000000000000000000000000000000000..2939cc48d52153884644cd47c9ff967363dfc441 GIT binary patch literal 98304 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 +/// Implements the authorization module for the server. +/// +namespace GsmComm.Server +{ + public class AuthorizationModule : IAuthorizeRemotingConnection + { + private bool allowAnonymous; + + /// + /// Initializes a new instance of the module. + /// + /// Specifies if users authenticated anonymously can + /// connect to the current channel. + public AuthorizationModule(bool allowAnonymous) + { + this.allowAnonymous = allowAnonymous; + } + + /// + /// Gets a Boolean value that indicates whether the network address of the client is + /// authorized to connect on the current channel. + /// + /// The that identifies the network address of the client. + /// true if the network address of the client is authorized; otherwise, false. + public bool IsConnectingEndPointAuthorized(EndPoint endPoint) + { + return true; + } + + /// + /// Gets a Boolean value that indicates whether the user identity of the client is + /// authorized to connect on the current channel. + /// + /// The that represents the user identity of the client. + /// true if the user identity of the client is authorized; otherwise, false. + public bool IsConnectingIdentityAuthorized(IIdentity identity) + { + bool flag = true; + WindowsIdentity windowsIdentity = identity as WindowsIdentity; + if (windowsIdentity != null && windowsIdentity.IsAnonymous && !this.allowAnonymous) + { + flag = false; + } + return flag; + } + } +} \ No newline at end of file diff --git a/GSMCommServer/Server/MessageSendErrorEventArgs.cs b/GSMCommServer/Server/MessageSendErrorEventArgs.cs new file mode 100644 index 0000000..62a1a46 --- /dev/null +++ b/GSMCommServer/Server/MessageSendErrorEventArgs.cs @@ -0,0 +1,46 @@ +using System; + +/// +/// Provides data for the error events that deal with message sending. +/// +namespace GsmComm.Server +{ + public class MessageSendErrorEventArgs : MessageSendEventArgs + { + private Exception exception; + + /// + /// Gets the exception that caused the error. + /// + public Exception Exception + { + get + { + return this.exception; + } + } + + /// + /// Initializes a new instance of the . + /// + /// The message that failed sending. + /// The destination the message was attempted to send to. + /// The exception that caused the error. + public MessageSendErrorEventArgs(string message, string destination, Exception exception) : base(message, destination) + { + this.exception = exception; + } + + /// + /// Initializes a new instance of the . + /// + /// The message that failed sending. + /// The destination the message was attempted to send to. + /// The exception that caused the error. + /// The name of the user from which the action started. + public MessageSendErrorEventArgs(string message, string destination, Exception exception, string userName) : base(message, destination, userName) + { + this.exception = exception; + } + } +} \ No newline at end of file diff --git a/GSMCommServer/Server/MessageSendErrorEventHandler.cs b/GSMCommServer/Server/MessageSendErrorEventHandler.cs new file mode 100644 index 0000000..7763f4b --- /dev/null +++ b/GSMCommServer/Server/MessageSendErrorEventHandler.cs @@ -0,0 +1,11 @@ +using System; + +/// +/// The method that handles the event. +/// +/// The origin of the event. +/// The associated with the event. +namespace GsmComm.Server +{ + public delegate void MessageSendErrorEventHandler(object sender, MessageSendErrorEventArgs e); +} \ No newline at end of file diff --git a/GSMCommServer/Server/MessageSendEventArgs.cs b/GSMCommServer/Server/MessageSendEventArgs.cs new file mode 100644 index 0000000..e512328 --- /dev/null +++ b/GSMCommServer/Server/MessageSendEventArgs.cs @@ -0,0 +1,74 @@ +using System; + +/// +/// Provides data for the events that deal with message sending. +/// +namespace GsmComm.Server +{ + public class MessageSendEventArgs : EventArgs + { + private string message; + + private string destination; + + private string userName; + + /// + /// Gets the destination the message is being or was sent to. + /// + public string Destination + { + get + { + return this.destination; + } + } + + /// + /// Gets the message that is being sent or was sent. + /// + public string Message + { + get + { + return this.message; + } + } + + /// + /// Gets the user name from which the action started. + /// + public string UserName + { + get + { + return this.userName; + } + } + + /// + /// Initializes a new instance of the . + /// + /// The message that is being sent or was sent. + /// The destination the message is being or was sent to. + public MessageSendEventArgs(string message, string destination) + { + this.message = message; + this.destination = destination; + this.userName = string.Empty; + } + + /// + /// Initializes a new instance of the . + /// + /// The message that is being sent or was sent. + /// The destination the message is being or was sent to. + /// The name of the user from which the action started. + public MessageSendEventArgs(string message, string destination, string userName) + { + this.message = message; + this.destination = destination; + this.userName = userName; + } + } +} \ No newline at end of file diff --git a/GSMCommServer/Server/MessageSendEventHandler.cs b/GSMCommServer/Server/MessageSendEventHandler.cs new file mode 100644 index 0000000..15e5347 --- /dev/null +++ b/GSMCommServer/Server/MessageSendEventHandler.cs @@ -0,0 +1,12 @@ +using System; + +/// +/// The method that handles the and +/// events. +/// +/// The origin of the event. +/// The associated with the event. +namespace GsmComm.Server +{ + public delegate void MessageSendEventHandler(object sender, MessageSendEventArgs e); +} \ No newline at end of file diff --git a/GSMCommServer/Server/SmsSender.cs b/GSMCommServer/Server/SmsSender.cs new file mode 100644 index 0000000..289231d --- /dev/null +++ b/GSMCommServer/Server/SmsSender.cs @@ -0,0 +1,193 @@ +using GsmComm.GsmCommunication; +using GsmComm.Interfaces; +using GsmComm.PduConverter; +using System; +using System.Threading; + +/// +/// Implements a remotable object to send SMS messages. +/// +namespace GsmComm.Server +{ + public class SmsSender : MarshalByRefObject, ISmsSender + { + private GsmCommMain comm; + + private bool disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The COM port to connect to. + /// The baud rate to use. + /// The communictaion timeout. + public SmsSender(string portName, int baudRate, int timeout) + { + this.disposed = false; + this.comm = new GsmCommMain(portName, baudRate, timeout); + this.ConnectEvents(); + try + { + this.comm.Open(); + } + catch (Exception exception) + { + this.DisconnectEvents(); + this.comm = null; + throw; + } + } + + private void comm_MessageSendComplete(object sender, MessageEventArgs e) + { + if (this.MessageSendComplete != null) + { + string userDataText = e.Pdu.UserDataText; + string empty = string.Empty; + if (e.Pdu is SmsSubmitPdu) + { + empty = (e.Pdu as SmsSubmitPdu).DestinationAddress; + } + MessageSendEventArgs messageSendEventArg = new MessageSendEventArgs(userDataText, empty, this.GetIdentityName()); + this.MessageSendComplete(this, messageSendEventArg); + } + } + + private void comm_MessageSendFailed(object sender, MessageErrorEventArgs e) + { + if (this.MessageSendFailed != null) + { + string userDataText = e.Pdu.UserDataText; + string empty = string.Empty; + if (e.Pdu is SmsSubmitPdu) + { + empty = (e.Pdu as SmsSubmitPdu).DestinationAddress; + } + MessageSendErrorEventArgs messageSendErrorEventArg = new MessageSendErrorEventArgs(userDataText, empty, e.Exception, this.GetIdentityName()); + this.MessageSendFailed(this, messageSendErrorEventArg); + } + } + + private void comm_MessageSendStarting(object sender, MessageEventArgs e) + { + if (this.MessageSendStarting != null) + { + string userDataText = e.Pdu.UserDataText; + string empty = string.Empty; + if (e.Pdu is SmsSubmitPdu) + { + empty = (e.Pdu as SmsSubmitPdu).DestinationAddress; + } + MessageSendEventArgs messageSendEventArg = new MessageSendEventArgs(userDataText, empty, this.GetIdentityName()); + this.MessageSendStarting(this, messageSendEventArg); + } + } + + private void ConnectEvents() + { + this.comm.MessageSendStarting += new GsmCommMain.MessageEventHandler(this.comm_MessageSendStarting); + this.comm.MessageSendComplete += new GsmCommMain.MessageEventHandler(this.comm_MessageSendComplete); + this.comm.MessageSendFailed += new GsmCommMain.MessageErrorEventHandler(this.comm_MessageSendFailed); + } + + private void DisconnectEvents() + { + this.comm.MessageSendStarting -= new GsmCommMain.MessageEventHandler(this.comm_MessageSendStarting); + this.comm.MessageSendComplete -= new GsmCommMain.MessageEventHandler(this.comm_MessageSendComplete); + this.comm.MessageSendFailed -= new GsmCommMain.MessageErrorEventHandler(this.comm_MessageSendFailed); + } + + private string GetIdentityName() + { + return Thread.CurrentPrincipal.Identity.Name; + } + + /// + /// Determines how long the remoting object lives. + /// + /// Always null so that the object lives forever. + public override object InitializeLifetimeService() + { + return null; + } + + /// + /// Sends an SMS message. + /// + /// The message to send. + /// The destination (phone number) to which the message should be sent. + public void SendMessage(string message, string destination) + { + lock (this) + { + if (!this.disposed) + { + SmsSubmitPdu smsSubmitPdu = new SmsSubmitPdu(message, destination); + this.comm.SendMessage(smsSubmitPdu); + } + else + { + throw new ObjectDisposedException("SmsSender"); + } + } + } + + /// + /// Sends an SMS message. + /// + /// The message to send. + /// The destination (phone number) to which the message should be sent. + /// Specifies if the message should be sent as Unicode. + public void SendMessage(string message, string destination, bool unicode) + { + SmsSubmitPdu smsSubmitPdu; + lock (this) + { + if (!this.disposed) + { + if (!unicode) + { + smsSubmitPdu = new SmsSubmitPdu(message, destination); + } + else + { + smsSubmitPdu = new SmsSubmitPdu(message, destination, 8); + } + this.comm.SendMessage(smsSubmitPdu); + } + else + { + throw new ObjectDisposedException("SmsSender"); + } + } + } + + /// + /// Stops the SMS sender and releases its resources. + /// + public void Shutdown() + { + if (!this.disposed) + { + this.comm.Close(); + this.DisconnectEvents(); + this.disposed = true; + } + } + + /// + /// The event that occurs after a successful message transfer. + /// + public event MessageSendEventHandler MessageSendComplete; + + /// + /// The event that occurs after a failed message transfer. + /// + public event MessageSendErrorEventHandler MessageSendFailed; + + /// + /// The event that occurs immediately before transferring a new message. + /// + public event MessageSendEventHandler MessageSendStarting; + } +} \ No newline at end of file diff --git a/GSMCommServer/Server/SmsServer.cs b/GSMCommServer/Server/SmsServer.cs new file mode 100644 index 0000000..a85a158 --- /dev/null +++ b/GSMCommServer/Server/SmsServer.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections; +using System.Runtime.Remoting; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Tcp; + +/// +/// Implements a server for sending SMS messages remotely. +/// +/// +/// The server uses .NET remoting with a TCP channel to publish an object. +/// After starting, the server can be accessed by default at tcp://(servername):2000/SMSSender. +/// +namespace GsmComm.Server +{ + public class SmsServer : IDisposable + { + private SmsSender smsSender; + + private IChannel channel; + + private ObjRef objRefSmsSender; + + private int networkPort; + + private string uri; + + private bool isSecured; + + private AuthorizationModule authModule; + + private bool allowAnonymous; + + private string portName; + + private int baudRate; + + private int timeout; + + /// + /// Gets or sets whether anonymous users can connect when the server is secured. + /// + /// If this property is changed, while the server is running, the server must be restarted. + public bool AllowAnonymous + { + get + { + return this.allowAnonymous; + } + set + { + this.allowAnonymous = value; + } + } + + /// + /// Gets or sets the baud rate to use when communicating with the phone. + /// + /// If this property is changed, while the server is running, the server must be restarted. + public int BaudRate + { + get + { + return this.baudRate; + } + set + { + this.baudRate = value; + } + } + + /// + /// Gets or sets a value that indicates whether security is enabled for the server. + /// + /// + /// When security is enabled, only user identities authenticated by Windows are allowed to + /// connect to the server and the communication between server and client is encrypted. + /// Clients must also have security enabled to be able to connect to the server. + /// Additionally, access may be allowed or denied for specific users when in secure mode. + /// This is currently determined by the property that specifies whether + /// anonymous users can connect or not. + /// If this property is changed, while the server is running, the server must be restarted. + /// + public bool IsSecured + { + get + { + return this.isSecured; + } + set + { + this.isSecured = value; + } + } + + /// + /// Gets or sets the network port for the SMS server to listen for requests. + /// + /// If this property is changed, while the server is running, the server must be restarted. + public int NetworkPort + { + get + { + return this.networkPort; + } + set + { + this.networkPort = value; + } + } + + /// + /// Gets or sets the COM port where the phone is connected. + /// + /// If this property is changed, while the server is running, the server must be restarted. + public string PortName + { + get + { + return this.portName; + } + set + { + this.portName = value; + } + } + + /// + /// Gets or sets the timeout when communicating with the phone. + /// + /// If this property is changed, while the server is running, the server must be restarted. + public int Timeout + { + get + { + return this.timeout; + } + set + { + this.timeout = value; + } + } + + /// + /// Gets or sets the URI under which the SMS sender is available. + /// + /// If this property is changed, while the server is running, the server must be restarted. + public string Uri + { + get + { + return this.uri; + } + set + { + this.uri = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public SmsServer() + { + this.smsSender = null; + this.channel = null; + this.objRefSmsSender = null; + this.networkPort = 2000; + this.uri = "SMSSender"; + this.isSecured = false; + this.authModule = null; + this.allowAnonymous = false; + this.portName = "COM1"; + this.baudRate = 19200; + this.timeout = 300; + } + + private void ConnectEvents() + { + this.smsSender.MessageSendStarting += new MessageSendEventHandler(this.smsSender_MessageSendStarting); + this.smsSender.MessageSendComplete += new MessageSendEventHandler(this.smsSender_MessageSendComplete); + this.smsSender.MessageSendFailed += new MessageSendErrorEventHandler(this.smsSender_MessageSendFailed); + } + + private void DisconnectEvents() + { + this.smsSender.MessageSendStarting -= new MessageSendEventHandler(this.smsSender_MessageSendStarting); + this.smsSender.MessageSendComplete -= new MessageSendEventHandler(this.smsSender_MessageSendComplete); + this.smsSender.MessageSendFailed -= new MessageSendErrorEventHandler(this.smsSender_MessageSendFailed); + } + + /// + /// Disposes of the host. + /// + public void Dispose() + { + this.StopInternal(); + } + + /// + /// Finalizes the class. + /// + protected override void Finalize() + { + try + { + this.Dispose(); + } + finally + { + this.Finalize(); + } + } + + /// + /// Tells if the remoting server is currently running. + /// + /// true if the server is running, false otherwise. + public bool IsRunning() + { + return this.smsSender != null; + } + + private void smsSender_MessageSendComplete(object sender, MessageSendEventArgs e) + { + if (this.MessageSendComplete != null) + { + this.MessageSendComplete(this, e); + } + } + + private void smsSender_MessageSendFailed(object sender, MessageSendErrorEventArgs e) + { + if (this.MessageSendFailed != null) + { + this.MessageSendFailed(this, e); + } + } + + private void smsSender_MessageSendStarting(object sender, MessageSendEventArgs e) + { + if (this.MessageSendStarting != null) + { + this.MessageSendStarting(this, e); + } + } + + /// + /// Starts the server. + /// + /// Server is already running. + public void Start() + { + if (this.smsSender == null) + { + IDictionary hashtables = new Hashtable(); + hashtables["port"] = this.networkPort; + hashtables["name"] = string.Concat("SMSSenderTCPChannel", this.networkPort.ToString()); + if (this.isSecured) + { + hashtables["secure"] = "true"; + this.authModule = new AuthorizationModule(this.allowAnonymous); + } + TcpServerChannel tcpServerChannel = new TcpServerChannel(hashtables, null, this.authModule); + SmsSender smsSender = null; + ObjRef objRef = null; + try + { + ChannelServices.RegisterChannel(tcpServerChannel, this.isSecured); + try + { + smsSender = new SmsSender(this.portName, this.baudRate, this.timeout); + objRef = RemotingServices.Marshal(smsSender, this.uri); + } + catch (Exception exception) + { + ChannelServices.UnregisterChannel(tcpServerChannel); + throw; + } + } + catch (Exception exception1) + { + tcpServerChannel.StopListening(null); + throw; + } + this.channel = tcpServerChannel; + this.smsSender = smsSender; + this.objRefSmsSender = objRef; + this.ConnectEvents(); + return; + } + else + { + throw new InvalidOperationException("Server is already running."); + } + } + + /// + /// Stops the server. + /// + /// Server is not running. + public void Stop() + { + if (this.smsSender != null) + { + this.StopInternal(); + return; + } + else + { + throw new InvalidOperationException("Server is not running."); + } + } + + private void StopInternal() + { + if (this.smsSender != null) + { + try + { + RemotingServices.Disconnect(this.smsSender); + this.smsSender.Shutdown(); + this.DisconnectEvents(); + } + finally + { + this.objRefSmsSender = null; + this.smsSender = null; + } + } + if (this.channel != null) + { + try + { + ChannelServices.UnregisterChannel(this.channel); + } + finally + { + this.channel = null; + this.authModule = null; + } + } + } + + /// + /// The event that occurs after a successful message transfer. + /// + public event MessageSendEventHandler MessageSendComplete; + + /// + /// The event that occurs after a failed message transfer. + /// + public event MessageSendErrorEventHandler MessageSendFailed; + + /// + /// The event that occurs immediately before transferring a new message. + /// + public event MessageSendEventHandler MessageSendStarting; + } +} \ No newline at end of file diff --git a/GSMCommShared/CommException.cs b/GSMCommShared/CommException.cs new file mode 100644 index 0000000..f9835d7 --- /dev/null +++ b/GSMCommShared/CommException.cs @@ -0,0 +1,75 @@ +using System; +using System.Runtime.Serialization; + +/// +/// General exception that gets thrown upon a communication error with the device. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class CommException : ApplicationException + { + private string commTrace; + + /// + /// Gets the communication trace associated with the exception. + /// + public string CommTrace + { + get + { + return this.commTrace; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// A message that describes the error. + public CommException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error + /// message and a communication trace. + /// + /// A message that describes the error. + /// The communication that occurred right before the error. + public CommException(string message, string commTrace) : base(message) + { + this.commTrace = commTrace; + } + + /// + /// Initializes a new instance of the class with a specified error + /// message and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The exception that is the cause of the current exception. + public CommException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, a communication trace and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The communication that occurred right before the error. + /// The exception that is the cause of the current exception. + public CommException(string message, string commTrace, Exception innerException) : base(message, innerException) + { + this.commTrace = commTrace; + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected CommException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/GSMCommShared/GSMCommShared.csproj b/GSMCommShared/GSMCommShared.csproj new file mode 100644 index 0000000..4ef338f --- /dev/null +++ b/GSMCommShared/GSMCommShared.csproj @@ -0,0 +1,57 @@ + + + + {71ea4054-a98a-46ba-84fa-81652db72b72} + 2 + Debug + AnyCPU + GSMCommShared + Library + GsmComm + v4.0 + + + bin\Debug\ + true + DEBUG;TRACE + false + 4 + full + prompt + AnyCPU + + + bin\Release\ + false + TRACE + true + 4 + pdbonly + prompt + AnyCPU + + + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + + \ No newline at end of file diff --git a/GSMCommShared/GsmCommunication/CommException.cs b/GSMCommShared/GsmCommunication/CommException.cs new file mode 100644 index 0000000..f9835d7 --- /dev/null +++ b/GSMCommShared/GsmCommunication/CommException.cs @@ -0,0 +1,75 @@ +using System; +using System.Runtime.Serialization; + +/// +/// General exception that gets thrown upon a communication error with the device. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class CommException : ApplicationException + { + private string commTrace; + + /// + /// Gets the communication trace associated with the exception. + /// + public string CommTrace + { + get + { + return this.commTrace; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// A message that describes the error. + public CommException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error + /// message and a communication trace. + /// + /// A message that describes the error. + /// The communication that occurred right before the error. + public CommException(string message, string commTrace) : base(message) + { + this.commTrace = commTrace; + } + + /// + /// Initializes a new instance of the class with a specified error + /// message and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The exception that is the cause of the current exception. + public CommException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, a communication trace and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The communication that occurred right before the error. + /// The exception that is the cause of the current exception. + public CommException(string message, string commTrace, Exception innerException) : base(message, innerException) + { + this.commTrace = commTrace; + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected CommException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/GSMCommShared/GsmCommunication/MessageServiceErrorException.cs b/GSMCommShared/GsmCommunication/MessageServiceErrorException.cs new file mode 100644 index 0000000..203cb47 --- /dev/null +++ b/GSMCommShared/GsmCommunication/MessageServiceErrorException.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.Serialization; + +/// +/// The exception that gets thrown when there is an error with the message service. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class MessageServiceErrorException : CommException + { + private int errorCode; + + /// + /// Gets the error code reported by the network. + /// + public int ErrorCode + { + get + { + return this.errorCode; + } + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code and a communication trace. + /// + /// A message that describes the error. + /// The error code reported by the network. + /// The communication that occurred right before the error. + /// This exception gets thrown when an action can not be executed due to a message service error. + public MessageServiceErrorException(string message, int errorCode, string commTrace) : base(message, commTrace) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code, a communication trace and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The error code reported by the network. + /// The communication that occurred right before the error. + /// The exception that is the cause of the current exception. + /// This exception gets thrown when an action can not be executed due to a message service error. + public MessageServiceErrorException(string message, int errorCode, string commTrace, Exception innerException) : base(message, commTrace, innerException) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected MessageServiceErrorException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/GSMCommShared/GsmCommunication/MobileEquipmentErrorException.cs b/GSMCommShared/GsmCommunication/MobileEquipmentErrorException.cs new file mode 100644 index 0000000..c9e0614 --- /dev/null +++ b/GSMCommShared/GsmCommunication/MobileEquipmentErrorException.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.Serialization; + +/// +/// The exception that gets thrown when the device detects an internal error. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class MobileEquipmentErrorException : CommException + { + private int errorCode; + + /// + /// Gets the error code reported by the device. + /// + public int ErrorCode + { + get + { + return this.errorCode; + } + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code and a communication trace. + /// + /// A message that describes the error. + /// The error code reported by the device. + /// The communication that occurred right before the error. + /// This exception gets thrown when an action can not be executed due to an internal device error. + public MobileEquipmentErrorException(string message, int errorCode, string commTrace) : base(message, commTrace) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code, a communication trace and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The error code reported by the device. + /// The communication that occurred right before the error. + /// The exception that is the cause of the current exception. + /// This exception gets thrown when an action can not be executed due to an internal device error. + public MobileEquipmentErrorException(string message, int errorCode, string commTrace, Exception innerException) : base(message, commTrace, innerException) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected MobileEquipmentErrorException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/GSMCommShared/Interfaces/ISmsSender.cs b/GSMCommShared/Interfaces/ISmsSender.cs new file mode 100644 index 0000000..338d0c4 --- /dev/null +++ b/GSMCommShared/Interfaces/ISmsSender.cs @@ -0,0 +1,25 @@ +using System; + +/// +/// Defines the interface for an SMS Sender. +/// +namespace GsmComm.Interfaces +{ + public interface ISmsSender + { + /// + /// Sends an SMS message. + /// + /// The message to send. + /// The destination (phone number) to which the message should be sent. + void SendMessage(string message, string destination); + + /// + /// Sends an SMS message. + /// + /// The message to send. + /// The destination (phone number) to which the message should be sent. + /// Specifies if the message should be sent as Unicode. + void SendMessage(string message, string destination, bool unicode); + } +} \ No newline at end of file diff --git a/GSMCommShared/MessageServiceErrorException.cs b/GSMCommShared/MessageServiceErrorException.cs new file mode 100644 index 0000000..203cb47 --- /dev/null +++ b/GSMCommShared/MessageServiceErrorException.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.Serialization; + +/// +/// The exception that gets thrown when there is an error with the message service. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class MessageServiceErrorException : CommException + { + private int errorCode; + + /// + /// Gets the error code reported by the network. + /// + public int ErrorCode + { + get + { + return this.errorCode; + } + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code and a communication trace. + /// + /// A message that describes the error. + /// The error code reported by the network. + /// The communication that occurred right before the error. + /// This exception gets thrown when an action can not be executed due to a message service error. + public MessageServiceErrorException(string message, int errorCode, string commTrace) : base(message, commTrace) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code, a communication trace and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The error code reported by the network. + /// The communication that occurred right before the error. + /// The exception that is the cause of the current exception. + /// This exception gets thrown when an action can not be executed due to a message service error. + public MessageServiceErrorException(string message, int errorCode, string commTrace, Exception innerException) : base(message, commTrace, innerException) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected MessageServiceErrorException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/GSMCommShared/MobileEquipmentErrorException.cs b/GSMCommShared/MobileEquipmentErrorException.cs new file mode 100644 index 0000000..c9e0614 --- /dev/null +++ b/GSMCommShared/MobileEquipmentErrorException.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.Serialization; + +/// +/// The exception that gets thrown when the device detects an internal error. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class MobileEquipmentErrorException : CommException + { + private int errorCode; + + /// + /// Gets the error code reported by the device. + /// + public int ErrorCode + { + get + { + return this.errorCode; + } + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code and a communication trace. + /// + /// A message that describes the error. + /// The error code reported by the device. + /// The communication that occurred right before the error. + /// This exception gets thrown when an action can not be executed due to an internal device error. + public MobileEquipmentErrorException(string message, int errorCode, string commTrace) : base(message, commTrace) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with a specified error + /// message, an error code, a communication trace and a reference to the inner exception that is the cause of this exception. + /// + /// A message that describes the error. + /// The error code reported by the device. + /// The communication that occurred right before the error. + /// The exception that is the cause of the current exception. + /// This exception gets thrown when an action can not be executed due to an internal device error. + public MobileEquipmentErrorException(string message, int errorCode, string commTrace, Exception innerException) : base(message, commTrace, innerException) + { + this.errorCode = errorCode; + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected MobileEquipmentErrorException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/GSMCommShared/Properties/AssemblyInfo.cs b/GSMCommShared/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..543a3bf --- /dev/null +++ b/GSMCommShared/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("Stefan Mayr")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCopyright("Copyright © 2004-2011 Stefan Mayr")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyFileVersion("1.21.0.0")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyTitle("GSMComm Shared Implementations")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("1.21.0.0")] +[assembly: CompilationRelaxations(8)] +[assembly: ComVisible(false)] +[assembly: Guid("c43ba0b1-3ffa-4b02-8fbd-2d5997e9b5c5")] +[assembly: RuntimeCompatibility(WrapNonExceptionThrows=true)] diff --git a/GSMCommunication/GSMCommunication.csproj b/GSMCommunication/GSMCommunication.csproj new file mode 100644 index 0000000..e7d0d86 --- /dev/null +++ b/GSMCommunication/GSMCommunication.csproj @@ -0,0 +1,250 @@ + + + + {32b76f1e-b022-47c6-86ec-28639d348067} + 2 + Debug + AnyCPU + GSMCommunication + Library + GsmComm + v4.0 + + + bin\Debug\ + true + DEBUG;TRACE + false + 4 + full + prompt + AnyCPU + + + bin\Release\ + false + TRACE + true + 4 + pdbonly + prompt + AnyCPU + + + + .\GSMCommunicationReferences\PDUConverter.dll + + + + .\GSMCommunicationReferences\GSMCommShared.dll + + + + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + + \ No newline at end of file diff --git a/GSMCommunication/GSMCommunicationReferences/GSMCommShared.dll b/GSMCommunication/GSMCommunicationReferences/GSMCommShared.dll new file mode 100644 index 0000000000000000000000000000000000000000..09feab41b0a4751223d1c2068a9486088c30699f GIT binary patch literal 16384 zcmeHNYitzP6+W}OUe*S-wrd~|M8?>>G|4=PC!K zj&qeg{hB4GO{33Ll5$*0r3_n+sj`_#$y!Qo-4K9v%!oKb$oJe$2bax3+F-!AgqfC6X&PyilP1<*9;LTUJUR?jxk7u91 ze(kws>0aZ5Yxg$}wSV+s$y0|nul&)HPoJ&!UrLuXZaW>De)3Yq&))9dKK$I!*{A;a z>?_Cb8a!71&-s0q*Zpwk^TXA<4t)CVleO#ZufKcb_4js%Jx90ozjc0S#!F|;l`PS5tS@n>2y54MZ#QA+!L#Q@XUt1q;6p|v+L9fELYClmAvU*YUqqeD~`Yd4~J~W7L4ON??6hKED z{j1tHM_O^e1AGOtt6HraG4P^+m9|cMM&f7u$-H`+4gus9xQcKgJjbZS9k9?;M<(Rr z61LfFOPsr(UZj)cr#~?DNmtOH&h*F93CT~Fq&GnS!L-~X`eCN)n8umzXL^F^Z#^%2 z{Pbt0!z`KN6?oV2>iYeZZ<7o8F;yrmSbKQLE~jk0XGHJ7c;k? zIWMY{=p4-eT_vq_C_g2s5>i9*ZO*E4_a+b~D9?WrT zfkV2$Po_NueshtBM2aM4f(BaH_fjS&8IhkWuoo{=q1;PZ$x)7WtU$X8eJ_yB+vg>| z|B2Tk63-yaw2^5u(+JZ|Oz&f=Fx8mu1ig<2xc?Z_BTSEheu++i#xauwEoQl(;%SQK z7Q%BZmO^+cn?UabRw1flx`g(kU(fw7&{6bTxF2P@olZmYFy4a@S@c)Xy?7fiEAib@ zth(Z@S}H9E-70N%s0Z(~7uqS%doU{|k?)|uavJpFPSi(Vfg=`#T$AX#BmEVfMk1rD zE2%x&*GBc|zU+s=%ZPO2$;j5cY-4O7=*6dkZ^HA&6s zwC;q>hY$%mV^AC^^_a+xLUG$LslbXOgt{{+TT7}TWTPp%wkMn7C@zxfH3*BQswA;) zwiqZFA7J}b`yo+qHhR9Nih5e`t(q-3f9CSNWVU`vsFtmzvI7cqgpZa=sRd|gV(7Rt zVz%a7##>a=0?tT@dK44stih*q^%28-xY@=EAIsRP15>D5?bX#dgWqgfYBHt|^k}xO z<_$;0t*R9_HGY8Ol_&uv&@bJp(d5W zVn%Xo$>?^*EJ%I~FgzoTv@^eWT5i!SEml}>dqzu)&&PpuykZUJp<=GaD`+DUv->FzA=PpdODu=#wQ0EQIC7lUOS59;tk4Ss8)_y$dPm@dwx; z5DdsZDHwn$Q~{_0!rdcC021loGgOUb zhU!)|%{X5C?d~awt*?8s`*OM>({9m)4pEm7RZ50n*ee(CS8miY{JAPsF zJtgm5w12Yc;^xqi@{k`TO~gei{Fy zJoD?p;)<@{b!=VoK-fH8mfHTctA9B&_?kXE)GpDiP*+<|?%lZCd5m8aTP2Blf)zPc zt7fHjWuOahQK|5ea~ip!N}@T=6Ops)p&Uu3byciU+2@K4kXTu%RHDgITkTa+a;Gw2 z5-pHuZf>H>ue~7G)z&uD)YXQ=@+iAS5>>|=>SIc6EL>CH+pE+x#A@qmmi5LGHFb%l zjg9xJjj^TirPx(T6bOgv!r1HKcbWh=F)+;$fcJQ&Z#9*4myyaXbv^y2@rWh82=YUMQ?l?+1C?ceKT8iy;IRr#4E3=@?Qbrb8{a2v0ZkXi-3!Oi-3fECeDoGj{0Iv z;-8Zbj>d@BN}QvJwl(4GCAP6no9%5tZwp1~A<#DJMlV7es0(}qbPf8V4f#I!EW4}C z#+t?>5v^ivc6TfOA{Dk)FcP9FWFn4+^RbGUDe5JI&8m4%+ykuwsRg}4HtY;=*>=je z4?!j5WW!2>yl;X?+t{WSKci>=HBgLyQeTEWxrF__K}kyU7z3zC0jF|!5miMtVsx20 zaUZ}aMEy8NNV#!ZVPoNxA;wRS%C8-Hbvp5rh!BORs8yu|xCoCbY8K HO$hu41B{St literal 0 HcmV?d00001 diff --git a/GSMCommunication/GSMCommunicationReferences/PDUConverter.dll b/GSMCommunication/GSMCommunicationReferences/PDUConverter.dll new file mode 100644 index 0000000000000000000000000000000000000000..baee9ee4e7671b06176e72aabadf56426d3c58d6 GIT binary patch literal 65536 zcmeFad0>>))jxipWu93w2^kV5Aps`r!%PBM*i?`$K|q#30tN(QmVt~UnK&~+Rt1aH z))lo>Ev?q7)oNRp)~eNN)l%0M(YJN0T6bFey3}2Zzt1`MJ~K}e{Jw9$@Avop=PNDe zxo5fOo_p?o?j2mR=3-$8Ax!-J@=GD^#FzfeX86AbJHgHi-JK_X;eBG{o!a6jMmBcD zQ)NBL#D-)`S6N$2cXuLP)*35I_I8)WyUS{qHI#KF+G7)QbA4l!>iRk%7HfvM?#<7a z+NHfL^pQE5M~H)vCd9;aY;zj;wNC(m$8}{pkyd}oggA9# zGL>utE^U)_6E7e9_@1Z@{T%v8iII;fv?sBI6f!UH05ZPtW+E=Wic`R^ITC zAuK((10NF~oX6>ds+oG>4j(>|5&E_q`i|0#9h?BcMTDigzRe;*M#HGR6)C7RMF}d7 z({(^M28+R3I79`wcd(?jgL57aRg9YwpKnM_%ayJbR47@*g>nJ}vW*Fx0D-I|Nj6i6 zf=XSCMABU%J<1(k-FhA%=I0X>0WfK@(5N(J}&tfHnv zKI?)Js7j6`x~*^l9OjNBQMsN}A<|o+!zF5=1F;obM^#@PnQ~X>xLKWFQY=r<9UcND z9;ZU812m(TL0=dt%V()t1W^n4*ATx1AKp{t9eWj3;ljAj>Ku&}{*=vLqf*0WpL__` zlfS~1$e!y>Ov0xk$1VNSFG`@NS&?Ka>oMu~R@jMl4Ioe1`AAnSEWKbjeC@M>Nes81Ypf!s z=b!^YlkXa)`|#yf-52qIP;G* zIb+RGX9hoKHfZ$uB1WWI51)n>X0z-*jzKHnk$}#_5naQIp#G-l0v)`75nO??bk0+D zHHcM%^&lq_7*0MIx+1FUXc>4t4&^zXa>FcI;dW#w(1WtfqScu^4u0i>?r<0PKij6$ zPz>U50fWtj+sUmtZdrVt>Yn1{ADKQBy@UIKrzE*hCrM{N zu);kk(=EFqJj3%uxCk_=zahwmT-XpEMYXhy1Y83f(7imePtKQik#bm<>ISqrDn1D73w^PHc(!*hV(T8Cf_2E2x z;53?N&V;={F)xsaQIV5;6Gf%aJ$+k^;NH@S`N@w#kkqkEEy6@rGASo{wE;o&XN-rAytECKq8@G85O6H{@Kg6}mAy2lV3R zV5Jd^YDL9s^rB{(pZ%UX16<7bG$T?k7g2vOC%U*eC%T|0Ct90r!6R_NHQu7ZBGYsc zlbddkO({vvUB;WYVw7r z!=M~@u*$UDO+E)LTm^>Tlk2S`o9Qo&&gfv>FjE+hG~bvhOdgmsFN9*x`Nu$tXPV?s zfQMm-{o{fBP&~Plt zMNLIKSdro{EBCpJo3K1^_Uh-X$R$Scy7K&FA#^0?14x(XMreMb94f<8sASncPv~EE zPI4{fVCVe%CG!}iwGgW=_7cTwisjT|g*Twg;NHl@ERpvePFb^fjZ-=@m1J#VWSnx( z$(ccDU_DrZvgnT`SOmWktCrP^fRm-owHaaa(364}^C zMtBC21ujp(Rs9v_riPM2@}$0vV4?(gd9}*v_5|F{oQ_gBV}TX0I7h`FRQ51sM{mkH zL{?KOA2*Q8HJ98m#2rSy3uEcB273m3!-b&f0dK%Vvx>tRY8p$|pgUns3(p2Ou?E8E ziHsPDlL@m~Yx}U)Ia#OlVKozmxtBU5Z-zMm0yQLW$+=w_r9rROdW^xN?8#Zw+QC_A zPtLe0aj2wr6QsBhI4n>&jejFFK80?)9eW>xzMHS}f{XbBaiSido)ilOAYU@(ZS%E=JONzI0GzdJF9s$xb9 zu@cNhGL1>7G}q%z%mV^iEj|^mzL+=IJQBpzT`C*Nlc)ndBHO7%j69CEkg}}h_+nlN z=tYjQ=0iGL))2caI-5~hSmS-;?806dSQwp&s4jMSqh`qR=)841m8<&2zu+{rbHxaA zdu%M|(|cjOUOvZ=-(&PN;W-9>VA*E4#KSPm>u0MwaR!qJ)->Y0){KNNx=8PZ(R%q@ zLw=9d(}d?5{3$TKiB}qvoMbhZQXF%4)bEqyb7zOGVfQ@qKjWgsI8?Yv1 z$T9c>8)@8m7}n@TK6`&ej)w_o+alVAQrnKp9s_7z)I|kku$fc)w+cNN3HqYgR3;X{ zvEepkAsT`c2n)#7`qd(P&cgg9aH7N>V=`c2vD`Igk^jX(s%M*C zOh#`6BOcy2q7R7><-IUoyygPTi5l$^$LsFB=%>YNSeK`y?b7cPR@5Z%(4gi^zmFLk z`$gz_%1SNb)~9_a`YP+gmMf!ef^N8*{(Jh&jJBU~QF=++MPyX?M3f*YC_GD|l}^pw zrofOF!0kQo$$a#n>XcyVP`gOf$`i^DFGe;Tf-*ba;*)899;FvECo&g_B@i(}m5HUQ z1R*L#75YMrm@KAI*6 z-i-E?J51gl??CUO%_atky{$xl$?PE!#-jMv|6hB^pW4P8bq~o~Mz(J|nb&X27*)Gq zGWRQXkB)s}|IL?kHxD7>Rq;@kwYaTG2RAfr zoH#Kt8K5ZH4`&$R~4lPV7trpiY|fiO#!O>N0|_+04_+I!$2 zBv3H+QH44BF*w)Y^o(`RS5!S(PpsfXiKLW$i6Sa3D!8=so)&uB^iIpVi#d%;A60|4u75E1G7^|_o0r#ngay=D=0n>Xv zDxf8>TKL8VbBFt*rl)ANKY;n1jw;fY>MQKsgEX{yw`cgIyuNv{Y+=9TV_)V3Om|`p zuo6ZKasoL;Qq!URft+Hggpxyx0DRG+pvjP#* zo{}wa4aahYMY(8ofthi3z_h*Xfh)=10S_WU?(kZui@Mm|iFHT^Fq0R!18#TqO}0xt z)!*}?GbRwbp`n^MM}AXhsxObFWG3h~4&4huH}qIjckMtFOUB;NuTW<%&%GcHjJ+X3 zQsNyLdK-$++aEz!p*O_Zc5n#@y~#2VU6Z9?yby=R4HZsbE}i-`5$%^Ne&@ z8VLJT1dm#iaC~|0qnNH!_J)44glA+g)WbIc$6DJ)OPEJ0tCv)1WtfJDwx0t5Lg+_?3_SdXrDJn)hXz+4q;j&Z51x7mzJ7W2B?pYjHDypw}g21F0j znuj>vY(jQ$#SK==INOSG5x}t0i*}rbV&SEY^3#b=#J)vzWMV0!q-}WfAt`ELlXhqa zi^CC=n?1t`5ST%p;RO6*2-HEg^)^ygfoQLbjgq-bT2Ga^xauSH)ZNrF=m_`G7Y(LU zoq{{QK_#)itGW5*Aob`(1f)_5sQ z{0voEg^8!rLWeDd@2Fwohe#5u<=OB{;%Y+ReRdLgKa;rDZXiwwiiu*C;XnE|q6MpQ zc}a;-s1nBzl`hLrCspc2Hg$G}+FYp@+tgDt)Uzt}p^=hE_rVgFPG>gsbyCeMWcXCz z5tK|e%#$WS>T#G}UgpTLopNwMKiq;G*O9J>o~0%?U<`!Iz>-^{eM$%R&`&_u`6SP7 zb7msvklx5BTn+qDdMova`aVkEJM{1a`0A;T0C$jARlB}M)UqGL;1r@8R`^YlUZsbh zBD%au)XgwSMKHJqQgs*K$7h%>8F@A6O2V>pUZQg5nMdK;8ejtL50Ax&z=DGMlEMF0 z-1!*v3*p$Bqu*7nci=ceW0`gt2(E;WgYp6ksU~b@!ie%%v<~ylL!uSlugC-TW3&Zh zeOY`2QePToWld=$29 zO?v&A^`2n0vyK;Qubh#J2+1rtnvc60}obbql2f*XlPX{#h=1-I#^LX zjKpVz*{px{VI6U@zU;$7JUW|8BP63xCk&(TIvR!a%;b6+VvN#Y<}<=qovT>X+QC^V zrp>r1aj3-Dq&*$YD~sS}`eXEtLfy*qWOa%XU!a6&xq)3N*DojurO98zeFpu2#K%*H zV_HEmN-v%FaEm~Dwt&3yQXZqmE~+Wk!*3vwrUJu*Xogr1)Q85-HduKiS(^J*O&ABq z(S)h_uqMZ-7^>41QP>M*Zr$Sz{|yNltHTQpYQFHbpm1RsGKRO=htkK*e|KmI`}$|* z3lL}KOlje~zOtw$2)LD!3BKi4Cdi4mgzwMLB)ncv!FK|DC$FL^a5GH~iWq#)sGbQF zpG>y{Zsy1uSnLv8#|-E|6WBTiOC3^@8!aNWAM#1HInpswz%5mks{R3;fnd%-oZ93X zHP(+O2kmhBnb=R75|+`A!fHH4JBbv{WlmAklZ?LTbC+A5a11$Kpv{_$i|S&=@qAe= z#_2&L{GU`ss=q0`I)*;kULoH?MD@^opHbnJ&pL9Vx}GEFP3!o(P)9_GQz>*KzbE`2 zlw24;lVSK- zRlFwPT8(I!-%b^HqmwdhAfqw}&SR^qab9$-23c7L6$M_b00FO@D?K>1c@i5?>^LII zQyS0zv$i2Xj%%0>@Z>2xKe-p3Jn71_j~I$e?}PLLU-)fQv>+!~;13q$PX7_A7x1G$ z6kvW1)&@^w9djbU`5;UI&NF@JIqDZC((P7yN_yW0sf@=b~IFsfgDXTbZ z8C4fAbt-anGllC8tDrFI)6Eg7KXJN9)^bQ-NH9=%O<;)Z)23L4#x6%y={|}hJY!(l zRz0x`xk3wY*_*&9w?cVUOXZ6PTzTO2Ob9-_2J2a90S70Fkq2mT@*+te8p`7^g`$@v zFO_u1+Z8!dhT?+gI`EPo1ITlq$!;qKqjV4$T>z31s`MEp&es8aU7uqX51{#rDBj?~ z-=Zdp3V3Dwf-lN&0a##K``vVDCSwmK2^W~yV`M0l6?Zfsuz1L~UmoxgOL1Ko4`4W` zXa=cq%WwwTmM*Z+LI@Yo8Nnr9fo~CMW8dzjdPv`17{_V%CaN40$CAU#&K`n6M{6$L zLYrtw|o?h%!{c>OyH?12)rlPF5io**zNG(--oK?aYsz#!zvgQMjDb3wWZo*4&CK8l-p zY503_3GY7svm+Q?(s6Pi<)h{EgNcoh59IS4Yal|;TP$wx>BKZ%1@H;4V=Z#pZu)~s(lb`5eY?tWZ{40?0xl?}blApWf=hyhq8Jk6o z1ZG}4C_=gTV9yC`vM4_jH~|7_Vge^XAe~I$1PH91PNST_xOB#Hu0dIuxDR-`=tZ5Y zP-jFsia0PrMVG4x3YU*jxtbq``5=l_m7Yb3=nyMZ40&AkhVtp_vPN)rvMU>NA)~S} z7qaMRF_*KTu$pZ!ikdS~mw#s3N=99Dr&Em(;t*ACy_j8Wgz_0HlGq4FhDz_ND9sLT z13OB?E#U~bu&?Y^MAKLpK5*eZFqr$gE{=nZHPknlhr7fPRyY?@cvoxCMToD$gMdG0 zkT0HPmwPTbAz*hoG!F9-KcFvXkNn0AR$&{JfQE9vk!*&v+|?O_w*Bw;;j47i!J!U`ULIpl*m z7S6YZ6*VAVe6rtIRJ;Z^a!v9Q8L8D(`7%u$?EL&iIj$2pzJ^ER=x0`GO{s1y@`R5? z8k@ZA?KW%2@0^pU~tCOn_x~vs>x-VLQHX z6W&r|(!`r2e!}i}89Neu`WqdsXU_)qPXx+l%N!a{`$b#VL^9U+2)W$+pwlRY3j?pAI@E^Kdu5v@g^7;Rt%0 z{XSbyY@rcCXLmzj>f%2tLQNAeqSzQ?xUIw#i}V3bQ`}qM@q12Vent9WPSeZ_$p3X@0CO9EsVz@~t(YDHc*sElIXdng zDliMOe{sA`RZ;Ut7r6bNkbiZbh!}Mz9Q{OejGj7%rAFzg<2W%?PtD{+zMh)J3Adh_ zM+qzP3gWGJiAGl(vU&Y@i~&S8Jk~(!DQ!b>Kat02N+BY{W|JtHPg3+H3HmbsCmF?* z1y{^aB)eIF)3Tf8t1R}HFIE2?_~nop@ePf@i4Q&nFJ+@I=c7G4 z&DoeoG4J7I&wHV7K!5PGYB&F$C+bS>!d4|mZFs`F!Q`KvaJ|%PBtN*c_mN!JV3(}5 z;^w*$2Sk(m@QR7c4Gmh;89s*yqx7Qia_WY%KVVNLj%QmMr7cH-etM-a46=wrCC`B+ z(R|+H>EWdi371i+1bE$!|KF%f@|&>28{R{OqCA>YLiqNvTkiP22@yTK!nQMlca?5_ z&&#shWT7>a^2!dn(QO!Qiu%hsvzTm7*_g5^WkbqlOq~n;z8AgY&ioc9*o_?5gE?Z+ zBH^6{=aGGn07p7t^x~ADAM-Py#&wjWqu_SxI%vQX$j)y=Q;xe_cjFq0x)SIM`qQav zfxfUmow^$6i~7^4>w$h^e>!zV&=>coQ`ZE23DM7Edl00;bzbs3I9?t;TiHuRMXB?_ z52ofrbSM4zfv&4=!m!23aWqhT@Kl~YV~L4#2{Y|`^dcGvB**I+CqSSaOyC3voR=0_ zPLMxw-c#$HTG}XYW@)2*xI&jp8|AT=HtOc+F9!c6Zw%U_$VXd*Dteh&G%+Xn`%L}p zd7LX@(DbXxn%JuboAQx-En*@Phw-VRMR=klPfq#CAs|Z5Sh$IPj))5ZepP~xw8bUX ztGXQZw_JFGIVnH6k@RqULg@+O^$1iL_)!;LWLd>ccwO=&Sul>vkv~@5F`h*5JcPbM zi=!;$aLP$vBEtys2aS6d{Go*HXc>04QhdKvB;_gKAyZZ{M?GjCsEakoKb!h@h1;w! zakgAasd96OFIsDq6RpS>J=WlNu%>(sTV(JnY@9w#PH@lnMTbZs{H#;Tg)cyju!h@5 zTY9f-iVsoV9F)fgL?2#d2XjJYc&0@UigKr8YlBmc?v=k~3MLjn1wzDTG;}6@^no4p zrJkBRJS}>X9+UgV;m2V^?3b`C5F~?qRfpUaBR%0xbnOC{6)Kg&h$q^)UNpwNDBKmR zjq*jM7icunv5yZXPbr{Ct5xJVe-da&6(`S>q@f5;6}Shx9Aoe$0yal@!;P^^Kh9Kb`)x>i ziSDyP6Q&HeLcWP}tWe>^Sym{#ntyvnqltPzbdeRBG_leO%~)NB-ax;4pcm-$(wSc# z(JX6)ViSi)M~3{7&?%A7wn%74B(ydXIyn+LGZI=C32l#*-CQOF=7B=dqkd19EJj*h zRh6KkTV5(Z?!>xEZr9b>xUPXL}SnYcM%<2=yqlNEU{a-J?wIQTB2VMoF?t$~t;fUvr{* zk>A;`CZJ!r`8NUdBEN8w2+IEC5?8|3TH6Oc+Y{04x<@);co?g_mFp>=b~;{V{yTO$vJJ2^`#!V4!VJL=Gy^hzbC2?|USVbtMQY$@&K@Mf54@DZbpZCG7il4D__8c>_N_- zVBcd3WG6dhr15ydzsFNP2BH-qcXBVfRNq!{WZ)$gbN`Rp38#qTj6 zJtIEBud071UKf`c&Ek6FQsaBZjmFi+4aSd*%Z(oxrQ(2iL%f9DQ5m8VqwtdSTD;I2 zhgT|L-26sx`&o|Ln2Cs{AMKs}u{cG}z|Q%2tmZ$&YA`qR>iz`$`y2~#r+=bYjMs2W z#WMVsX1Q2_f4Q;|yT4W9B)s8VjWz2b@h9=5ctQMF+=sceRkUF*9>aRPL3D^X&Z!$k zCw>{Ux z?01(+CHvi^?x^a;+=fMSHM&N^9~>~mrpXg4C)QNfR3Vr|JCRPncM-@MjU(Dx&~7P5 zx*?s6cWA z?$pI+V{2$>qtfT#?+pA!lZkEki{mrxgZMXzkK-G_U&?~{g#!9B`nJ)QcyH{V$AUi2H_j3@4C@&t7@o`UhYasw_-BUij-$Nhc!Gn+Q{E`Uj`0h| zTjE@Xw~wb%?q~Qrh6fqG$M6WlMFN%F_Qim@L#p0T@GO9^Hf;{AaoOE9|-_7qE& zqJI2gZk1%$Su^#~QVvG6`uw2aP;33~XfVE?dc#5&lH1hQgSSv_No=#eRg87sr zM*~|deh!SvI|i6b+#@Uq=8FZub^~*3yHG+i^X|pJEkwKyg$>mx-!{gcz;95=XXj(6 zda>G6F?J zBCKs-PuvDc!s5((h$SazDGjK29y~Anu${4Y7^?z??k4_>ap@9MfFZglreQ(K71I>9 z1GBEgmhX(4MPm8Rv>6hscAkm!s-5QpVoZUei5txcfWPGQT@3GK__$dP`hT$eAKWDQ ztoshY!+^_o9`6YOE@yZepeerLc>>(wUXq;1aGsYmH+u6>ky9CVdkc{6^%iLXvC@AZ z(pUOP{#Jih@~&Je_x@a>Kg#eR!#^{8C6_FJ8*tT5X;tYUl8iDu25|Y#5Az9r$?St0ptJj00$rvhqXM&aWak_#C&GwcO~ zKfztK^8$ufvgA*hdmE?k0JQBqr|>1nzryfchMzL@4k3CW!;uWf1YXM2?wrEI;8qMF zcv~L9s{#byW%@LxA7siHz~wt<4I%qiGu#MB{qsZJlxm` z@Ii*pF?@%i86^2ah7%c{$gqRqHij27{3*jn86IZnE26w37*1k1li^B+8yW5bB-^NO zUs5z0@>epvjp1(@jtPu5G||oD%U4V-GPsz=&WPd;BOvw+y#sfzcMmORZMk%YUbov|Y*YDu&lG zypiG07~anCr`#* zjSPRu@V5+~VE8PxfRe1ze13}0jT4#R&jbcIOL&#-`D381ZKWawOQk7L-tFvf5T!;2XHh~cdae+y{$ znI}Vgk@t}i1fOH*Vv3((0mBkNyR272--G1)3_oW$Ch$E6_iE-|ZDMgLqn65HSW-rH zAIjJG-*>%D8(vs2dqFyI4Yz4IGsnH*T zekQ{U0SmOoh~+8JzE}P&AFZ}R9B{2Q1JHS}jIiCsubZ@^{F1Rfp1+%ufw@OA&-0}@ z9auhNdprfMnb@ZmD{Q=L4zO~CO>@-&TOl!Vf@={@B&`Zt<*LUDpH`AFXmN|}3QN11 zfSseTZ-D0!7bxs<@H}Fl!hQ;#SNuR>_q)~slYb86KH+M0U~jtC1G_;=3e9SzNPxl$ zt#!bLjiQ`;#5l$}6*kM71K#-xTWf6uRzF%wc3Y?89l>uT=Gh9#T=BHR&a>)(xyG=h zXTLQWuLXuE?5B(ohdh6;wHdrI%sU|Ng%)~qK!0SbV0X{F-Qs|?13dbXGTKNyW9<=x z#m$WE6K`AR?2mi*#(+V2{UWr&l zhY^%e174{(Nn+e5N{N9#2V4)h*MZl}JZUw~4~*@Bl3 z10tv}zlX3g#tsP5IYM+P-o>6Lfo*4OuejRtXJ95`7DDV3Kli*SLSkPAdjoNrs~MAh zcZ|3}@gDH(MGWX)8SI}p%Z$P9lC(Sp-gvQDVXt{U#Tn~zg}v?hCt^L@!2Xpyajti?HdV}F?0|=Q z>r}B+VboivinU7e3U4dk_{0_V6YqLpXDIB~;7u3174`^t)5RqU`?Gf@-oad{u(!QU zz<#7K%{LQa?3)x;;+q5PeuYi))d71$Vxrc!2q)5y6tBq_Ky>Sf!a95tG>lw4PNo`d z2Jd(=NMYXw?|4zHu>HPDZI&og*pGcvfK5=?ZNB5Q*`i8ekND;QJC-r(S)0WiF<)Uc zOU)JajO`O^bDFie;$4NEp0h!lCzc~PL~Xk#r(3JVe|m+mz2erKv{om|arHsiA9K#p z=8KL>!uE;3=3K6wD4tf>M>#*z7K>Vn@`E?X|5I&=FmWeJ`NjZSCTbbmCuaD6sVx_$ zFecl5h3HVcX8(iQ3URH%Hu@jd8pO*A+vI;jYZR3=vV=|kr*P)nk-?tVn#9ctJInvF zwpzTWuyg%yXlul03ft@dyLPfzNH-GPvns_pQOVfe;)4@(&pNTZ4{vt{8>X!j*Wr1d z?1S%Vr;6JYM*Zwmai7AdpPee6P}oEMRzxG8Q`n0Lb+w4M7?XXsReY#0>btGNm?}&7 z1o_%TzQX*u>w%3>SXu5&oV~{>?6}-Hz-B0HMQ$CiR)tZoi;2?|M!ha3QjEzSvR<5} zFzO*2#5biRMx3c{5SM4LjfkNR!*e;RyDx7h;;Ca4R?Jwr!bUMRSz#57%~05^ymiRe zBQc({;^GR0Ey(lfad87+thilb zo?k&SB{~&GS`bB67->m~?TpDeIVEmk?62Mv2HgkVoeH~T5J}#zu;&?jn6X_v52nOH z#`cLZfdzU>{7qpMfhBrcm=wyz7@H1WuNbGWg}^q6*$O)el4poD3flnQX3B&=2XZg= z+@^04+nFaSx)hWMlCL(3>&qJ=F!X{wfm0CQz^6u2PimMottHm}uA9Ow`wuu`Y zIx{@hX=767Ht`@!?t{*U^zGs?#-z?2;wi@D%&~Huh0*vsQ=qth0*xiC2AE$<8PO! zXY7C=FP$x}X6zwBUOHQRDkZu7z9E8m0wjA+llX?HlbGjj{0N} z5vM5Z5P0W^4vBGjd&K38kve)eu?8Y7~|L?^-d3F*&neCzdLVde(Jft&*gkb-joy zjC$7f;tYj-vv?-r&u1y@n&LUYE>qZD#dW~$mY6tLya@5uM-}gl;){%*h^H0yY4LY} zmDf@oWD}Ke8T#N4&f9=EbFCi@?*(B~2rHs*BaGU-cF+%b@)MCF8;}k|M_8T!t zVGotOZQLiODeNyL?*W^wFlx>F#R7#jnci61j2>-UhjGlP9${7!r+F`f$!2=_Af zgm9JW<^fU2*e($%bpxwqOqTwLc!9CKkQ`(_B0B3SAC0OZ=A$B=!G@cUiR~F|jQP0O zt+3v_sQH9AUt;3e(i-y*qJ24Q5zBxb5}zonrSy37Ph$TH;$13wN>4PO7ybq*`R&r> z=3hjU!iKn?&|VU0h1CFiSzOGRtovVuuaWZY@eCT)DqazT6*hd>dSImri-Py67_G3G z;Jqr!6}Ak#*TiInwSf1Un5nQ7c(02(g`ET5>tdQ*=9c{}exvnCE?z_kp-xVPxkA;%AP0ABZ~~`92T_ z9J~+3;~Cy{=7(bcNnBgh?`HF3QQRc!UORFo-aV8lYzbrZFF=xCPGYQ5Va<$9S6ByQ zvlW(NY=Oeg961>}>lLiekxv&JU+*LDu%75(sy}&i+rDn z%NW}uUSaHhh5d8n?dE4<@*0wSLpw6^PGH`X36rzw=VBCNa;E)U%*tR*;&ZW-F}bt) zLM)d&K3#tyRx6BVzAr?p!f4m^FL9c}XxH^GLH`gKd5KQfN5nRT(dqh#*v**SJ$)%2 zVoc71Uy4C%IUm~L0aIx88EhkNEFX}V=c-XNg`qvFFgi^b+S825-L|0>t|KjPXg?eE zh-qkB8I!ejX}cNQCk~8y(sXH;pF)y^?G+yF7RL69r$#+%dbI`3#M>+WKI%o&r#-{i zK4FaBD{{2gGT5u8Uwi9RlH4Z>!OPPwZjspd(JyO*w8gCwJ9hLFTE14?PMBAz?M!Z-!C^4Kg?h`|_XB0*!jUn303Zs+85bZ6-WZj2olVVhYJZTKkrYVe0 z8bh>M3Zs(-{ace1ozKD_jHZ_l*N@gg{{bMSvs|a-i%h?A6v@Btka?HWGOrk;BUCBzYa6TaZZ4W?0oZOE>I;Cck z>0qv;&~Lu+t5T*MJ#BDg5V8A=q?4kFf#o`VGB9oXlk6GDl@#f*v)L-rFH6zHUQQF# z#diS>aV?-J?trgc;=T;mmVA;W|HO1>TI!)1Y2tZ?f5|}U4MV)jl*0_Aq=ZD##ow9! z@7CQ}ySs9!^mkd0Gc8LT2=UJipxh{+A&vpGTVLw@jP*!3kRr9oe&S3^ij*G+rQ{LT z?Bq&{K9+3w@AN&Hx7$nd5@tOq`xRbNL7OfDtTUT7aMw<`oRl#~l`(~+|GO@k$()S9}O$&_VmXA7WRFF9JfnNFkF_MX$X1JkkfD|dnW5V z8_*DYm?F6sa9To{>msH|pY-y4LbH&m>h)jINxwFdK{K1KyiF8mT2nFu zYw_QqT*B5$%Qf7g;N8-{3GEtD-tR!0F2=Klt5~zN;j8+*@)*zFa&DCE^-I?CE7m~$ zRu^}Tr8)eO3|8;TM9cKT>z*}(l%9$uzb<5q5pun>!%jCdf${Z9w1 zL!^n|E{5M?xR>D-41dV*MuxXCybF*{VwCF(3srcLY$6W96-H}(L8>GYH#&#*{s^!p z{5jyq<4ubwgRG(23E?q-pLrtINX|Rd z&5d08YL;0ou9?oi!hg88Xm?I{4(Zp;w~!`};oVl@r-1ibUx5DbgkI}gs7s~T zt9?6??< zjoRPF*FffVvk@{+fxA%rC9=lzTWC1X^Q1N+dWk0>Dx=>6JU03x&tWk;`nk(17Dn&) z{8c+G`V?SK^f}LAF7Yrlzlk(yKCGQS;Um|r+I7(hUMl4=S0nqRQT#O8;9V|`jNI;B z&F#Ki+z~w&l;1@s&qZlg`JcS+ak=lowm0w! z?AYk}i22uI|KB2(Fg%H2Gs6ysDZu3-RQ{g#R&7H0htO70Zu+RD2Kidh1B(E03kG;h z`8eNuuruQGVvj$G>6dyn{m97UeAH6&eV>Y0`6SM}m|>gGgBh*_^P2~^eIC5_D*?5K+n|F@^UdEJb8Qvgf>n=oz<^tX#Y5{*C<^$ft+=rO^uvi1i z<6UHu@qfY*5(hCguO&`3t7s$$IW#Ywu@kA7X1CH|E3IgT@7r zeA>7O@EOkg0_S>};cHyhTU_qj#;dwVd|>P|J>nCC+UN^|S_IF}0Zo(I%VSb|`Auq( zLX&$JVvHD%h$dn@n$(}eOfP5pcBbz%$#1(&^8dLe`Tts^J>pK*d=G2BpEW(MEXQdpUkjLl!KBI zt8yMRpEAmgR~Z^EO3!m|Lr>F08BWs2VP8Z4pCR_Ue(3s{>ps^3*CE%duD`kHA0ZfG z4YMk(dDe+mqZPAytZmj=)-~4c*1xQJ_eJiT-1oX4aX;yP$^D=158Z{HV?1r19?vGv zPR~uA1D-#4Uh=%{8RDJjUF==s-RSM{Zt|Y(J>Pr1_jd2S-iN&hy)SrQ@&3*Gnb+r= z?3?Xd;cNEAeJS5|A9kzos~>x(Jj~#O@P?)Y*eJZ084n8nZ4J!lh4`V}5bUvn*k29B zyM$#B%73^V%}{EB9*SOGsZ;#EcyevPvR@6J!gFLKr*=Q`v(6**hr<&D@?B%tAR zcw(;mD!>))9|5+ye+*ctQF@zBu$$>S+&3Y;$9)UnewMkBbKSu(sgy6gv>Hk4|yfT6Q4Yll+a)KIF+G5R%-lp3Ubp8hc? z19Sar7-@44Cpd)RXogh`XEB`5a0$cxBdC-c7~VR9a-T!sY`LIGSN(6!o!VMo}G?j=C48t`<&9uMX@df8bvCRc+V5rRX$6 zW5)!fa~}Oh#Et*y7)frVbFjU8X>LV$C`NaUIesEc`+t2B(o(MZ=}G$Qf?sEZRYhBf@s zZ9LKo@!JskS8Su8oCrw&1;YxYmolu!c-8SMxJjU_z?jvsb0pY+af`S0fI5EVHU*GI zF#dC@fI3FoB^d>+Zzl58G^cL8w<2P}0klqG+b^JDt;7-^} zzpOa{@NC$qi*LX>oo=)NFMvfl?unNJUIbfoafw(7cqx84qhmk08gL)x8pO3Q)9B)I z(F}MsW*bBy@U}(A&0`ED-hx*sI({v(0qNUtKGemn3~$Grq+#cYh%o*;$z4d_!SEi` zQWw8wcrR+H;lA&5P!6D$I(DeNpgatyV_$m);IpWoE}lcpaGnL!u{+&?^j`pV@giOa zXyPS6UA&CCYB)#kLi!cdRTr;{b3l0wP{+N{H<5l5P{*$ITS$K<&O`cBhM)8Ae!gIM z1pd$w8@~uLLfeb?0ZIr^$Nu(vNRMPV zTKhgIqZp3St^$25!|~b=K_AC3j9FR}69CaCwd(*UX+H+6)@}r>(QX2qto;;liuQBB zY1*xT)3w_HkJWw&I79mt;PKjBfU~t<1J2Rz1)Qth2Ux597Vre^LBIvt?*JEK&xwAE zJ*qBF#GY2izWNEkCE6bWmugP}F4LX@T%r93;A-v9fNQj8Vdu#V*J;m#vKA2UdbPhG zeG0>N+!yL1ru`MLS9=w3llD5`8QPnGTeQP~TeZIfZqxpOaa*pHXnCmhFu+P}gf>mQ zua^LRpbrE5NFSm3#6{MF)}O4mtRe1F_h|Pq?$g{`-MifRlksn`VOpkN5t=(`3orz8 z5^n{>XXf?eDH@yoqX~o2zBKtuH7D(J?E^$Z>6eXzFoO)j`_n;~F@l&wg7^*DSd99y zctbfB<9;mG@v#`0$70RqXFoHRB-(pBW5`bSycsy|Ys+Y>aosQt6hi z9&tjdYhI$OYhrzSZ$?Hmbftich285D$*z`kJked(8S9F5r;jQ=v7xIanO+i0rCK)N zhnb>2kxb8NZ%=}tvQYm(k*l2>8_R@u@I)WwWMR+Y%7^9Bt;q(Q)WyT^~pp!(U$1M z4@8$H7IwoQXTSam*Kxvxa*|H&(sZ1YY*0?@o<%z~EJuyLG z+4`luU9G5*{6KYmXG?dc9Oa)AyAx-0D?QRZLVo6*fuc%lN>x5f3#rl!a0`+M2lmKk$ioul&yW8Vv zg+x-+wxnC;CEBUE+d9y3#0jzPShA&4(rnXDYKf=G^;u~#r?aP{r8Nev$|9K~Wh-!q zDgmWLm5rWi=TwZ^c&e=>+1`)Z04rlhqpfU9RoirB?TT0`mfRF;m*@$}L~jo{E<1#P zID2p(U#{%lNG*Akkt91PF);pmT9W7yjj_#XSwl5gkJ^hdnEG`}?pyW%Q^hl_E7g`r zcE(#p!q%3qA-SxLQ%hPlQ*{={x;Lad1hAFe@ivTP2QdZn8kfxx^i`uo z)MI4U&TA08@Hx3c<+oAM28+_k-ZoqDMC#pQLoD44fA-;|jzS|jw3b~O*S;T3P(Fu@ zXo#hqC~EFRDYs)%q0A1HxY;hN#sbL%x9i}%FgW7VdcAbu?1}wQ$lf1h@~adE*0Imt2N#o>yzcEL^;bOW73iabWq!l1udImgl9U9 z+MCWv#^!WlyGFZew!&U*I%1n^G26hbg{cLx&1ztAn(4HUZcKn9@VAk+*kW$HTPBzV zEHhzYOgcxKJ&$F{yplNgRx4ts_hM>`wR3kpI){1@CZ^6U^)2a+y3O#h9AW)wBxg^0 z*&?|^9G%5w4$Pr8=5j8~8rZ+Mff>1|fn?Dt4>fUY4d=&Wo$ca`csuq`9kKX^j?GDmsRc?al4$!AvdW1` z+Z555P-^6-F_ArYWl|ZLL{D?w=AO=YTO8|BPxC_TJ2;&!UKXv2?OP-lN9FeKjzr^y zQT5xD-0}4$F?Xj`m+haYul&3|vPpZ|S9#O4^T_CK>mz%#19HC;@5F)U&xuE?#{WQ7 z-`e~S6!mq)|E8k;zW(1-^M9mLeJj)*KV#YY^;j`PCehuiDA+KoRH{uS?6VzavrO^? zTJz+QkIzV04H|l|jINKjVd{lsUmW6n2r|zmI$bq4(i#(Fvu-7!&@?82C@=NOAR&#j8L#}b&`adM>Xedb%=P+z;$ zCeCvr^$m>~#=-^&t!#_qgfFXGS(i=Dw|8O-TDsdivFmJ_Tv<7{B^7J82^=x$j-~6C zw8T3z8LMit$;sK|lx%WpHaX4VTdaQ>KUX`_HIDRTM|z4QJ=Kw(Mlq@-iPm^0wu2lG z5)B<#Zq@LnNw*Rf^mMkg#riXs6=Kz6YBms^DCBRs2J!Z$0;9*>l*+mQG2+ znm0|P5xwc|iSy*%h%{~d=u30yQqT#fFVX1RM~_&l6bco+vj?rfVI|d;j8n{w<4fJW zT?m4;i8-yU$=Iei4wLOXR^%|NPsTTa4r{ARCKCwGQBFkvx?opVB26<=Tdch|8DqBOa15yjencq9 zaXyqx7}+gjLUx&Rw`6pp)Mc&d792wgRh&?O$O@b$Gv_x2+8)Vs5`gvL(5Nc@$Z}07s0L>}=`AmPR%SHC|SXY#|{U44EMC z!W0LJuGmIWZ_< z-k$fNlf0uZIXC=AXR)S%Ip{2U6e{WH+iN&~Kaz?hWJ7-{E_4)y11fy9$_~uOoB4rc z#1-s#Kiyy~MKlbXC$;UAkt})2qBELWqC{O+PkPI;9z@I$(^;I@fZGg?dHok8JG&IN zkBRDVDv1;zQZ1n9E4x#2bAxeaNBS`{rV^dKm;~#(J6aGO!8BFhyrP!is`~lOPAcJ< ze2SmR4!mK-3iyELC`ZTY+xgUGL{gsZ26UdjO{s*bYaW#W)xDS@5t3=>!L>?bB9oMR zripwQmgm;Fz41=ETbk3;6YFkYxPED(duuG25b|`d?kwy9qhe2vF4Q*&K%);cE2co4 zylm&wLYq?hxL~Yvm2|+XB2Z1BhQMS3QwU5I>7M4EcsqUC+wgA$^fdRjcfy5oi`L9J zoY>?(yeWOKsRF$!)}5k-g$7Dp1|$DvG2+>qz~;GRgdF;+Os4dgnKD3Tikd~Txh4;g znLI$IW`Il$%b@S9tX<4Zl_U$0r0W4PPbC$VE)EoxlG(cTt(Oza?n$Z6+080pHPqLs zd{qiQq_(ZIC6(fShx@~MgfvquS#Yw_k*;#2s~zbYnYJ@ecVy0{tD3XvYDc<8rtQpC zQysY-NL8}~sdgeY3Tee|DxRDse%`?P3?eHE8Bb!mp&uQX69F?A0Rz`PlE#LY1X0?8XHwQTa57>7jE; zYJ+|AIlpDI1LK2~17DWzh$S7kw2Joh(tVg$*dG}O)=_|n`UKx%Iy}hTR_%x3-4=Dr zYDdAj_wt~_?$!1>w;%QJY(65XMMh>}nC183;ogR}5tw?ETe5^qj4kUXB|{}-i7b~z zl}whv{PwIrD7V8sG<|M~wIp$x=|=pl9k(dB-vS!%rnN1Fmj&3Ur?FJ5!TEZb91mCn zVss-dqxz(AHL2jLvl>~5+=ne7bz{COA^5c!S*j<+iT$hwt0!PSJ~Rn&c3K!R-d}awY4v)pDDETFBhk0ZZQz5 z$XsAiSdnVyxYSbiqE$Ab)I&32X;`>5e_NxvjIeJq8=vXb^}ry*R-s zy|E*nvNth^5v4^Z-^tHQAbQFtN4lv&oop1REPm>tn>D90sxhTyuWmVb zDx94pMMo?R3PJ_olW#+B1`f zx}5KouM{w4PymT943k?h-NqC}#TJ;7aPH8R*HQvRJg~uiU{YW&DpI}c*T*;GnIx)? zkOehtiXUDnr}iCe{2vrZcO(;M)NO7<@1qA8g4+k(h9Xw&c8K5ZB&!hhffPmi8|gAK zJ)bTh>Ge{!uh{)6wq8Bz5Oc7~cC~hHX^f{kV_89)jB^YECS3eLCeADC;~RSA+4^W~ zOg!CN1~L$EXvf2ifsA>Ho-MeW?l=moF^Q)RcwOeGjBVpQ>;w?K$g0DrToq4I=psuy zp*P;1#q6xgX9F4Y(dGk~w)fPiq=^b*$;_~n1YYUrAhelNsjPCsL>U9F$%yi?p1%2r)93PVfG@0^@j#gfFP7@gI{LR|m$BvKsJ zl1bbEWOJbm@(<4woLY#m5+_cKZIQQBI4$7H68>Z>>9rE9U(teqpE`F+4KxSyI!2Dd zc@&G9bR8o8?TcgU(_&&9zjd0S0+boYaDS_fA^6srI(}PCU3J~O+RDill~bx~D<)5$ zKCfa9p;IfT)y|zZc}{ihoGCk?TXtf3#)b2<9kLnwUZ`wi?=I}dyBVw_4VYkOqH~QI zgU1aWiT29PQ!6VgtENq_K*(GUj0LeySe>#_zH5|M1?ZS~=!d?x)qcy;)k3wWcP(_e zjn`r5aP^5ZU{)vmb3!a#AKx77$<6fBd(aXWwJdaHIetnTzgF3JkGLQF)xPs0IKNdn&~@yFGl+`*@LialZh-z?%yO; zZsKHmNxZW&E;qI*v4&z9Fka0n{7@q6q75jqG2XT@MbRF<6|j}!%bhke%CRCZThCoK zJ4V%DhI;O;bFmV)!v$*55?|ems;5jH(2ZKrjf6P8QD7H?UmC1aB27k*~vIRKF+tNLpmt?ykMbvJV-{c?* zNcR@xrn3I)^8f9U7p^=?ourkxIOke-0^$$=JInBkiZc9Wp#|H6IKI93TL$iWKrw+* zZK!1of5~T33)O=(oB?_}>li3WJn8Ko=qArdSz$!+pQ&sFwpsa+^!}T6Z7EZ&(F;3p z9m{no<1%`H|5w(^mMKHcdZCA`%(iLI|JUBR$JkNTar~UQckawC?XY*Y+tRvMZ-L6L zbl1JxZCReB&_dBt3R|E?nzD4akFd0xK7cByrm|L_s@^F8NI@4dTQSg0h%+->iinKS47-j6e9X3kJ2{i=39`-1N| zm%dHsbS!B4j^@@GHFJ~PDbP}E)((`MJ`TMMCuf`I0Dl!Gi^u^?;GvYP8j5A5Q z-3UZ3u$!}Db`#}3*G@~hj@3lMIJ_qAt0;H4HZq@jzv(!8Ooio-%Z#NNqdKgO(0b)8 zTTce7nW07}m8t4uS%NF49&&q!sFC8z@ROOiIrn-fy_KI#R-83EtIY2Z5)c%FPZ%{bQ=au9lT&DO)5H`2hJ7_)<)k3iem6Qzp461~0! zC)Mqyjc?^m{eX+RqQ0)^lTp84YCDqQYlNM}__|7`yTx?uIu>2;8UOf3XK0d^@`mK& zWh;n2(_{uaoe#B>R7RbAchg(n(386RsohkAQ|7%zvp5TGw;?g*8y}|Nzs;Kh!+_cz zOLvcW<68?P1wNVhKH!~_$#r^nx(s*+(BUY2n(EgT*&2<*M?rtut5;0(Kmy%loKjgC zzim#)I;%*KF*(Sqlq*T0zEpG&+lm)i3ZLGiLhB0cCTVl0W7`==^}Exy$Y_jm z`ANNIgpiRXiK_ux9|4tl7jv%ZVHnTMlEb_CgwlomFQkhV@TFimOS)j0NLlJZC+>PU z%p~H&;!bCoK7MGFC=2$1e*p2W;Qd)8l2z=v1b0m%%w`Gi0McI6R`=#JT_E)1Qd#C0 zZ__I+2!3sJNqAGNG1tF0xFhIN=}5_LI}mp8+ddQed3BFfl<{e#kN1PunGNXm2FmNG zZ}wL7vN|)*OjfdpEoUHKll~-gW0ZW#|>J4QXaBuvqkJsWh8OYgUT4O&`37S6w$KRLmRz zyccdR0FJpj%MQ&}@qT0jKj(;_X8WE=C3@JetUUK@JktxML-~fRP!iEz-3NVGY|J#^ zT&y96>}IXAIi5b|NLL|dZ)40>R`M-n9lCZ=mhu)STlMnS@Ny?Vot;UYj}gjN+{1A= z8>ut7JKaEYS}49&oG1<|zbWQQ#~L^uaz`4xHRAF(6vt$3JDtUenudYzS?>iHvH9|ZvE66u-qd&{osE2^x6gb`iL8C&WK4IghYlLe8gHej`iezZgmC@K}euk-=W)sXVO)}|aSl+;;fBn+MRzEM1e9&yqfsjR)&#tmev>=qL!;l)?fHN}PueW3vLoP?#~q&U%*dKy_v0caXI1kXb#XI1fFL@Y@=;MySCbY5s|kk0GC zxzojgD!S5=P3NU1>`Ir~ZhsU6FwPg#OH}G?8xrw*kqPMTY64$_N^(dnkl2D8;FFS= zW(zYMg;c>HyI~@}C3wCA0fp)2_{r7Vjuc;NzaSI~%XDHHbe2L_q{}?j%hCZ~sUjWl z=LV$5oDIX!mhGjsYM&JbDKH2T-=+N4+NJhPdzL-hF0&uC=hzbswaaav?YAGd z1AH!e(5|pUb|s&RKF<#GiRcULh4vzQvAx8u2Eux~#$IMGw`=+A^A+|=yUiZ9pR-4t zf~VjsOgA`r`~6Ke9~1(<`ut^1rCY>!m~H{gO*Ur>He&B)8?%mRyX9N3=?1tdR;QeW zjMXfJl2*IydV7_<*^cr>>FsvRj#D?0#{*bHQ=34WoHnYI?25Z&6%*R*5?YiG@JT7X zILS%OVL}FVk8_d4nN?CNaF{#>k78s^Rkyvz+tfYIgsN_P4+taFUrG-+?@$@YP0-eW zYIFugdsTJF>_Q9=I`a!FP!!zuT+Y-Uw}`eK9s-i@gYiZCd>T&_Z9Z zRV_uaw;e$w9kIN8o(Z)Z;=}l~!B=29!1}~HT_2>7bJ(Iy`IPno+hrHpMRqaE3Vq0( zdMK33J!1JxeX;sg`x85*ok#O;NxtlTQ=4xoW`IUTu@Z~mO1^C4ZgJ78+7K?wL5xQY z$@&u<2wTj_AVa(Ye-9+}2onXK!*RU&C=2KSh>lE>Y(_j4Z z?YsAPPaW$YTz%=Hhui*n@7=$B@>k#Z+B0w8cK*?4|8U!Pe(<*?k9T$bXx)OXuRrkf zB|~R_^38`|zJ68v@mCH!b?qx}E?sOV?>_eQlLyvc@fG{X6|em1`$v|&m4CT#)}pbF z!!P_}^Iv}U=O1qWdwTH4$J!Qb{ON`p&%LR#?|a2PpT6()-yA*m+{D|@WDp}FhpqwYt+BC zC?6`$f?CQ*lJ+PctA_tz0idwtK%JZ(wFfa~2fYu*bR`Kbwb(6Q)roJJQm5&NqFN>4 z26OK?Ju0L_&b*1NX-6Dq!L*HDN@AUc9@WPbI?IN=Cu%iMP=oPs8cB8Xsrf}PGnDlv zu(D5M!nBLV0`e3`(4;U7O`Rr=1+m1QuN}&ctyt~u?p_?Vxy}mhKE%gy>_WW*8j7M~ z^=St{Wp{u+9o-PzX}Y42$nH|XQdzZY+YRcpFDYi{*!T4)BS@F???8G0EeOpJQdPRm zDg5VhaTMlqEEbE%NxnImLe5zY>hcL8CbC$0#qS6r*iW(gVtW)hrGOvPm_L6$@+wwe zi)9Fu0+k+K63ur?#~fbbObPwI#(&i*%)vDd7spi1i`s|_=`yU6GE!pp7BYo})J$Uv zuT<14R(~BUWV}(V{ytK+tG>}q*-e=&h&muEgm9vzHsBm zwi~HL0)j;1q$gS!Cu#Ln>?p1NHO96Pn@sOj*|Y3gf~2xDIt}1dQtSR{XkhI8`K73s zE(p=*u(%HCOrvy|zUo$pP^S08Sa$1-ne)U(*(pv+XFM3HC50&j87taS)5}0+KK%*1 zeTr0wPlEF0+^NGjy zme(FYyZH8@{sC5HOctCtr3a8EMh|V|0lyCQDW5n~9$Lr}lPuO!=6SMm{h=$uCPAKs zugz&A2Y6b1Pxg-sa>x%XRlbbeZ}xCm0S9T z$Il<-iK4;bzTs6X$NR>|Mpupv4Gazr4Ue%(r!{eKdFprG|Hne``6gK-FC86-=pU zf+(8@Q@gW`dTn-dvbt}fS=Vd|NatC*IDa!hE!5Q?m6!?XBG)FHHPBZkZjlW5O7=YZ zYJxyBg_@~51V@@T9Cy$K)lU&=DoHn24?&CJZdOXa43F*x^GGyj_#$upP%?> z#MaN;k?y7IZx{FPES!upf3DtYJ?Z$1GvVQ#2BOiJeK=%XZ($@*A zwP{J9C4rU%S`uhUpe2Ep1X>bkNuVWxmIVHnBrs*Tnl +/// Contains network address data. +/// +namespace GsmComm.GsmCommunication +{ + public class AddressData + { + private string address; + + private int typeOfAddress; + + /// + /// Gets the network address. + /// + public string Address + { + get + { + return this.address; + } + } + + /// + /// Gets the type of the . + /// + public int TypeOfAddress + { + get + { + return this.typeOfAddress; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The network address + /// The type of the given address + public AddressData(string address, int typeOfAddress) + { + this.address = address; + this.typeOfAddress = typeOfAddress; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/BatteryChargeInfo.cs b/GSMCommunication/GsmCommunication/BatteryChargeInfo.cs new file mode 100644 index 0000000..eb460c1 --- /dev/null +++ b/GSMCommunication/GsmCommunication/BatteryChargeInfo.cs @@ -0,0 +1,53 @@ +using System; + +/// +/// Contains the ME battery charging status and charge level. +/// +namespace GsmComm.GsmCommunication +{ + public class BatteryChargeInfo + { + private int bcs; + + private int bcl; + + /// + /// Gets the battery charge level. + /// + /// Usual values are in the range from 0 (empty) to 100 (full). + public int BatteryChargeLevel + { + get + { + return this.bcl; + } + } + + /// + /// Gets the battery charging status. + /// + /// Usual values are 0 for "not charging" and 1 for "charging". + public int BatteryChargingStatus + { + get + { + return this.bcs; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The battery charging status, usually 0 for "not charging" and 1 for "charging". + /// + /// + /// The battery charge level, usually in the range of 0 (empty) to 100 (full). + /// + public BatteryChargeInfo(int batteryChargingStatus, int batteryChargeLevel) + { + this.bcs = batteryChargingStatus; + this.bcl = batteryChargeLevel; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/CbmIndicationStyle.cs b/GSMCommunication/GsmCommunication/CbmIndicationStyle.cs new file mode 100644 index 0000000..cea16f3 --- /dev/null +++ b/GSMCommunication/GsmCommunication/CbmIndicationStyle.cs @@ -0,0 +1,30 @@ +/// +/// Specifies the possible indication settings for new cell broadcast messages (CBMs). +/// +namespace GsmComm.GsmCommunication +{ + public enum CbmIndicationStyle + { + /// + /// No CBM indications are routed to the TE. + /// + Disabled, + /// + /// If CBM is stored into ME/TA, indication of the memory location is routed to the TE. + /// + RouteMemoryLocation, + /// + /// New CBMs are routed directly to the TE. + /// + /// If ME supports data coding groups which define special routing also for messages other than + /// class 3 (e.g. SIM specific messages), ME may choose not to route messages of such data coding schemes + /// into TE (indication of a stored CBM may be given as with . + RouteMessage, + /// + /// Class 3 CBMs are routed directly to TE using the same indications as with . + /// If CBM storage is supported, messages of other classes result in indication as with + /// . + /// + RouteSpecial + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/Charset.cs b/GSMCommunication/GsmCommunication/Charset.cs new file mode 100644 index 0000000..4b4c8d8 --- /dev/null +++ b/GSMCommunication/GsmCommunication/Charset.cs @@ -0,0 +1,35 @@ +using System; + +/// +/// Lists some common character sets. +/// +namespace GsmComm.GsmCommunication +{ + public class Charset + { + /// The UCS2 (Unicode) character set + public const string Ucs2 = "UCS2"; + + /// The GSM character set + public const string Gsm = "GSM"; + + /// The PCCP437 character set + public const string Pccp437 = "PCCP437"; + + /// The PCDN character set + public const string Pcdn = "PCDN"; + + /// The IRA character set + public const string Ira = "IRA"; + + /// The ISO 8859-1 character set + public const string Iso8859_1 = "8859-1"; + + /// The characters encoded as hex + public const string Hex = "HEX"; + + public Charset() + { + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/DecodedShortMessage.cs b/GSMCommunication/GsmCommunication/DecodedShortMessage.cs new file mode 100644 index 0000000..515147e --- /dev/null +++ b/GSMCommunication/GsmCommunication/DecodedShortMessage.cs @@ -0,0 +1,78 @@ +using GsmComm.PduConverter; +using System; + +/// +/// Represents a short message from the phone in its decoded state. +/// +namespace GsmComm.GsmCommunication +{ + public class DecodedShortMessage + { + private int index; + + private SmsPdu data; + + private PhoneMessageStatus status; + + private string storage; + + /// + /// Gets the decoded message. + /// + public SmsPdu Data + { + get + { + return this.data; + } + } + + /// + /// Gets the index where the message is saved in the device in the . + /// + public int Index + { + get + { + return this.index; + } + } + + /// + /// Gets the parsed message status. + /// + public PhoneMessageStatus Status + { + get + { + return this.status; + } + } + + /// + /// Gets the phone storage the message was read from. + /// + public string Storage + { + get + { + return this.storage; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The index where the message is saved in the device in the . + /// The decoded message. + /// The parsed message status. + /// The phone storage the message was read from. + public DecodedShortMessage(int index, SmsPdu data, PhoneMessageStatus status, string storage) + { + this.index = index; + this.data = data; + this.status = status; + this.storage = storage; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/DeleteFlag.cs b/GSMCommunication/GsmCommunication/DeleteFlag.cs new file mode 100644 index 0000000..cb8a54e --- /dev/null +++ b/GSMCommunication/GsmCommunication/DeleteFlag.cs @@ -0,0 +1,30 @@ +/// +/// Lists the possible delete flags for AT+CMGD. +/// +namespace GsmComm.GsmCommunication +{ + public enum DeleteFlag + { + /// Delete the message specified in index. + DeleteSpecified, + /// + /// Delete all read messages from preferred message storage, leaving unread messages and stored mobile + /// originated messages (whether sent or not) untouched. + /// + DeleteRead, + /// + /// Delete all read messages from preferred message storage and sent mobile originated messages, + /// leaving unread messages and unsent mobile originated messages untouched. + /// + DeleteReadAndSent, + /// + /// Delete all read messages from preferred message storage, sent and unsent mobile originated messages + /// leaving unread messages untouched. + /// + DeleteReadSentAndUnsent, + /// + /// Delete all messages from preferred message storage including unread messages. + /// + DeleteAll + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/DeleteScope.cs b/GSMCommunication/GsmCommunication/DeleteScope.cs new file mode 100644 index 0000000..190a92e --- /dev/null +++ b/GSMCommunication/GsmCommunication/DeleteScope.cs @@ -0,0 +1,17 @@ +/// +/// Lists the possible scopes for deleting short messages. +/// +namespace GsmComm.GsmCommunication +{ + public enum DeleteScope + { + /// Delete all read messages. + Read = 1, + /// Delete all read and sent messages. + ReadAndSent = 2, + /// Delete all read, sent and unsent messages + ReadSentAndUnsent = 3, + /// Delete all messages including unread messages. + All = 4 + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/GsmCommMain.cs b/GSMCommunication/GsmCommunication/GsmCommMain.cs new file mode 100644 index 0000000..e198f8e --- /dev/null +++ b/GSMCommunication/GsmCommunication/GsmCommMain.cs @@ -0,0 +1,1543 @@ +using GsmComm.PduConverter; +using System; +using System.Collections; +using System.Reflection; +using System.Runtime.Remoting.Messaging; + +/// +/// Interacts with a mobile phone to execute various functions. +/// +namespace GsmComm.GsmCommunication +{ + public class GsmCommMain + { + /// + /// The default port to connect to. + /// + public const string DefaultPortName = "COM1"; + + /// + /// The default baud rate to use. + /// + public const int DefaultBaudRate = 19200; + + /// + /// The default communication timeout. + /// + public const int DefaultTimeout = 300; + + private GsmPhone theDevice; + + private static bool versionInfoLogged; + + private LogLevel logLevel; + + /// + /// Gets the baud rate in use. + /// + public int BaudRate + { + get + { + return this.theDevice.BaudRate; + } + } + + /// + /// Gets or sets the delay in milliseconds between the checks to verify that the connection + /// to the phone is still alive. + /// + public int ConnectionCheckDelay + { + get + { + return this.theDevice.ConnectionCheckDelay; + } + set + { + this.theDevice.ConnectionCheckDelay = value; + } + } + + /// + /// Get or sets the current log level for this instance. + /// + public LogLevel LogLevel + { + get + { + return this.logLevel; + } + set + { + this.logLevel = value; + this.theDevice.LogLevel = value; + } + } + + /// + /// Gets COM port currently connected to. + /// + public string PortName + { + get + { + return this.theDevice.PortName; + } + } + + /// + /// Gets the current communication timeout. + /// + public int Timeout + { + get + { + return this.theDevice.Timeout; + } + } + + static GsmCommMain() + { + GsmCommMain.versionInfoLogged = false; + } + + /// + /// Initializes a new instance of the class using default parameters. + /// + /// Uses the default values: port=COM1, baud rate=19200, timeout=300ms. + public GsmCommMain() + { + this.logLevel = LogLevel.Verbose; + this.theDevice = new GsmPhone("COM1", 19200, 300); + this.theDevice.LogLevel = this.logLevel; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// The communication (COM) port to use. + /// Uses the default values: baud rate=19200, timeout=300ms. + public GsmCommMain(string portName) + { + this.logLevel = LogLevel.Verbose; + this.theDevice = new GsmPhone(portName, 19200, 300); + this.theDevice.LogLevel = this.logLevel; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// The communication (COM) port to use. + /// Uses the default values: baud rate=19200, timeout=300ms. + public GsmCommMain(int portNumber) + { + this.logLevel = LogLevel.Verbose; + this.theDevice = new GsmPhone(portNumber, 19200, 300); + this.theDevice.LogLevel = this.logLevel; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// The communication (COM) port to use. + /// The baud rate (speed) to use. + /// Uses the default values: timeout=300ms. + public GsmCommMain(string portName, int baudRate) + { + this.logLevel = LogLevel.Verbose; + this.theDevice = new GsmPhone(portName, baudRate, 300); + this.theDevice.LogLevel = this.logLevel; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// The communication (COM) port to use. + /// The baud rate (speed) to use. + /// Uses the default values: timeout=300ms. + public GsmCommMain(int portNumber, int baudRate) + { + this.logLevel = LogLevel.Verbose; + this.theDevice = new GsmPhone(portNumber, baudRate, 300); + this.theDevice.LogLevel = this.logLevel; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// The communication (COM) port to use. + /// The baud rate (speed) to use. + /// The communication timeout in milliseconds. + public GsmCommMain(string portName, int baudRate, int timeout) + { + this.logLevel = LogLevel.Verbose; + this.theDevice = new GsmPhone(portName, baudRate, timeout); + this.theDevice.LogLevel = this.logLevel; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// The communication (COM) port to use. + /// The baud rate (speed) to use. + /// The communication timeout in milliseconds. + public GsmCommMain(int portNumber, int baudRate, int timeout) + { + this.logLevel = LogLevel.Verbose; + this.theDevice = new GsmPhone(portNumber, baudRate, timeout); + this.theDevice.LogLevel = this.logLevel; + } + + /// + /// Acknowledges a new received short message that was directly routed to the application. + /// + /// Acknowledges are required for most received messages if + /// returns true. The acknowledge requirement can be changed with the + /// method. + /// + public void AcknowledgeNewMessage() + { + this.theDevice.AcknowledgeNewMessage(); + } + + private void AsyncCallback(IAsyncResult ar) + { + AsyncResult asyncResult = (AsyncResult)ar; + if (asyncResult.AsyncDelegate as GsmCommMain.MessageEventHandler == null) + { + if (asyncResult.AsyncDelegate as GsmCommMain.MessageErrorEventHandler == null) + { + this.LogIt(LogLevel.Warning, string.Concat("AsyncCallback got unknown delegate: ", asyncResult.AsyncDelegate.GetType().ToString())); + return; + } + else + { + this.LogIt(LogLevel.Info, "Ending async MessageErrorEventHandler call"); + GsmCommMain.MessageErrorEventHandler asyncDelegate = (GsmCommMain.MessageErrorEventHandler)asyncResult.AsyncDelegate; + asyncDelegate.EndInvoke(ar); + return; + } + } + else + { + this.LogIt(LogLevel.Info, "Ending async MessageEventHandler call"); + GsmCommMain.MessageEventHandler messageEventHandler = (GsmCommMain.MessageEventHandler)asyncResult.AsyncDelegate; + messageEventHandler.EndInvoke(ar); + return; + } + } + + /// + /// Closes the connection to the device. + /// + /// You can check the current connection state with the method. + /// + /// + /// + public void Close() + { + this.theDevice.Close(); + this.DisconnectEvents(); + } + + private void ConnectEvents() + { + this.theDevice.LoglineAdded += new LoglineAddedEventHandler(this.theDevice_LoglineAdded); + this.theDevice.ReceiveProgress += new ProgressEventHandler(this.theDevice_ReceiveProgress); + this.theDevice.ReceiveComplete += new ProgressEventHandler(this.theDevice_ReceiveComplete); + this.theDevice.MessageReceived += new MessageReceivedEventHandler(this.theDevice_MessageReceived); + this.theDevice.PhoneConnected += new EventHandler(this.theDevice_PhoneConnected); + this.theDevice.PhoneDisconnected += new EventHandler(this.theDevice_PhoneDisconnected); + } + + /// + /// Creates a new phonebook entry. + /// + /// The entry to create. + /// The storage to save the entry. + /// The property of the entry is ignored, + /// the entry is always saved in the first free location. All other properties must be set + /// correctly. + /// + public void CreatePhonebookEntry(PhonebookEntry entry, string storage) + { + this.theDevice.SelectPhonebookStorage(storage); + this.theDevice.WritePhonebookEntry(entry); + } + + /// + /// Decodes a received short message. + /// + /// The message to decode + /// The decoded message as object. + public SmsPdu DecodeReceivedMessage(ShortMessage message) + { + IncomingSmsPdu incomingSmsPdu = null; + try + { + incomingSmsPdu = IncomingSmsPdu.Decode(message.Data, true, message.Length); + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Error, string.Concat("IncomingSmsPdu decoder can't decode message: ", exception.Message)); + this.LogIt(LogLevel.Error, string.Concat(" Message data = \"", message.Data, "\"")); + int length = message.Length; + this.LogIt(LogLevel.Error, string.Concat(" Message length = ", length.ToString())); + throw; + } + return incomingSmsPdu; + } + + /// + /// Decodes a short message read from the phone. + /// + /// The message to decode. + /// The decoded short message. + /// + /// Use this function to decode messages that were read with the function. + /// + /// + /// There is no decoder available that can handle the message's status. + public SmsPdu DecodeShortMessage(ShortMessageFromPhone message) + { + PhoneMessageStatus status = (PhoneMessageStatus)message.Status; + SmsPdu smsPdu = null; + PhoneMessageStatus phoneMessageStatu = status; + if (phoneMessageStatu == PhoneMessageStatus.ReceivedUnread || phoneMessageStatu == PhoneMessageStatus.ReceivedRead) + { + try + { + smsPdu = this.DecodeReceivedMessage(message); + } + catch (Exception exception) + { + this.LogIt(LogLevel.Warning, "Unable to decode message with specified status - trying other variants."); + smsPdu = this.DecodeStoredMessage(message); + } + } + else if (phoneMessageStatu == PhoneMessageStatus.StoredUnsent || phoneMessageStatu == PhoneMessageStatus.StoredSent) + { + try + { + smsPdu = this.DecodeStoredMessage(message); + } + catch (Exception exception1) + { + this.LogIt(LogLevel.Warning, "Unable to decode message with specified status - trying other variants."); + smsPdu = this.DecodeReceivedMessage(message); + } + } + else + { + string[] str = new string[5]; + str[0] = "No decoder available for message of status \""; + str[1] = status.ToString(); + str[2] = "\" (index "; + int index = message.Index; + str[3] = index.ToString(); + str[4] = ")."; + string str1 = string.Concat(str); + this.LogIt(LogLevel.Error, str1); + throw new CommException(str1); + } + return smsPdu; + } + + private SmsPdu DecodeStoredMessage(ShortMessage message) + { + OutgoingSmsPdu outgoingSmsPdu = null; + try + { + outgoingSmsPdu = OutgoingSmsPdu.Decode(message.Data, true, message.Length); + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Error, string.Concat("OutgoingSmsPdu decoder can't decode message: ", exception.Message)); + this.LogIt(LogLevel.Error, string.Concat(" Message data = \"", message.Data, "\"")); + int length = message.Length; + this.LogIt(LogLevel.Error, string.Concat(" Message length = ", length.ToString())); + throw; + } + return outgoingSmsPdu; + } + + /// + /// Deletes all phonebook entries. + /// + /// The storage to use. + /// To delete single entries, use the function. + /// + /// + public void DeleteAllPhonebookEntries(string storage) + { + this.theDevice.SelectPhonebookStorage(storage); + PhonebookEntry[] phonebookEntryArray = this.theDevice.ReadPhonebookEntries(); + PhonebookEntry[] phonebookEntryArray1 = phonebookEntryArray; + for (int i = 0; i < (int)phonebookEntryArray1.Length; i++) + { + PhonebookEntry phonebookEntry = phonebookEntryArray1[i]; + this.theDevice.DeletePhonebookEntry(phonebookEntry.Index); + } + } + + /// + /// Deletes the specified short message. + /// + /// The index of the message to delete. + /// The storage to use. + /// + /// To delete a group of messages, use the function. + /// + /// + public void DeleteMessage(int index, string storage) + { + this.LogIt(LogLevel.Info, "Deleting message..."); + this.theDevice.SelectReadStorage(storage); + this.theDevice.DeleteMessage(index); + } + + /// + /// Deletes the specified group of messages. + /// + /// Specifies the messages that are affected by this command. + /// The storage to use. + /// + /// To delete a single message, use the function. + /// + /// + public void DeleteMessages(DeleteScope scope, string storage) + { + int num = scope; + DeleteFlag deleteFlag = (DeleteFlag)Enum.Parse(typeof(DeleteFlag), num.ToString()); + string[] str = new string[5]; + str[0] = "Deleting \""; + str[1] = scope.ToString(); + str[2] = "\" messages in storage \""; + str[3] = storage; + str[4] = "\"..."; + this.LogIt(LogLevel.Info, string.Concat(str)); + this.theDevice.SelectReadStorage(storage); + this.theDevice.DeleteMessage(1, deleteFlag); + } + + /// + /// Deletes a phonebook entry. + /// + /// The index of the entry to delete. + /// The storage to use. + /// To delete all phonebook entries at once, use the + /// function. + /// + /// + /// + public void DeletePhonebookEntry(int index, string storage) + { + this.theDevice.SelectPhonebookStorage(storage); + this.theDevice.DeletePhonebookEntry(index); + } + + /// + /// Disables all message notifications. + /// + /// + /// Call this function after a call to to disable this functionality + /// again. + /// It's highly recommended to disable notifications again before closing the connection to + /// the phone. If it doesn't get disabled, the phone will still try to send notifications, which will not be + /// successful. The phone may buffer unsuccessful notifications, but you should generally not rely + /// on this. + /// + /// + public void DisableMessageNotifications() + { + this.theDevice.SetMessageIndications(new MessageIndicationSettings(0, 0, 0, 0, 0)); + } + + /// + /// Disables all message routings. + /// + /// + /// Call this function after a call to to disable this functionality + /// again. + /// CAUTION: It's highly recommended to disable routing again before closing the connection to + /// the phone. If it doesn't get disabled, the phone will still try to route messages on, which will not be + /// successful. You may lose messages in such a case if the phone doesn't buffer unsuccessful routings. + /// + /// + /// + public void DisableMessageRouting() + { + this.theDevice.SetMessageIndications(new MessageIndicationSettings(0, 0, 0, 0, 0)); + } + + /// + /// Disables the SMS batch mode. + /// + /// Disables the SMS batch mode previously enabled with + /// or . + /// + public void DisableSmsBatchMode() + { + this.theDevice.SetMoreMessagesToSend(MoreMessagesMode.Disabled); + } + + private void DisconnectEvents() + { + this.theDevice.LoglineAdded -= new LoglineAddedEventHandler(this.theDevice_LoglineAdded); + this.theDevice.ReceiveProgress -= new ProgressEventHandler(this.theDevice_ReceiveProgress); + this.theDevice.ReceiveComplete -= new ProgressEventHandler(this.theDevice_ReceiveComplete); + this.theDevice.MessageReceived -= new MessageReceivedEventHandler(this.theDevice_MessageReceived); + this.theDevice.PhoneConnected -= new EventHandler(this.theDevice_PhoneConnected); + this.theDevice.PhoneDisconnected -= new EventHandler(this.theDevice_PhoneDisconnected); + } + + /// + /// Enables notifications of new received short messages. + /// + /// + /// When a new message is received that is either a standard SMS message or a status report, + /// the event is fired. The + /// in this event must be cast to a object, which contains the memory + /// location of the message that was saved in the phone. You can then use the + /// method (for example) to read the new message. + /// The supported notification settings vary between different phone models. Therefore the phone is + /// queried first and then the supported settings of the phone are compared to the settings needed for + /// the notification functionality to work. If a specific setting is not supported and there is no + /// alternative setting possible, an exception will be raised. + /// To disable message notifications, use the function. + /// + /// EnableMessageNotifications can't be used with at the same time. + /// Disable one functionality before using the other. + /// + /// + /// + /// + public void EnableMessageNotifications() + { + MessageIndicationSupport supportedIndications = this.theDevice.GetSupportedIndications(); + MessageIndicationMode messageIndicationMode = MessageIndicationMode.BufferAndFlush; + if (!supportedIndications.SupportsMode(messageIndicationMode)) + { + if (supportedIndications.SupportsMode(MessageIndicationMode.SkipWhenReserved)) + { + messageIndicationMode = MessageIndicationMode.SkipWhenReserved; + } + else + { + if (supportedIndications.SupportsMode(MessageIndicationMode.ForwardAlways)) + { + messageIndicationMode = MessageIndicationMode.ForwardAlways; + } + else + { + throw new CommException("The phone does not support any of the required message indication modes."); + } + } + } + SmsDeliverIndicationStyle smsDeliverIndicationStyle = SmsDeliverIndicationStyle.RouteMemoryLocation; + if (supportedIndications.SupportsDeliverStyle(smsDeliverIndicationStyle)) + { + CbmIndicationStyle cbmIndicationStyle = CbmIndicationStyle.Disabled; + SmsStatusReportIndicationStyle smsStatusReportIndicationStyle = SmsStatusReportIndicationStyle.RouteMemoryLocation; + if (!supportedIndications.SupportsStatusReportStyle(smsStatusReportIndicationStyle)) + { + this.LogIt(LogLevel.Warning, "Attention: The phone does not support notification about new status reports. As a fallback it will be disabled."); + smsStatusReportIndicationStyle = SmsStatusReportIndicationStyle.Disabled; + } + IndicationBufferSetting indicationBufferSetting = IndicationBufferSetting.Flush; + if (!supportedIndications.SupportsBufferSetting(indicationBufferSetting)) + { + if (supportedIndications.SupportsBufferSetting(IndicationBufferSetting.Clear)) + { + indicationBufferSetting = IndicationBufferSetting.Clear; + } + else + { + throw new CommException("The phone does not support any of the required buffer settings."); + } + } + MessageIndicationSettings messageIndicationSetting = new MessageIndicationSettings(messageIndicationMode, smsDeliverIndicationStyle, cbmIndicationStyle, smsStatusReportIndicationStyle, indicationBufferSetting); + this.theDevice.SetMessageIndications(messageIndicationSetting); + return; + } + else + { + throw new CommException("The phone does not support notification for standard SMS (SMS-DELIVER) messages. "); + } + } + + /// + /// Enables direct routing of new received short messages to the application. + /// + /// + /// When a new message is received that is either a standard SMS message or a status report, + /// the event is fired. The + /// in this event must be cast to a object which can then be decoded using + /// . + /// CAUTION: Because the messages are forwared directly, they are not saved in the phone. + /// If for some reason the message must be saved it must explicitly be done afterwards. Either by using + /// the or functions + /// to write the message back to the phone or by storing the message somewhere else for later use. + /// It may be necessary to acknlowledge new routed messages to the phone, either because this + /// is desired for reliable message transfer or because it is preconfigured in the phone. Use + /// to find out if acknowledgements must be done. To do the actual + /// acknowledge, use . + /// The supported routing settings vary between different phone models. Therefore the phone is + /// queried first and then the supported settings of the phone are compared to the settings needed for + /// the routing functionality to work. If a specific setting is not supported and there is no + /// alternative setting possible, an exception will be raised. + /// To disable message routing, use the function. + /// EnableMessageRouting can't be used with at the same time. + /// Disable one functionality before using the other. + /// + /// + /// + /// + /// + /// + public void EnableMessageRouting() + { + MessageIndicationSupport supportedIndications = this.theDevice.GetSupportedIndications(); + MessageIndicationMode messageIndicationMode = MessageIndicationMode.BufferAndFlush; + if (!supportedIndications.SupportsMode(messageIndicationMode)) + { + if (supportedIndications.SupportsMode(MessageIndicationMode.SkipWhenReserved)) + { + messageIndicationMode = MessageIndicationMode.SkipWhenReserved; + } + else + { + if (supportedIndications.SupportsMode(MessageIndicationMode.ForwardAlways)) + { + messageIndicationMode = MessageIndicationMode.ForwardAlways; + } + else + { + throw new CommException("The phone does not support any of the required message indication modes."); + } + } + } + SmsDeliverIndicationStyle smsDeliverIndicationStyle = SmsDeliverIndicationStyle.RouteMessage; + if (supportedIndications.SupportsDeliverStyle(smsDeliverIndicationStyle)) + { + CbmIndicationStyle cbmIndicationStyle = CbmIndicationStyle.Disabled; + SmsStatusReportIndicationStyle smsStatusReportIndicationStyle = SmsStatusReportIndicationStyle.RouteMessage; + if (!supportedIndications.SupportsStatusReportStyle(smsStatusReportIndicationStyle)) + { + this.LogIt(LogLevel.Warning, "Attention: The phone does not support routing of new status reports. As a fallback it will be disabled."); + smsStatusReportIndicationStyle = SmsStatusReportIndicationStyle.Disabled; + } + IndicationBufferSetting indicationBufferSetting = IndicationBufferSetting.Flush; + if (!supportedIndications.SupportsBufferSetting(indicationBufferSetting)) + { + if (supportedIndications.SupportsBufferSetting(IndicationBufferSetting.Clear)) + { + indicationBufferSetting = IndicationBufferSetting.Clear; + } + else + { + throw new CommException("The phone does not support any of the required buffer settings."); + } + } + MessageIndicationSettings messageIndicationSetting = new MessageIndicationSettings(messageIndicationMode, smsDeliverIndicationStyle, cbmIndicationStyle, smsStatusReportIndicationStyle, indicationBufferSetting); + this.theDevice.SetMessageIndications(messageIndicationSetting); + return; + } + else + { + throw new CommException("The phone does not support routing of standard SMS (SMS-DELIVER) messages."); + } + } + + /// + /// Enables the SMS batch mode permanently. + /// + /// + /// When this feature is enabled (and supported by the network), multiple messages can be sent much + /// faster, as the SMS link is kept open between the messages. + /// If there is no message sent for 1-5 seconds (the exact value is up to the phones's implementation) + /// after the last sent SMS message, the SMS link is closed but the batch mode is kept enabled. You have + /// to explicitely disable it with . + /// If you don't want to care about turning the batch mode off when done with sending, + /// consider using instead. + /// + public void EnablePermanentSmsBatchMode() + { + this.theDevice.SetMoreMessagesToSend(MoreMessagesMode.Permanent); + } + + /// + /// Enables the SMS batch mode temporarily. + /// + /// + /// When this feature is enabled (and supported by the network), multiple messages can be sent much + /// faster, as the SMS link is kept open between the messages. + /// If there is no message sent for 1-5 seconds (the exact value is up to the phones's implementation) + /// after the last sent SMS message, the SMS link is closed and the batch mode is disabled + /// automatically. You have to re-enable it before you send the next batch of messages, but you + /// also don't have to care about turning it off when finished with sending. + /// + public void EnableTemporarySmsBatchMode() + { + this.theDevice.SetMoreMessagesToSend(MoreMessagesMode.Temporary); + } + + /// + /// Enters a password at the phone which is necessary before it can operated. + /// + /// The SIM PIN, SIM PUK or other password required. + /// Get the current PIN status with to check + /// whether a password must be entered. + public void EnterPin(string pin) + { + this.theDevice.EnterPin(pin); + } + + /// + /// Finds phonebook entries. + /// + /// The text in the entry to find. + /// The storage to search. + /// An array of phonebook entries matching the specified criteria. + /// The device executes the actual search. If you need the storage information with the results, + /// use the function instead. + /// + /// + public PhonebookEntry[] FindPhonebookEntries(string findtext, string storage) + { + new ArrayList(); + this.theDevice.SelectPhonebookStorage(storage); + return this.theDevice.FindPhonebookEntries(findtext); + } + + /// + /// Finds phonebook entries and saves the storage where they came from. + /// + /// The text in the entry to find. + /// The storage to search. + /// An array of phonebook entries matching the specified criteria. + /// The device executes the actual search. If you don't need the storage information with the + /// results, use the function instead. + /// + /// + public PhonebookEntryWithStorage[] FindPhonebookEntriesWithStorage(string findtext, string storage) + { + ArrayList arrayLists = new ArrayList(); + this.theDevice.SelectPhonebookStorage(storage); + PhonebookEntry[] phonebookEntryArray = this.theDevice.FindPhonebookEntries(findtext); + for (int i = 0; i < (int)phonebookEntryArray.Length; i++) + { + PhonebookEntry phonebookEntry = phonebookEntryArray[i]; + arrayLists.Add(new PhonebookEntryWithStorage(phonebookEntry, storage)); + } + PhonebookEntryWithStorage[] phonebookEntryWithStorageArray = new PhonebookEntryWithStorage[arrayLists.Count]; + arrayLists.CopyTo(phonebookEntryWithStorageArray, 0); + return phonebookEntryWithStorageArray; + } + + /// + /// Gets the phone's battery charging status. + /// + /// A object containing the battery details. + public BatteryChargeInfo GetBatteryCharge() + { + return this.theDevice.GetBatteryCharge(); + } + + /// + /// Gets the currenty selected text mode character set. + /// + /// A string containing the name of the currently selected character set. + /// + /// + /// + /// + public string GetCurrentCharacterSet() + { + return this.theDevice.GetCurrentCharacterSet(); + } + + /// + /// AT+COPS. Gets the currently selected network operator. + /// + /// An object containing the data or null if there is no current operator. + public OperatorInfo GetCurrentOperator() + { + return this.theDevice.GetCurrentOperator(); + } + + /// + /// Gets the memory status of the specified message storage. + /// + /// The storage to return the status for + /// An object containing the memory status of the specified storage. + public MemoryStatus GetMessageMemoryStatus(string storage) + { + return this.theDevice.SelectReadStorage(storage); + } + + /// + /// Gets the device's supported message storages. + /// + /// A object that contains details about the supported storages. + public MessageStorageInfo GetMessageStorages() + { + return this.theDevice.GetMessageStorages(); + } + + /// + /// Determines the current mode to select a network operator. + /// + /// The current mode. + public OperatorSelectionMode GetOperatorSelectionMode() + { + int operatorSelectionMode = this.theDevice.GetOperatorSelectionMode(); + if (Enum.IsDefined(typeof(OperatorSelectionMode), operatorSelectionMode)) + { + OperatorSelectionMode operatorSelectionMode1 = (OperatorSelectionMode)Enum.Parse(typeof(OperatorSelectionMode), operatorSelectionMode.ToString()); + return operatorSelectionMode1; + } + else + { + throw new CommException(string.Concat("Unknown operator selection mode ", operatorSelectionMode)); + } + } + + /// + /// Gets the entire phonebook of the selected storage. + /// + /// The storage to read the data from. + /// An array of phonebook entries. If you need the storage information with the + /// results, use the function instead. + /// + /// + /// + public PhonebookEntry[] GetPhonebook(string storage) + { + new ArrayList(); + this.theDevice.SelectPhonebookStorage(storage); + return this.theDevice.ReadPhonebookEntries(); + } + + /// + /// Gets the memory status of the specified phonebook storage. + /// + /// The storage to return the status for + /// An object containing the memory status of the specified storage. + public MemoryStatusWithStorage GetPhonebookMemoryStatus(string storage) + { + this.theDevice.SelectPhonebookStorage(storage); + return this.theDevice.GetPhonebookMemoryStatus(); + } + + /// + /// Gets the device's supported phonebook storages. + /// + /// An array of supported storages in coded form, usually "SM" for SIM, "ME" for + /// phone, etc. + public string[] GetPhonebookStorages() + { + return this.theDevice.GetPhonebookStorages(); + } + + /// + /// Gets the entire phonebook of the selected storage and saves the storage where the entries came from. + /// + /// The storage to read the data from. + /// An array of phonebook entries. If you don't need the storage information with the + /// results, use the function instead. + /// + /// + /// + public PhonebookEntryWithStorage[] GetPhonebookWithStorage(string storage) + { + ArrayList arrayLists = new ArrayList(); + this.theDevice.SelectPhonebookStorage(storage); + PhonebookEntry[] phonebookEntryArray = this.theDevice.ReadPhonebookEntries(); + for (int i = 0; i < (int)phonebookEntryArray.Length; i++) + { + PhonebookEntry phonebookEntry = phonebookEntryArray[i]; + arrayLists.Add(new PhonebookEntryWithStorage(phonebookEntry, storage)); + } + PhonebookEntryWithStorage[] phonebookEntryWithStorageArray = new PhonebookEntryWithStorage[arrayLists.Count]; + arrayLists.CopyTo(phonebookEntryWithStorageArray, 0); + return phonebookEntryWithStorageArray; + } + + /// + /// Returns a value indicating whether some password must be entered at the phone or not. + /// + /// The current PIN status as one of the values. + public PinStatus GetPinStatus() + { + return this.theDevice.GetPinStatus(); + } + + /// + /// Enables access to the protocol level of the current connection. + /// + /// An object that sends and receives data at the protocol level. + /// This method enables execution of custom commands that are not directly supported. It also disables execution of background + /// operations that would usually take place, such as checking whether the phone is still connected. + /// The method must be called as soon as execution of the custom commands is completed, + /// and allows for normal operations to continue. Execution of other commands besides from is not allowed + /// until is called. + /// + /// + public IProtocol GetProtocol() + { + return this.theDevice.GetProtocol(); + } + + /// + /// Gets the signal quality as calculated by the phone. + /// + /// A object containing the signal details. + public SignalQualityInfo GetSignalQuality() + { + return this.theDevice.GetSignalQuality(); + } + + /// + /// Gets the current SMS batch mode setting. + /// + /// The current mode. + public MoreMessagesMode GetSmsBatchModeSetting() + { + return this.theDevice.GetMoreMessagesToSend(); + } + + /// + /// Gets the SMS Service Center Address. + /// + /// The current SMSC address + /// This command returns the SMSC address, through which SMS messages are transmitted. + /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is + /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals + /// zero. + public AddressData GetSmscAddress() + { + return this.theDevice.GetSmscAddress(); + } + + /// + /// Returns the MSISDNs related to the subscriber. + /// + /// An array of objects with one for each MSISDN + /// (Mobile Subscriber ISDN Number), depending on the services subscribed. + /// + /// This information can be stored in the SIM/UICC or in the MT. + /// If the command is supported by the phone but no number can be retrieved, + /// an empty array is returned. + /// + public SubscriberInfo[] GetSubscriberNumbers() + { + return this.theDevice.GetSubscriberNumbers(); + } + + /// + /// Gets the phone's supported text mode character sets. + /// + /// A string array containing the names of the phone's supportet text mode character sets. + /// + /// + /// + /// + public string[] GetSupportedCharacterSets() + { + return this.theDevice.GetSupportedCharacterSets(); + } + + /// + /// Gathers information that identifiy the connected device. + /// + /// An object containing data about the device. + public IdentificationInfo IdentifyDevice() + { + this.LogIt(LogLevel.Info, "Identifying device..."); + IdentificationInfo identificationInfo = new IdentificationInfo(); + identificationInfo.Manufacturer = this.theDevice.RequestManufacturer(); + identificationInfo.Model = this.theDevice.RequestModel(); + identificationInfo.Revision = this.theDevice.RequestRevision(); + identificationInfo.SerialNumber = this.theDevice.RequestSerialNumber(); + this.LogIt(LogLevel.Info, string.Concat("Manufacturer: ", identificationInfo.Manufacturer)); + this.LogIt(LogLevel.Info, string.Concat("Model: ", identificationInfo.Model)); + this.LogIt(LogLevel.Info, string.Concat("Revision: ", identificationInfo.Revision)); + this.LogIt(LogLevel.Info, string.Concat("Serial number: ", identificationInfo.SerialNumber)); + return identificationInfo; + } + + /// + /// Checks if it is required to acknowledge new directly routed incoming messages. + /// + /// true if directly routed incoming messages need to be acknowledged, false if not. + public bool IsAcknowledgeRequired() + { + int num = 0; + int num1 = 0; + int num2 = 0; + int num3 = 0; + this.theDevice.GetCurrentMessageService(out num, out num1, out num2, out num3); + return num == 1; + } + + /// + /// Determines if there is actually a device connected that responds to commands. + /// + /// true if there is a device connected and responsive, otherwise false. + /// + /// You can use this function after opening the port with to verify that there is really a device connected + /// before processding. + /// + /// + /// + public bool IsConnected() + { + return this.theDevice.IsConnected(); + } + + /// + /// Determines if the port is currently open. + /// + /// true if the port is open, otherwise false. + /// The port is open after a auccessful call to and must be closed with + /// . + /// This function does not check if there is actually a device connected, use the + /// function for that. + /// + /// + /// + /// + public bool IsOpen() + { + return this.theDevice.IsOpen(); + } + + /// + /// Lists the network operators detected by the phone. + /// + /// An array of objects containing the data of each operator. + /// If you want to determine the current operator, use the method. + public OperatorInfo2[] ListOperators() + { + return this.theDevice.ListOperators(); + } + + private void LogIt(LogLevel level, string text) + { + if (this.LoglineAdded != null && level <= this.logLevel) + { + DateTime now = DateTime.Now; + text = string.Concat(now.ToString("HH:mm:ss.fff"), " ", text); + this.LoglineAdded(this, new LoglineAddedEventArgs(level, text)); + } + } + + private void LogVersionInfo() + { + Assembly executingAssembly = Assembly.GetExecutingAssembly(); + Version version = executingAssembly.GetName().Version; + string str = version.ToString(2); + string str1 = version.ToString(); + string imageRuntimeVersion = executingAssembly.ImageRuntimeVersion; + string str2 = string.Format("GSMComm {0} (Build {1} for .NET {2})", str, str1, imageRuntimeVersion); + this.LogIt(LogLevel.Info, str2); + } + + private void OnMessageSendComplete(OutgoingSmsPdu pdu) + { + if (this.MessageSendComplete != null) + { + this.LogIt(LogLevel.Info, "Firing async MessageSendComplete event."); + MessageEventArgs messageEventArg = new MessageEventArgs(pdu); + this.MessageSendComplete.BeginInvoke(this, messageEventArg, new AsyncCallback(this.AsyncCallback), null); + } + } + + private void OnMessageSendFailed(OutgoingSmsPdu pdu, Exception exception) + { + if (this.MessageSendFailed != null) + { + this.LogIt(LogLevel.Info, "Firing async MessageSendFailed event."); + MessageErrorEventArgs messageErrorEventArg = new MessageErrorEventArgs(pdu, exception); + this.MessageSendFailed.BeginInvoke(this, messageErrorEventArg, new AsyncCallback(this.AsyncCallback), null); + } + } + + private void OnMessageSendStarting(OutgoingSmsPdu pdu) + { + if (this.MessageSendStarting != null) + { + this.LogIt(LogLevel.Info, "Firing async MessageSendStarting event."); + MessageEventArgs messageEventArg = new MessageEventArgs(pdu); + this.MessageSendStarting.BeginInvoke(this, messageEventArg, new AsyncCallback(this.AsyncCallback), null); + } + } + + /// + /// Opens the connection to the device. + /// + /// You can check the current connection state with the method. + /// + /// + /// + public void Open() + { + if (!GsmCommMain.versionInfoLogged) + { + this.LogVersionInfo(); + GsmCommMain.versionInfoLogged = true; + } + this.ConnectEvents(); + this.theDevice.Open(); + } + + /// + /// Reads a single short message. + /// + /// The index of the message to read. + /// The storage to look in for the message. + /// A object containing the message at the index specified. + public DecodedShortMessage ReadMessage(int index, string storage) + { + this.LogIt(LogLevel.Info, "Reading message..."); + this.theDevice.SelectReadStorage(storage); + ShortMessageFromPhone shortMessageFromPhone = this.theDevice.ReadMessage(index); + if (!Enum.IsDefined(typeof(PhoneMessageStatus), shortMessageFromPhone.Status)) + { + int status = shortMessageFromPhone.Status; + throw new CommException(string.Concat("Unknown message status \"", status.ToString(), "\"!")); + } + else + { + int num = shortMessageFromPhone.Status; + PhoneMessageStatus phoneMessageStatu = (PhoneMessageStatus)Enum.Parse(typeof(PhoneMessageStatus), num.ToString()); + DecodedShortMessage decodedShortMessage = new DecodedShortMessage(shortMessageFromPhone.Index, this.DecodeShortMessage(shortMessageFromPhone), phoneMessageStatu, storage); + return decodedShortMessage; + } + } + + /// + /// Reads and decodes short messages from phone. + /// + /// The status of the messages to read. + /// The storage to look in for the messages. + /// An array of decoded messages. + /// As the decoded version of the message is not always guaranteed to + /// be exactly the same when encoded back, do not use this function if you want + /// to save the message in their original form. Use instead + /// for that case. + /// + /// + public DecodedShortMessage[] ReadMessages(PhoneMessageStatus status, string storage) + { + this.LogIt(LogLevel.Info, "Reading messages..."); + ArrayList arrayLists = new ArrayList(); + this.theDevice.SelectReadStorage(storage); + ShortMessageFromPhone[] shortMessageFromPhoneArray = this.theDevice.ListMessages(status); + for (int i = 0; i < (int)shortMessageFromPhoneArray.Length; i++) + { + ShortMessageFromPhone shortMessageFromPhone = shortMessageFromPhoneArray[i]; + try + { + if (!Enum.IsDefined(typeof(PhoneMessageStatus), shortMessageFromPhone.Status)) + { + int num = shortMessageFromPhone.Status; + throw new CommException(string.Concat("Unknown message status \"", num.ToString(), "\"!")); + } + else + { + int num1 = shortMessageFromPhone.Status; + PhoneMessageStatus phoneMessageStatu = (PhoneMessageStatus)Enum.Parse(typeof(PhoneMessageStatus), num1.ToString()); + arrayLists.Add(new DecodedShortMessage(shortMessageFromPhone.Index, this.DecodeShortMessage(shortMessageFromPhone), phoneMessageStatu, storage)); + } + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Error, string.Concat("Error while decoding a message: ", exception.Message, " The message was ignored.")); + } + } + DecodedShortMessage[] decodedShortMessageArray = new DecodedShortMessage[arrayLists.Count]; + arrayLists.CopyTo(decodedShortMessageArray); + return decodedShortMessageArray; + } + + /// + /// Reads short messages in their original form from phone. + /// + /// The status of the messages to read. + /// The storage to look in for the messages. + /// An array of undecoded short messages, as read from the phone. + /// This function is intended to download the messages exactly as they are + /// returned from the phone, e.g. for a backup. If you want to use the messages + /// directly, e.g. displaying them to the user without saving them, use the + /// function instead. + /// If you want to decode the saved messages later, you can use the + /// function. + /// You can import the saved message back to the phone using the + /// function. + /// + /// + /// + /// + /// + public ShortMessageFromPhone[] ReadRawMessages(PhoneMessageStatus status, string storage) + { + this.LogIt(LogLevel.Info, "Reading messages..."); + new ArrayList(); + this.theDevice.SelectReadStorage(storage); + return this.theDevice.ListMessages(status); + } + + /// + /// Disables access to the protocol level of the current connection. + /// + /// This method must be called as soon as the execution of the custom commands initiated + /// by is completed and allows for normal operations to continue. + /// + public void ReleaseProtocol() + { + this.theDevice.ReleaseProtocol(); + } + + /// + /// Enables or disables the requirement to acknowledge new received short messages + /// that are directly routed to the application. + /// + /// Set to true to require acknowledgements, set to false + /// to turn off the requirement. + /// It depends on the phone when this setting can actually be changed. + /// Because of this, it is recommended to execute + /// after a call to RequireAcknowledge to verify the new setting. + /// + public void RequireAcknowledge(bool require) + { + int num = 0; + int num1 = 0; + int num2 = 0; + int num3; + if (require) + { + num3 = 1; + } + else + { + num3 = 0; + } + int num4 = num3; + this.theDevice.SelectMessageService(num4, out num, out num1, out num2); + } + + /// + /// Resets all settings that are not stored in a profile to their factory defaults. + /// + /// This function is useful if you don't know the state your phone is and + /// want to set it up from scratch. + public void ResetToDefaultConfig() + { + this.theDevice.ResetToDefaultConfig(); + } + + /// Selects the text mode character set. + /// The character set to use. + /// + /// The class contains some common character sets. + /// To get a list of the character sets that the phone supports, use . + /// + /// + /// + public void SelectCharacterSet(string charset) + { + this.theDevice.SelectCharacterSet(charset); + } + + /// + /// Sends a short message. + /// + /// The object containing the message to send. + /// Indicates whether an exception should be + /// thrown upon an error. + /// + /// This method sends the short message contained in the PDU object. + /// The message reference returned by the phone is stored in the MessageReference + /// property of the PDU object. If there is an error, an exception will be thrown. + /// Additionally, this function also fires the event + /// upon an error and the event upon success. + /// Set the throwExceptions parameter to false if your message handling + /// uses only the events fired by this method. + /// To send multiple messages in succession, use the function. + /// + /// + public void SendMessage(OutgoingSmsPdu pdu, bool throwExceptions) + { + this.LogIt(LogLevel.Info, "Sending message..."); + this.OnMessageSendStarting(pdu); + byte num = 0; + try + { + num = this.theDevice.SendMessage(pdu.ToString(), pdu.ActualLength); + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Error, string.Concat("Error while sending the message: ", exception.Message)); + this.OnMessageSendFailed(pdu, exception); + if (!throwExceptions) + { + return; + } + else + { + throw; + } + } + if (pdu.MessageReference == 0) + { + pdu.MessageReference = num; + } + this.LogIt(LogLevel.Info, "Message sent successfully."); + this.OnMessageSendComplete(pdu); + } + + /// + /// Sends a short message. + /// + /// The object containing the message to send. + /// + /// This method sends the short message contained in the PDU object. + /// The message reference returned by the phone is stored in the MessageReference + /// property of the PDU object. If there is an error, an exception will be thrown. + /// Additionally, this function also fires the event + /// upon an error and the event upon success. + /// To send multiple messages in succession, use the function. + /// + /// + public void SendMessage(OutgoingSmsPdu pdu) + { + this.SendMessage(pdu, true); + } + + /// + /// Sends multiple messages in succession. Sending stops at the first error. + /// + /// The messages to send. + /// + /// + /// + /// + public void SendMessages(OutgoingSmsPdu[] pdus) + { + if (pdus != null) + { + if ((int)pdus.Length != 0) + { + int length = (int)pdus.Length; + this.LogIt(LogLevel.Info, string.Concat(length.ToString(), " message(s) to send.")); + for (int i = 0; i < (int)pdus.Length; i++) + { + OutgoingSmsPdu outgoingSmsPdu = pdus[i]; + string[] str = new string[5]; + str[0] = "Sending message "; + int num = i + 1; + str[1] = num.ToString(); + str[2] = " of "; + int length1 = (int)pdus.Length; + str[3] = length1.ToString(); + str[4] = "..."; + this.LogIt(LogLevel.Info, string.Concat(str)); + this.SendMessage(outgoingSmsPdu); + } + return; + } + else + { + this.LogIt(LogLevel.Warning, "Nothing to do!"); + return; + } + } + else + { + this.LogIt(LogLevel.Error, "Failed. Message array is null."); + throw new ArgumentNullException("pdus"); + } + } + + /// + /// Sets the new SMS service center address. + /// + /// An object containing the new address + /// This command changes the SMSC address, through which SMS messages are transmitted. + /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is + /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals + /// zero. + public void SetSmscAddress(AddressData data) + { + this.theDevice.SetSmscAddress(data); + } + + /// + /// Sets the new SMS service center address. + /// + /// The new SMSC address + /// This command changes the SMSC address, through which SMS messages are transmitted. + /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is + /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals + /// zero. + public void SetSmscAddress(string address) + { + this.theDevice.SetSmscAddress(address); + } + + private void theDevice_LoglineAdded(object sender, LoglineAddedEventArgs e) + { + if (this.LoglineAdded != null) + { + this.LoglineAdded(this, e); + } + } + + private void theDevice_MessageReceived(object sender, MessageReceivedEventArgs e) + { + if (this.MessageReceived != null) + { + this.MessageReceived(this, e); + } + } + + private void theDevice_PhoneConnected(object sender, EventArgs e) + { + if (this.PhoneConnected != null) + { + this.PhoneConnected(this, e); + } + } + + private void theDevice_PhoneDisconnected(object sender, EventArgs e) + { + if (this.PhoneDisconnected != null) + { + this.PhoneDisconnected(this, e); + } + } + + private void theDevice_ReceiveComplete(object sender, ProgressEventArgs e) + { + if (this.ReceiveComplete != null) + { + this.ReceiveComplete(this, e); + } + } + + private void theDevice_ReceiveProgress(object sender, ProgressEventArgs e) + { + if (this.ReceiveProgress != null) + { + this.ReceiveProgress(this, e); + } + } + + /// + /// Stores a raw short message in the specified storage. + /// + /// The message to store. + /// The storage to store the message in. + /// The index of the message. If the index could not be retrieved, zero is returned. + /// + /// This function is useful for importing messages that were previously exported with . + /// + /// + public int WriteRawMessage(ShortMessageFromPhone message, string storage) + { + this.theDevice.SelectWriteStorage(storage); + return this.theDevice.WriteMessageToMemory(message.Data, message.Length, message.Status); + } + + /// + /// Stores a raw short message in the specified storage. + /// + /// The message to store. + /// The storage to store the message in. + /// The status to set for the message. + /// The index of the message. If the index could not be retrieved, zero is returned. + public int WriteRawMessage(ShortMessage message, string storage, int status) + { + this.theDevice.SelectWriteStorage(storage); + return this.theDevice.WriteMessageToMemory(message.Data, message.Length, status); + } + + /// + /// Stores a raw short message in the specified storage without setting a specific message status. + /// + /// The message to store. + /// The storage to store the message in. + /// The index of the message. If the index could not be retrieved, zero is returned. + /// + /// The message is stored with a predefined status set in the phone. + /// + /// + /// + public int WriteRawMessageWithoutStatus(ShortMessage message, string storage) + { + this.theDevice.SelectWriteStorage(storage); + return this.theDevice.WriteMessageToMemory(message.Data, message.Length); + } + + /// + /// The event that occurs when a new line was added to the log. + /// + public event LoglineAddedEventHandler LoglineAdded; + + /// + /// The event that occurs when a new message was received. + /// + public event MessageReceivedEventHandler MessageReceived; + + /// + /// The event that occurs after a successful message transfer. + /// + public event GsmCommMain.MessageEventHandler MessageSendComplete; + + /// + /// The event that occurs after a failed message transfer. + /// + public event GsmCommMain.MessageErrorEventHandler MessageSendFailed; + + /// + /// The event that occurs immediately before transferring a new message. + /// + public event GsmCommMain.MessageEventHandler MessageSendStarting; + + /// The event that occurs when the phone is connected. + public event EventHandler PhoneConnected; + + /// The event that occurs when the phone is disconnected. + public event EventHandler PhoneDisconnected; + + /// The event that occurs when receiving from the phone is completed. + /// This event is only fired by reading operations that may take longer to complete. + public event ProgressEventHandler ReceiveComplete; + + /// The event that occurs when new data was received from the phone. + /// This event is only fired by reading operations that may take longer to complete. + public event ProgressEventHandler ReceiveProgress; + + /// + /// The method that handles the event. + /// + /// The origin of the event. + /// The associated with the event. + public delegate void MessageErrorEventHandler(object sender, MessageErrorEventArgs e); + + /// + /// The method that handles the and + /// events. + /// + /// The origin of the event. + /// The associated with the event. + public delegate void MessageEventHandler(object sender, MessageEventArgs e); + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/GsmPhone.cs b/GSMCommunication/GsmCommunication/GsmPhone.cs new file mode 100644 index 0000000..74485b2 --- /dev/null +++ b/GSMCommunication/GsmCommunication/GsmPhone.cs @@ -0,0 +1,3262 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO.Ports; +using System.Runtime.Remoting.Messaging; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Timers; + +/// +/// Interacts with a mobile phone at a low level to execute various functions. +/// +namespace GsmComm.GsmCommunication +{ + public class GsmPhone : IProtocol + { + private const int receiveTimeout = 5000; + + private const string commOK = "\r\nOK\r\n"; + + private const string commError = "\r\nERROR\r\n"; + + private const string messageServiceErrorPattern = "\\r\\n\\+CMS ERROR: (\\d+)\\r\\n"; + + private const string mobileEquipmentErrorPattern = "\\r\\n\\+CME ERROR: (\\d+)\\r\\n"; + + private SerialPort port; + + private string portName; + + private int baudRate; + + private int timeout; + + private bool logEnabled; + + private LogLevel logLevel; + + private int commThreadCheckDelay; + + private Queue rawQueue; + + private Queue inputQueue; + + private ManualResetEvent terminateCommThread; + + private ManualResetEvent dataReceived; + + private ManualResetEvent noData; + + private Thread commThread; + + private string dataToSend; + + private ManualResetEvent sendNow; + + private ManualResetEvent receiveNow; + + private ManualResetEvent sendDone; + + private bool connectionState; + + private AutoResetEvent checkConnection; + + private ManualResetEvent logThreadInitialized; + + private ManualResetEvent terminateLogThread; + + private Queue logQueue; + + private Thread logThread; + + /// + /// Gets the baud rate. + /// + public int BaudRate + { + get + { + return this.baudRate; + } + } + + /// + /// Gets or sets the delay in milliseconds between the checks to verify that the connection + /// to the phone is still alive. + /// + public int ConnectionCheckDelay + { + get + { + return this.commThreadCheckDelay; + } + set + { + int num; + if (value < 1000) + { + num = 1000; + } + else + { + num = value; + } + int num1 = num; + this.commThreadCheckDelay = num1; + } + } + + /// + /// Get or sets the current log level for this instance. + /// + public LogLevel LogLevel + { + get + { + return this.logLevel; + } + set + { + this.logLevel = value; + } + } + + /// + /// Gets the COM port. + /// + public string PortName + { + get + { + return this.portName; + } + } + + /// + /// Gets the communication timeout. + /// + public int Timeout + { + get + { + return this.timeout; + } + } + + /// + /// Initializing a new instance of the class. + /// + /// The communication (COM) port to use. + /// The baud rate (speed) to use. + /// The communication timeout in milliseconds. + public GsmPhone(string portName, int baudRate, int timeout) + { + this.commThreadCheckDelay = 10000; + if (portName != null) + { + if (portName == null || portName.Length != 0) + { + if (baudRate >= 0) + { + if (timeout >= 0) + { + this.port = null; + this.portName = portName; + this.baudRate = baudRate; + this.timeout = timeout; + this.logEnabled = true; + this.logLevel = LogLevel.Verbose; + this.rawQueue = new Queue(); + this.inputQueue = Queue.Synchronized(this.rawQueue); + this.terminateCommThread = new ManualResetEvent(false); + this.dataReceived = new ManualResetEvent(false); + this.noData = new ManualResetEvent(false); + this.commThread = null; + this.dataToSend = string.Empty; + this.sendNow = new ManualResetEvent(false); + this.receiveNow = new ManualResetEvent(false); + this.sendDone = new ManualResetEvent(false); + this.connectionState = false; + this.checkConnection = new AutoResetEvent(false); + this.logThreadInitialized = new ManualResetEvent(false); + this.terminateLogThread = new ManualResetEvent(false); + this.logQueue = new Queue(); + this.logThread = null; + return; + } + else + { + throw new ArgumentException("timeout must not be negative.", "timeout"); + } + } + else + { + throw new ArgumentException("baudRate must not be negative.", "baudRate"); + } + } + else + { + throw new ArgumentException("portName must not be an empty string."); + } + } + else + { + throw new ArgumentNullException("portName"); + } + } + + /// + /// Initializing a new instance of the class. + /// + /// The communication (COM) port to use. + /// The baud rate (speed) to use. + /// The communication timeout in milliseconds. + public GsmPhone(int portNumber, int baudRate, int timeout) : this(string.Concat("COM", portNumber.ToString()), baudRate, timeout) + { + } + + /// + /// AT+CNMA. Confirms reception of a new message (SMS-DELIVER or SMS-STATUS-REPORT) which is routed + /// directly to the TE. This acknowledgement command shall be used when "service" parameter + /// of the function equals 1. + /// + /// This sends a positive acknowledgement to the network. + public void AcknowledgeNewMessage() + { + lock (this) + { + this.AcknowledgeNewMessage(true); + } + } + + /// + /// AT+CNMA. Confirms reception of a new message (SMS-DELIVER or SMS-STATUS-REPORT) which is routed + /// directly to the TE. This acknowledgement command shall be used when "service" parameter + /// of the function equals 1. + /// + /// Specifies whether the message was received correctly. + /// Setting this parameter to true, will send a positive (RP-ACK) acknowledgement to the network. + /// Setting this parameter to false, will send a negative (RP-ERROR) acknowledgement to the network. + /// + /// + /// If ME does not get acknowledgement within required time (network timeout), ME should send RP-ERROR to + /// the network. ME/TA shall automatically disable routing to TE. + /// If command is executed, but no acknowledgement is expected, or some other ME related error occurs, + /// a is raised. + /// + public void AcknowledgeNewMessage(bool ok) + { + byte num; + lock (this) + { + this.VerifyValidConnection(); + this.ActivatePduMode(); + string str = "AT+CNMA="; + if (ok) + { + num = 0; + } + else + { + num = 2; + } + string str1 = string.Concat(str, num); + this.ExecAndReceiveMultiple(str1); + } + } + + /// + /// AT+CMGF. Activates the PDU mode. Device must support it or the call will fail. + /// + private void ActivatePduMode() + { + this.LogIt(LogLevel.Info, "Activating PDU mode..."); + this.ExecAndReceiveMultiple("AT+CMGF=0"); + } + + /// + /// AT+CMGF. Activates the text mode. Device must support it or the call will fail. + /// + private void ActivateTextMode() + { + this.LogIt(LogLevel.Info, "Activating text mode..."); + this.ExecAndReceiveMultiple("AT+CMGF=1"); + } + + private void AsyncCallback(IAsyncResult ar) + { + AsyncResult asyncResult = (AsyncResult)ar; + if (asyncResult.AsyncDelegate as MessageReceivedEventHandler == null) + { + if (asyncResult.AsyncDelegate as EventHandler == null) + { + if (asyncResult.AsyncDelegate as ProgressEventHandler == null) + { + this.LogIt(LogLevel.Warning, string.Concat("AsyncCallback got unknown delegate: ", asyncResult.AsyncDelegate.GetType().ToString())); + return; + } + else + { + ProgressEventHandler asyncDelegate = (ProgressEventHandler)asyncResult.AsyncDelegate; + asyncDelegate.EndInvoke(ar); + return; + } + } + else + { + this.LogIt(LogLevel.Info, "Ending async EventHandler call"); + EventHandler eventHandler = (EventHandler)asyncResult.AsyncDelegate; + eventHandler.EndInvoke(ar); + return; + } + } + else + { + this.LogIt(LogLevel.Info, "Ending async MessageReceivedEventHandler call"); + MessageReceivedEventHandler messageReceivedEventHandler = (MessageReceivedEventHandler)asyncResult.AsyncDelegate; + messageReceivedEventHandler.EndInvoke(ar); + return; + } + } + + private void CheckConnection() + { + if (Monitor.TryEnter(this)) + { + try + { + bool flag = this.IsConnectedInternal(); + this.SetNewConnectionState(flag); + } + finally + { + Monitor.Exit(this); + } + return; + } + else + { + this.LogIt(LogLevel.Verbose, "Object locked - connection check not performed."); + return; + } + } + + /// + /// Closes the connection to the device. + /// + /// You can check the current connection state with the method. + /// + /// + /// + /// Port not open. + public void Close() + { + lock (this) + { + if (this.IsOpen()) + { + this.TerminateCommThread(); + try + { + this.ClosePort(); + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Warning, "Closing the port failed. The exeption will not be rethrown to allow proper shutdown."); + this.LogIt(LogLevel.Warning, "Error details:"); + this.LogItShow(LogLevel.Warning, exception.ToString(), ""); + } + this.TerminateLogThread(); + } + else + { + throw new InvalidOperationException("Port not open."); + } + } + } + + private void ClosePort() + { + if (!this.port.IsOpen) + { + this.LogIt(LogLevel.Warning, "Attempted to close a closed serial connection. Ignored."); + } + else + { + this.LogIt(LogLevel.Info, "Closing serial connection."); + this.port.Close(); + this.port.DataReceived -= new SerialDataReceivedEventHandler(this.port_DataReceived); + if (this.connectionState) + { + this.connectionState = false; + this.OnPhoneDisconnected(); + return; + } + } + } + + private void CommThread() + { + this.LogIt(LogLevel.Info, "Communication thread started."); + System.Timers.Timer timer = new System.Timers.Timer((double)this.commThreadCheckDelay); + timer.Elapsed += new ElapsedEventHandler(this.connectionTimer_Elapsed); + timer.AutoReset = false; + timer.Start(); + object[] objArray = new object[1]; + objArray[0] = this.commThreadCheckDelay; + this.LogIt(LogLevel.Info, "Connection to phone will be checked every {0} ms.", objArray); + WaitHandle[] waitHandleArray = new WaitHandle[4]; + waitHandleArray[0] = this.terminateCommThread; + waitHandleArray[1] = this.sendNow; + waitHandleArray[2] = this.receiveNow; + waitHandleArray[3] = this.checkConnection; + WaitHandle[] waitHandleArray1 = waitHandleArray; + while (true) + { + int num = WaitHandle.WaitAny(waitHandleArray1); + if (num == 0) + { + break; + } + if (num != 1) + { + if (num != 2) + { + if (num == 3) + { + timer.Stop(); + this.CheckConnection(); + timer.Start(); + } + } + else + { + if (this.CommThreadReceive()) + { + timer.Stop(); + this.checkConnection.Reset(); + timer.Start(); + } + } + } + else + { + string str = this.dataToSend; + this.sendNow.Reset(); + this.inputQueue.Clear(); + this.dataReceived.Reset(); + try + { + this.SendInternal(str, true); + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Error, "Error while sending data to the phone."); + this.LogIt(LogLevel.Error, "Error details:"); + this.LogIt(LogLevel.Error, exception.ToString()); + } + this.sendDone.Set(); + } + } + this.LogIt(LogLevel.Info, "Communication thread is terminating."); + if (!this.terminateCommThread.WaitOne(0, false)) + { + this.LogIt(LogLevel.Warning, "Communication thread terminates without a stop signal!"); + } + timer.Stop(); + timer.Dispose(); + this.checkConnection.Reset(); + this.receiveNow.Reset(); + this.dataReceived.Reset(); + this.noData.Reset(); + this.inputQueue.Clear(); + this.sendNow.Reset(); + this.sendDone.Reset(); + this.dataToSend = string.Empty; + } + + private bool CommThreadReceive() + { + bool flag; + string str = null; + this.receiveNow.Reset(); + this.noData.Reset(); + bool flag1 = false; + MessageIndicationHandlers messageIndicationHandler = new MessageIndicationHandlers(); + StringBuilder stringBuilder = new StringBuilder(); + do + { + flag = false; + try + { + if (this.ReceiveInternal(out str)) + { + stringBuilder.Append(str); + flag = true; + } + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Error, "Error while receiving data from the phone."); + this.LogIt(LogLevel.Error, "Error details:"); + this.LogIt(LogLevel.Error, exception.ToString()); + stringBuilder = new StringBuilder(); + flag = false; + break; + } + try + { + if (!flag && messageIndicationHandler.IsIncompleteUnsolicitedMessage(stringBuilder.ToString())) + { + this.LogIt(LogLevel.Info, "Incomplete unsolicited message found, reading on after sleep."); + Thread.Sleep(this.timeout); + flag = true; + } + } + catch (Exception exception3) + { + Exception exception2 = exception3; + this.LogIt(LogLevel.Error, "Error while checking received data for unsolicited messages."); + this.LogIt(LogLevel.Error, "Received data was:"); + this.LogItShow(LogLevel.Error, stringBuilder.ToString(), ">> "); + this.LogIt(LogLevel.Error, "This data will not be processed further."); + this.LogIt(LogLevel.Error, "Error details:"); + this.LogIt(LogLevel.Error, exception2.ToString()); + stringBuilder = new StringBuilder(); + flag = false; + } + } + while (flag); + if (stringBuilder.Length <= 0) + { + this.noData.Set(); + flag1 = false; + } + else + { + this.inputQueue.Enqueue(stringBuilder.ToString()); + string str1 = this.MakeQueueString(this.inputQueue); + if (this.HandleUnsolicitedMessages(ref str1)) + { + this.inputQueue.Clear(); + this.inputQueue.Enqueue(str1); + } + this.dataReceived.Set(); + flag1 = true; + } + return flag1; + } + + private void connectionTimer_Elapsed(object sender, ElapsedEventArgs e) + { + this.checkConnection.Set(); + } + + private void CreateCommThread() + { + if (!this.IsCommThreadRunning()) + { + this.terminateCommThread.Reset(); + this.commThread = new Thread(new ThreadStart(this.CommThread)); + this.commThread.Name = "GsmPhone comm thread"; + this.commThread.Start(); + return; + } + else + { + this.LogIt(LogLevel.Warning, "Comm thread already created, ignoring call to CreateCommThread."); + return; + } + } + + private void CreateLogThread() + { + if (this.logThread == null || !this.logThread.IsAlive) + { + this.logThreadInitialized.Reset(); + this.terminateLogThread.Reset(); + this.logThread = new Thread(new ThreadStart(this.LogThread)); + this.logThread.Name = "GsmPhone log thread"; + this.logThread.Start(); + this.logThreadInitialized.WaitOne(); + this.LogIt(LogLevel.Info, "Log thread started."); + return; + } + else + { + this.LogIt(LogLevel.Warning, "Log thread already created, ignoring call to CreateLogThread."); + return; + } + } + + /// + /// Decodes a data stream with phonebook entries into objects. + /// + /// The entries to decode + /// The string the lines start with + private PhonebookEntry[] DecodePhonebookStream(string input, string prefix) + { + this.LogIt(LogLevel.Info, "Decoding phonebook entries..."); + ArrayList arrayLists = new ArrayList(); + Regex regex = new Regex(string.Concat(Regex.Escape(prefix), "(\\d+),\"(.+)\",(\\d+),\"(.+)\".*\\r\\n")); + for (Match i = regex.Match(input); i.Success; i = i.NextMatch()) + { + int num = int.Parse(i.Groups[1].Value); + string value = i.Groups[2].Value; + int num1 = int.Parse(i.Groups[3].Value); + string str = i.Groups[4].Value; + arrayLists.Add(new PhonebookEntry(num, value, num1, str)); + string[] strArrays = new string[9]; + strArrays[0] = "Entry: index="; + strArrays[1] = num.ToString(); + strArrays[2] = ", number=\""; + strArrays[3] = value; + strArrays[4] = "\", type="; + strArrays[5] = num1.ToString(); + strArrays[6] = ", text=\""; + strArrays[7] = str; + strArrays[8] = "\""; + this.LogIt(LogLevel.Info, string.Concat(strArrays)); + } + if (arrayLists.Count == 1) + { + this.LogIt(LogLevel.Info, "1 entry decoded."); + } + else + { + int count = arrayLists.Count; + this.LogIt(LogLevel.Info, string.Concat(count.ToString(), " entries decoded.")); + } + PhonebookEntry[] phonebookEntryArray = new PhonebookEntry[arrayLists.Count]; + arrayLists.CopyTo(phonebookEntryArray, 0); + return phonebookEntryArray; + } + + /// + /// AT+CMGD. Deletes the specified SMS message from the current read/delete storage. + /// + /// The index of the message to delete. + public void DeleteMessage(int index) + { + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, string.Concat("Deleting message with index ", index.ToString(), "...")); + this.ExecAndReceiveMultiple(string.Concat("AT+CMGD=", index.ToString())); + } + } + + /// + /// AT+CMGD. Deletes the specified SMS message from the current read/delete storage. + /// + /// The index of the message to delete. + /// The delete flag, this controls the behaviour of the delete command. + public void DeleteMessage(int index, DeleteFlag delflag) + { + lock (this) + { + this.VerifyValidConnection(); + string[] str = new string[5]; + str[0] = "Deleting message with index "; + str[1] = index.ToString(); + str[2] = ", using delflag "; + str[3] = delflag.ToString(); + str[4] = "..."; + this.LogIt(LogLevel.Info, string.Concat(str)); + int num = delflag; + string str1 = string.Concat("AT+CMGD=", index.ToString(), ",", num.ToString()); + this.ExecAndReceiveMultiple(str1); + } + } + + /// + /// AT+CPBW. Deletes a phonebook entry. + /// + /// The index of the entry to delete. + /// In this case it does not matter whether the specified index is valid. + /// If the entry does not exist, no error is returned. + public void DeletePhonebookEntry(int index) + { + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, string.Concat("Deleting phonebook entry ", index.ToString(), "...")); + this.ExecAndReceiveMultiple(string.Concat("AT+CPBW=", index.ToString())); + } + } + + private bool DispatchLog() + { + LoglineAddedEventArgs loglineAddedEventArg = null; + lock (this.logQueue) + { + if (this.logQueue.Count > 0) + { + loglineAddedEventArg = (LoglineAddedEventArgs)this.logQueue.Dequeue(); + } + } + if (loglineAddedEventArg == null) + { + return false; + } + else + { + try + { + if (this.LoglineAdded != null) + { + this.LoglineAdded(this, loglineAddedEventArg); + } + } + catch (Exception exception) + { + } + return true; + } + } + + /// + /// AT+CPIN. Enters a password at the phone which is necessary before it can operated. + /// + /// The SIM PIN, SIM PUK or other password required. + /// Get the current PIN status with to check + /// whether a password must be entered. + public void EnterPin(string pin) + { + lock (this) + { + this.VerifyValidConnection(); + object[] objArray = new object[1]; + objArray[0] = pin; + this.LogIt(LogLevel.Info, "Entering PIN...", objArray); + this.logEnabled = false; + try + { + string str = string.Format("AT+CPIN=\"{0}\"", pin); + this.ExecAndReceiveMultiple(str); + } + finally + { + this.logEnabled = true; + } + } + } + + private string ExecCommandInternal(string command, string receiveErrorMessage) + { + string str = null; + string str1 = string.Concat(command, "\r"); + this.receiveNow.Reset(); + this.SendInternal(str1, false); + StringBuilder stringBuilder = new StringBuilder(); + while (this.receiveNow.WaitOne(this.timeout, false)) + { + this.receiveNow.Reset(); + this.ReceiveInternal(out str); + stringBuilder.Append(str); + } + string str2 = stringBuilder.ToString(); + if (str2.Length != 0) + { + if (this.IsSuccess(str2)) + { + if (str2.EndsWith("\r\nOK\r\n")) + { + str2 = str2.Remove(str2.LastIndexOf("\r\nOK\r\n"), "\r\nOK\r\n".Length); + } + if (str2.StartsWith(str1)) + { + str2 = str2.Substring(str1.Length); + } + return str2; + } + else + { + this.HandleCommError(str2); + return null; + } + } + else + { + this.HandleRecvError(str2, receiveErrorMessage); + return null; + } + } + + /// + /// AT+CPBF. Searches for the specified text in the phonebook. + /// + /// The text to find. + /// An array of objects containing + /// the specified text + public PhonebookEntry[] FindPhonebookEntries(string findText) + { + PhonebookEntry[] phonebookEntryArray; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, string.Concat("Searching phonebook entry \"", findText, "\"...")); + string str = string.Concat("AT+CPBF=\"", findText, "\""); + string str1 = this.ExecAndReceiveMultiple(str); + phonebookEntryArray = this.DecodePhonebookStream(str1, "+CPBF: "); + } + return phonebookEntryArray; + } + + /// + /// AT+CBC. Gets the ME battery charging status and charge level. + /// + /// A object containing the battery details. + public BatteryChargeInfo GetBatteryCharge() + { + BatteryChargeInfo batteryChargeInfo; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting battery charge..."); + string str = this.ExecAndReceiveMultiple("AT+CBC"); + Regex regex = new Regex("\\+CBC: (\\d+),(\\d+)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + batteryChargeInfo = null; + } + else + { + int num = int.Parse(match.Groups[1].Value); + int num1 = int.Parse(match.Groups[2].Value); + this.LogIt(LogLevel.Info, string.Concat("Battery charging status = ", num.ToString())); + this.LogIt(LogLevel.Info, string.Concat("Battery charge level = ", num1.ToString())); + BatteryChargeInfo batteryChargeInfo1 = new BatteryChargeInfo(num, num1); + batteryChargeInfo = batteryChargeInfo1; + } + } + return batteryChargeInfo; + } + + /// + /// AT+CSCS. Retrives the currently selected character set. + /// + /// The current character set. + /// + /// + /// + /// + public string GetCurrentCharacterSet() + { + string empty; + string str; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Retrieving current character set..."); + string str1 = this.ExecAndReceiveMultiple("AT+CSCS?"); + Regex regex = new Regex("\\+CSCS: \"([^\"]+)\""); + Match match = regex.Match(str1); + if (!match.Success) + { + empty = string.Empty; + this.HandleCommError(str1); + } + else + { + empty = match.Groups[1].Value; + } + this.LogIt(LogLevel.Info, string.Concat("Current character set is \"", empty, "\".")); + str = empty; + } + return str; + } + + /// + /// AT+CSMS. Gets the supported message types along with the current service setting. + /// + /// Specifies the compatibility level of the SMS AT commands. + /// The requirement of service setting 1 depends on specific commands. + /// + /// ME supports mobile terminated messages + /// ME supports mobile originated messages + /// ME supports broadcast type messages + public void GetCurrentMessageService(out int service, out int mt, out int mo, out int bm) + { + lock (this) + { + this.VerifyValidConnection(); + string str = this.ExecAndReceiveMultiple("AT+CSMS?"); + Regex regex = new Regex("\\+CSMS: (\\d+),(\\d+),(\\d+),(\\d+)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + throw new CommException("Unexpected response.", str); + } + else + { + service = int.Parse(match.Groups[1].Value); + mt = int.Parse(match.Groups[2].Value); + mo = int.Parse(match.Groups[3].Value); + bm = int.Parse(match.Groups[4].Value); + } + } + } + + /// + /// AT+COPS. Gets the currently selected network operator. + /// + /// An object containing the data or null if there is no current operator. + public OperatorInfo GetCurrentOperator() + { + OperatorInfo operatorInfo; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting current operator..."); + string str = this.ExecAndReceiveMultiple("AT+COPS?"); + Regex regex = new Regex("\\+COPS: (\\d+)(?:,(\\d+),\"(.+)\")?(?:,(.+))?"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + throw new CommException("Unexpected response.", str); + } + else + { + int.Parse(match.Groups[1].Value); + if (match.Groups.Count <= 1) + { + this.LogIt(LogLevel.Info, "There is no operator currently selected!"); + operatorInfo = null; + } + else + { + int num = int.Parse(match.Groups[2].Value); + string value = match.Groups[3].Value; + string empty = string.Empty; + if (match.Groups.Count > 3) + { + empty = match.Groups[4].Value; + } + object[] objArray = new object[3]; + objArray[0] = num; + objArray[1] = value; + objArray[2] = empty; + this.LogIt(LogLevel.Info, "format={0}, oper=\"{1}\", act=\"{2}\"", objArray); + if (Enum.IsDefined(typeof(OperatorFormat), num)) + { + OperatorFormat operatorFormat = (OperatorFormat)Enum.Parse(typeof(OperatorFormat), num.ToString()); + OperatorInfo operatorInfo1 = new OperatorInfo(operatorFormat, value, empty); + operatorInfo = operatorInfo1; + } + else + { + throw new CommException(string.Concat("Unknown operator format ", num.ToString()), str); + } + } + } + } + return operatorInfo; + } + + /// + /// AT+CNMI. Gets the current message notification settings. + /// + /// A structure containing the detailed settings. + public MessageIndicationSettings GetMessageIndications() + { + MessageIndicationSettings messageIndicationSetting; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting current message indications..."); + string str = this.ExecAndReceiveMultiple("AT+CNMI?"); + Regex regex = new Regex("\\+CNMI: (\\d+),(\\d+),(\\d+),(\\d+),(\\d+)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + throw new CommException("Unexpected response.", str); + } + else + { + MessageIndicationSettings messageIndicationSetting1 = new MessageIndicationSettings(); + messageIndicationSetting1.Mode = int.Parse(match.Groups[1].Value); + messageIndicationSetting1.DeliverStyle = int.Parse(match.Groups[2].Value); + messageIndicationSetting1.CellBroadcastStyle = int.Parse(match.Groups[3].Value); + messageIndicationSetting1.StatusReportStyle = int.Parse(match.Groups[4].Value); + messageIndicationSetting1.BufferSetting = int.Parse(match.Groups[5].Value); + object[] mode = new object[5]; + mode[0] = messageIndicationSetting1.Mode; + mode[1] = messageIndicationSetting1.DeliverStyle; + mode[2] = messageIndicationSetting1.CellBroadcastStyle; + mode[3] = messageIndicationSetting1.StatusReportStyle; + mode[4] = messageIndicationSetting1.BufferSetting; + this.LogIt(LogLevel.Info, string.Format("mode={0:g}, mt={1:g}, bm={2:g}, ds={3:g}, bfr={4:g}", mode)); + messageIndicationSetting = messageIndicationSetting1; + } + } + return messageIndicationSetting; + } + + /// + /// Returns the message service error code in the input string. + /// + /// The data received + /// The error code + /// Use the method to check if the string + /// contains a message service error message. + /// Input string does not contain a message service error code + private int GetMessageServiceErrorCode(string input) + { + Regex regex = new Regex("\\r\\n\\+CMS ERROR: (\\d+)\\r\\n"); + Match match = regex.Match(input); + if (!match.Success || match.Groups[1].Captures.Count <= 0) + { + throw new ArgumentException("The input string does not contain a message service error code."); + } + else + { + return int.Parse(match.Groups[1].Captures[0].ToString()); + } + } + + /// + /// AT+CPMS. Gets the supported message storages. + /// + /// A object that contains details about the supported storages. + public MessageStorageInfo GetMessageStorages() + { + MessageStorageInfo messageStorageInfo; + lock (this) + { + this.VerifyValidConnection(); + ArrayList arrayLists = new ArrayList(); + this.LogIt(LogLevel.Info, "Enumerating supported message storages..."); + string str = this.ExecAndReceiveMultiple("AT+CPMS=?"); + int num = str.IndexOf("+CPMS: "); + if (num < 0) + { + this.HandleCommError(str); + } + else + { + str = str.Substring(num + "+CPMS: ".Length); + } + Regex regex = new Regex("\\((?:\"(\\w+)\"(?(?!\\)),))+\\)"); + for (Match i = regex.Match(str); i.Success; i = i.NextMatch()) + { + int count = i.Groups[1].Captures.Count; + string[] value = new string[count]; + for (int j = 0; j < count; j++) + { + value[j] = i.Groups[1].Captures[j].Value; + } + arrayLists.Add(value); + } + } + return messageStorageInfo; + } + + /// + /// Returns the mobile equipment error code in the input string. + /// + /// The data received + /// The mobile equipment error code + /// Use the method to check if the string + /// contains mobile equipment error message. + /// Input string does not contain a mobile equipment error code + private int GetMobileEquipmentErrorCode(string input) + { + Regex regex = new Regex("\\r\\n\\+CME ERROR: (\\d+)\\r\\n"); + Match match = regex.Match(input); + if (!match.Success || match.Groups[1].Captures.Count <= 0) + { + throw new ArgumentException("The input string does not contain a mobile equipment error code."); + } + else + { + return int.Parse(match.Groups[1].Captures[0].ToString()); + } + } + + /// + /// AT+CMMS. Gets the current SMS batch mode setting. + /// + /// The current mode. + public MoreMessagesMode GetMoreMessagesToSend() + { + MoreMessagesMode moreMessagesMode; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting more messages mode..."); + string str = string.Format("AT+CMMS?", new object[0]); + string str1 = this.ExecAndReceiveMultiple(str); + Regex regex = new Regex("\\+CMMS: (\\d+)"); + Match match = regex.Match(str1); + if (!match.Success) + { + this.HandleCommError(str1); + throw new CommException("Unexpected response.", str1); + } + else + { + int num = int.Parse(match.Groups[1].Value); + object[] objArray = new object[1]; + objArray[0] = num; + this.LogIt(LogLevel.Info, "mode=\"{0}\"", objArray); + if (Enum.IsDefined(typeof(MoreMessagesMode), num)) + { + MoreMessagesMode moreMessagesMode1 = (MoreMessagesMode)Enum.Parse(typeof(MoreMessagesMode), num.ToString()); + moreMessagesMode = moreMessagesMode1; + } + else + { + throw new CommException(string.Concat("Unknown more messages mode ", num.ToString(), "."), str1); + } + } + } + return moreMessagesMode; + } + + /// + /// AT+COPS. Determines the current mode to select a network operator. + /// + /// The current mode, see for possible values. + public int GetOperatorSelectionMode() + { + int num; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting current operator selection mode..."); + string str = this.ExecAndReceiveMultiple("AT+COPS?"); + Regex regex = new Regex("\\+COPS: (\\d+)(?:,(\\d+),\"(.+)\")?(?:,(.+))?"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + throw new CommException("Unexpected response.", str); + } + else + { + int num1 = int.Parse(match.Groups[1].Value); + object[] objArray = new object[1]; + objArray[0] = num1.ToString(); + this.LogIt(LogLevel.Info, "Current mode is {0}.", objArray); + num = num1; + } + } + return num; + } + + /// + /// AT+CPBS. Gets the memory status of the currently selected phonebook storage. + /// + /// The memory status of the currently selected storage. + public MemoryStatusWithStorage GetPhonebookMemoryStatus() + { + MemoryStatusWithStorage memoryStatusWithStorage; + int used; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting phonebook memory status..."); + string str = this.ExecAndReceiveMultiple("AT+CPBS?"); + MemoryStatusWithStorage memoryStatusWithStorage1 = this.ParsePhonebookMemoryStatus(str); + if (memoryStatusWithStorage1.Total > 0) + { + used = (int)((double)memoryStatusWithStorage1.Used / (double)memoryStatusWithStorage1.Total * 100); + } + else + { + used = 0; + } + int num = used; + object[] storage = new object[4]; + storage[0] = memoryStatusWithStorage1.Storage; + storage[1] = memoryStatusWithStorage1.Used; + storage[2] = memoryStatusWithStorage1.Total; + storage[3] = num; + this.LogIt(LogLevel.Info, string.Format("Memory status: storage=\"{0}\" {1}/{2} ({3}% used)", storage)); + memoryStatusWithStorage = memoryStatusWithStorage1; + } + return memoryStatusWithStorage; + } + + /// + /// AT+CPBR. Queries the size of the currently selected phonebook. + /// + /// Receives the lower bound of the phonebook + /// Receives the upper bound of the phonebook + /// Receives the maximum number length, 0 if unknown + /// Receives the maximum text length, 0 if unknown + public void GetPhonebookSize(out int lowerBound, out int upperBound, out int nLength, out int tLength) + { + int num; + int num1; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting phonebook size..."); + string str = this.ExecAndReceiveMultiple("AT+CPBR=?"); + Regex regex = new Regex("\\+CPBR: \\((\\d+)-(\\d+)\\)\\,(\\d*),(\\d*)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + } + lowerBound = int.Parse(match.Groups[1].Value); + upperBound = int.Parse(match.Groups[2].Value); + int& numPointer = nLength; + if (match.Groups[3].Value != "") + { + num = int.Parse(match.Groups[3].Value); + } + else + { + num = 0; + } + *(numPointer) = num; + int& numPointer1 = tLength; + if (match.Groups[4].Value != "") + { + num1 = int.Parse(match.Groups[4].Value); + } + else + { + num1 = 0; + } + *(numPointer1) = num1; + string[] strArrays = new string[8]; + strArrays[0] = "lowerBound="; + strArrays[1] = lowerBound.ToString(); + strArrays[2] = ", upperBound="; + strArrays[3] = upperBound.ToString(); + strArrays[4] = ", nLength="; + strArrays[5] = nLength.ToString(); + strArrays[6] = ", tLength="; + strArrays[7] = tLength.ToString(); + this.LogIt(LogLevel.Info, string.Concat(strArrays)); + } + } + + /// + /// AT+CPBS. Gets the supported phonebook storages. + /// + /// An array of the supported storages + public string[] GetPhonebookStorages() + { + string[] strArrays; + string str; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Enumerating supported storages..."); + string str1 = this.ExecAndReceiveMultiple("AT+CPBS=?"); + string[] value = null; + Regex regex = new Regex("\\+CPBS: \\((?:\"(\\w+)\"(?(?!\\)),))+\\)"); + Match match = regex.Match(str1); + if (!match.Success) + { + this.HandleCommError(str1); + } + else + { + int count = match.Groups[1].Captures.Count; + value = new string[count]; + for (int i = 0; i < count; i++) + { + value[i] = match.Groups[1].Captures[i].Value; + } + } + string empty = string.Empty; + int num = 0; + while (num < (int)value.Length) + { + string str2 = empty; + if (empty == string.Empty) + { + str = ""; + } + else + { + str = ", "; + } + empty = string.Concat(str2, str, value[num]); + num++; + } + this.LogIt(LogLevel.Info, string.Concat("Supported storages: ", empty)); + strArrays = value; + } + return strArrays; + } + + /// + /// AT+CPIN. Returns a value indicating whether some password must be entered at the phone or not. + /// + /// The current PIN status as one of the values. + public PinStatus GetPinStatus() + { + PinStatus status; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting PIN status..."); + string str = string.Format("AT+CPIN?", new object[0]); + string str1 = this.ExecAndReceiveMultiple(str); + Regex regex = new Regex("\\+CPIN: (.+)\\r\\n"); + Match match = regex.Match(str1); + if (!match.Success) + { + this.HandleCommError(str1); + throw new CommException("Unexpected response.", str1); + } + else + { + string value = match.Groups[1].Value; + object[] objArray = new object[1]; + objArray[0] = value; + this.LogIt(LogLevel.Info, "status=\"{0}\"", objArray); + status = this.MapPinStatusStringToStatus(value); + } + } + return status; + } + + /// + /// Enables access to the protocol level of the current connection. + /// + /// An object that sends and receives data at the protocol level. + /// This method enables execution of custom commands that are not directly supported. It also disables execution of background + /// operations that would usually take place, such as checking whether the phone is still connected. + /// The method must be called as soon as execution of the custom commands is completed, + /// and allows for normal operations to continue. Execution of other commands besides from is not allowed + /// until is called. + /// + /// + public IProtocol GetProtocol() + { + Monitor.Enter(this); + return this; + } + + /// + /// AT+CSQ. Gets the signal quality as calculated by the ME. + /// + /// A object containing the signal details. + public SignalQualityInfo GetSignalQuality() + { + SignalQualityInfo signalQualityInfo; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting signal quality..."); + string str = this.ExecAndReceiveMultiple("AT+CSQ"); + Regex regex = new Regex("\\+CSQ: (\\d+),(\\d+)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + signalQualityInfo = null; + } + else + { + int num = int.Parse(match.Groups[1].Value); + int num1 = int.Parse(match.Groups[2].Value); + this.LogIt(LogLevel.Info, string.Concat("Signal strength = ", num.ToString())); + this.LogIt(LogLevel.Info, string.Concat("Bit error rate = ", num1.ToString())); + SignalQualityInfo signalQualityInfo1 = new SignalQualityInfo(num, num1); + signalQualityInfo = signalQualityInfo1; + } + } + return signalQualityInfo; + } + + /// + /// AT+CSCA. Gets the SMS Service Center Address. + /// + /// The current SMSC address + /// This command returns the SMSC address, through which SMS messages are transmitted. + /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is + /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals + /// zero. + public AddressData GetSmscAddress() + { + AddressData addressDatum; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting SMSC address..."); + string str = "AT+CSCA?"; + string str1 = this.ExecAndReceiveMultiple(str); + Regex regex = new Regex("\\+CSCA: \"(.*)\",(\\d+)"); + Match match = regex.Match(str1); + if (!match.Success) + { + this.HandleCommError(str1); + throw new CommException("Unexpected response.", str1); + } + else + { + string value = match.Groups[1].Value; + int num = int.Parse(match.Groups[2].Value); + object[] objArray = new object[2]; + objArray[0] = value; + objArray[1] = num.ToString(); + this.LogIt(LogLevel.Info, "address=\"{0}\", typeOfAddress={1}", objArray); + AddressData addressDatum1 = new AddressData(value, num); + addressDatum = addressDatum1; + } + } + return addressDatum; + } + + /// + /// AT+CNUM. Returns the MSISDNs related to the subscriber. + /// + /// An array of objects with one for each MSISDN + /// (Mobile Subscriber ISDN Number), depending on the services subscribed. + /// + /// This information can be stored in the SIM/UICC or in the MT. + /// If the command is supported by the phone but no number can be retrieved, + /// an empty array is returned. + /// + public SubscriberInfo[] GetSubscriberNumbers() + { + SubscriberInfo[] subscriberInfoArray; + string empty; + int num; + int num1; + int num2; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting subscriber numbers..."); + string str = this.ExecAndReceiveMultiple("AT+CNUM"); + Regex regex = new Regex("\\+CNUM: (.*),\"(.*)\",(\\d+)(?:,(\\d+),(\\d+)(?:,(\\d+))?)?(?:\\r\\n)?"); + Match match = regex.Match(str); + List subscriberInfos = new List(); + while (match.Success) + { + if (match.Groups[1].Captures.Count <= 0 || match.Groups[1].Length <= 0) + { + empty = string.Empty; + } + else + { + empty = match.Groups[1].Value; + } + string str1 = empty; + string value = match.Groups[2].Value; + int num3 = int.Parse(match.Groups[3].Value); + if (match.Groups[4].Captures.Count <= 0 || match.Groups[4].Length <= 0) + { + num = -1; + } + else + { + num = int.Parse(match.Groups[4].Value); + } + int num4 = num; + if (match.Groups[5].Captures.Count <= 0 || match.Groups[5].Length <= 0) + { + num1 = -1; + } + else + { + num1 = int.Parse(match.Groups[5].Value); + } + int num5 = num1; + if (match.Groups[6].Captures.Count <= 0 || match.Groups[6].Length <= 0) + { + num2 = -1; + } + else + { + num2 = int.Parse(match.Groups[6].Value); + } + int num6 = num2; + object[] objArray = new object[6]; + objArray[0] = str1; + objArray[1] = value; + objArray[2] = num3; + objArray[3] = num4; + objArray[4] = num5; + objArray[5] = num6; + this.LogIt(LogLevel.Info, "alpha=\"{0}\",number=\"{1}\",type={2},speed={3},service={4},itc={5}", objArray); + SubscriberInfo subscriberInfo = new SubscriberInfo(str1, value, num3, num4, num5, num6); + subscriberInfos.Add(subscriberInfo); + match = match.NextMatch(); + } + SubscriberInfo[] subscriberInfoArray1 = new SubscriberInfo[subscriberInfos.Count]; + subscriberInfos.CopyTo(subscriberInfoArray1); + subscriberInfoArray = subscriberInfoArray1; + } + return subscriberInfoArray; + } + + /// + /// AT+CSCS. Retrieves the phone's supported character sets. + /// + /// A string array containing the supported character sets. + /// + /// + /// + /// + public string[] GetSupportedCharacterSets() + { + string[] strArrays; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Enumerating supported character sets..."); + string str = this.ExecAndReceiveMultiple("AT+CSCS=?"); + string[] value = null; + Regex regex = new Regex("\\+CSCS: \\((?:\"([^\"]+)\"(?(?!\\)),))+\\)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + } + else + { + int count = match.Groups[1].Captures.Count; + value = new string[count]; + for (int i = 0; i < count; i++) + { + value[i] = match.Groups[1].Captures[i].Value; + } + } + string str1 = this.MakeArrayString(value); + this.LogIt(LogLevel.Info, string.Concat("Supported character sets: ", str1)); + strArrays = value; + } + return strArrays; + } + + /// + /// AT+CNMI. Gets the supported new message indications from the phone. + /// + /// A object containing information about the supported + /// indications. + public MessageIndicationSupport GetSupportedIndications() + { + MessageIndicationSupport messageIndicationSupport; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Getting supported message indications..."); + string str = this.ExecAndReceiveMultiple("AT+CNMI=?"); + Regex regex = new Regex("\\+CNMI: \\(([\\d,-])+\\),\\(([\\d,-]+)\\),\\(([\\d,-]+)\\),\\(([\\d,-]+)\\),\\(([\\d,-]+)\\)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + throw new CommException("Unexpected response.", str); + } + else + { + string empty = string.Empty; + string empty1 = string.Empty; + string str1 = string.Empty; + string empty2 = string.Empty; + string str2 = string.Empty; + foreach (Capture capture in match.Groups[1].Captures) + { + empty = string.Concat(empty, capture.Value); + } + foreach (Capture capture1 in match.Groups[2].Captures) + { + empty1 = string.Concat(empty1, capture1.Value); + } + foreach (Capture capture2 in match.Groups[3].Captures) + { + str1 = string.Concat(str1, capture2.Value); + } + foreach (Capture capture3 in match.Groups[4].Captures) + { + empty2 = string.Concat(empty2, capture3.Value); + } + foreach (Capture capture4 in match.Groups[5].Captures) + { + str2 = string.Concat(str2, capture4.Value); + } + object[] objArray = new object[5]; + objArray[0] = empty; + objArray[1] = empty1; + objArray[2] = str1; + objArray[3] = empty2; + objArray[4] = str2; + this.LogIt(LogLevel.Info, "mode=\"{0}\", deliver=\"{1}\", cellBroadcast=\"{2}\", statusReport=\"{3}\", buffer=\"{4}\"", objArray); + MessageIndicationSupport messageIndicationSupport1 = new MessageIndicationSupport(empty, empty1, str1, empty2, str2); + messageIndicationSupport = messageIndicationSupport1; + } + } + return messageIndicationSupport; + } + + /// + /// Executes the specified command and reads multiple times from the phone + /// until a specific pattern is detected in the response. + /// + /// The command to execute. + /// The regular expression pattern that the received data must match to stop + /// reading. + /// The response received. + /// + /// + /// + public string ExecAndReceiveAnything(string command, string pattern) + { + string str = string.Concat(command, "\r"); + this.Send(str); + string str1 = this.ReceiveAnything(pattern); + if (str1.StartsWith(str)) + { + str1 = str1.Substring(str.Length); + } + return str1; + } + + /// Executes the specified command and reads multiple times from the phone + /// until one of the defined message termination patterns is detected in the response. + /// The command to execute. + /// The response received. + /// + /// + /// + public string ExecAndReceiveMultiple(string command) + { + + string str = string.Concat(command, "\r"); + this.Send(str); + string str1 = this.ReceiveMultiple(); + if (str1.EndsWith("\r\nOK\r\n")) + { + str1 = str1.Remove(str1.LastIndexOf("\r\nOK\r\n"), "\r\nOK\r\n".Length); + } + if (str1.StartsWith(str)) + { + str1 = str1.Substring(str.Length); + } + return str1; + } + + /// Executes the specified command and reads a single response. + /// The command to execute. + /// The response received. + /// + /// This method returns whatever response comes in from the phone during a single read operation. + /// The response received may not be complete. + /// If you want to ensure that always complete responses are read, use instead. + /// + /// + /// + /// + /// + public string ExecCommand(string command) + { + return this.ExecCommand(command, "No answer from phone."); + } + + /// + /// Executes the specified command and reads a single response. + /// The command to execute. + /// The message text for the exception if no data is received. + /// The response received. + /// + /// This method returns whatever response comes in from the phone during a single read operation. + /// The response received may not be complete. + /// If you want to ensure that always complete responses are read, use instead. + /// + /// + /// + /// + /// + public string ExecCommand(string command, string receiveErrorMessage) + { + string str = null; + string str1 = string.Concat(command, "\r"); + this.Send(str1); + if (this.Receive(out str)) + { + this.LogItShow(LogLevel.Verbose, str, ">> "); + if (this.IsSuccess(str)) + { + if (str.EndsWith("\r\nOK\r\n")) + { + str = str.Remove(str.LastIndexOf("\r\nOK\r\n"), "\r\nOK\r\n".Length); + } + if (str.StartsWith(str1)) + { + str = str.Substring(str1.Length); + } + return str; + } + else + { + this.HandleCommError(str); + return null; + } + } + else + { + this.HandleRecvError(str, receiveErrorMessage); + return null; + } + } + + /// Receives raw string data. + /// The data received. + /// true if reception was successful, otherwise false. + /// + public bool GsmComm.GsmCommunication.IProtocol.Receive(out string input) + { + input = string.Empty; + if (!this.IsCommThreadRunning()) + { + throw new InvalidOperationException("Communication thread is not running."); + } + else + { + WaitHandle[] waitHandleArray = new WaitHandle[2]; + waitHandleArray[0] = this.dataReceived; + waitHandleArray[1] = this.noData; + WaitHandle[] waitHandleArray1 = waitHandleArray; + int num = WaitHandle.WaitAny(waitHandleArray1, 5000, false); + if (num == 0) + { + this.dataReceived.Reset(); + if (this.inputQueue.Count <= 0) + { + this.LogIt(LogLevel.Warning, "Nothing in input queue"); + } + else + { + while (this.inputQueue.Count > 0) + { + input = string.Concat(input, (string)this.inputQueue.Dequeue()); + } + } + } + this.noData.Reset(); + return input.Length > 0; + } + } + + /// Reads multiple times from the phone until a specific pattern is detected in the response. + /// The response received. + /// The regular expression pattern that the received data must match to + /// stop reading. Can be an empty string if the pattern should not be checked. + /// + /// + /// + public string GsmComm.GsmCommunication.IProtocol.ReceiveAnything(string pattern) + { + string str = null; + string empty = string.Empty; + int tickCount = Environment.TickCount; + this.OnReceiveProgress(empty.Length); + int num = 0; + int num1 = 0; + while (!this.IsSuccess(empty) && !this.IsCommError(empty) && !this.IsMessageServiceError(empty) && !this.IsMobileEquipmentError(empty) && num < 6) + { + if (!this.Receive(out str)) + { + num++; + if (num <= 1) + { + continue; + } + this.LogIt(LogLevel.Info, string.Concat("Waiting for response from phone (", num.ToString(), " empty read(s)).")); + } + else + { + empty = string.Concat(empty, str); + num = 0; + num1++; + this.OnReceiveProgress(empty.Length); + if (pattern.Length <= 0 || !Regex.IsMatch(empty, pattern)) + { + continue; + } + break; + } + } + int tickCount1 = Environment.TickCount - tickCount; + this.LogItShow(LogLevel.Verbose, empty, ">> "); + this.OnReceiveComplete(empty.Length); + if (num < 6) + { + if (tickCount1 > this.timeout) + { + object[] objArray = new object[2]; + int length = empty.Length; + objArray[0] = length.ToString(); + objArray[1] = tickCount1.ToString(); + this.LogIt(LogLevel.Info, "{0} characters received after {1} ms.", objArray); + } + if (pattern.Length != 0 || this.IsSuccess(empty)) + { + this.HandleUnsolicitedMessages(ref empty); + return empty; + } + else + { + this.HandleCommError(empty); + return string.Empty; + } + } + else + { + string[] strArrays = new string[5]; + strArrays[0] = "Timeout after "; + strArrays[1] = num.ToString(); + strArrays[2] = " empty reading attempts within "; + strArrays[3] = tickCount1.ToString(); + strArrays[4] = " ms."; + this.LogIt(LogLevel.Error, string.Concat(strArrays)); + throw new CommException(string.Concat("No data received from phone after waiting for ", tickCount1.ToString(), " ms.")); + } + } + + /// Reads multiple times from the phone until one of the defined + /// message termination patterns is detected in the response. + /// The response received. + /// + /// + /// + public string GsmComm.GsmCommunication.IProtocol.ReceiveMultiple() + { + return this.ReceiveAnything(string.Empty); + } + + /// Sends raw string data. + /// The data to send. + /// + public void GsmComm.GsmCommunication.IProtocol.Send(string output) + { + if (!this.IsCommThreadRunning()) + { + throw new InvalidOperationException("Communication thread is not running."); + } + else + { + this.dataToSend = output; + this.sendDone.Reset(); + this.sendNow.Set(); + this.sendDone.WaitOne(); + return; + } + } + + /// + /// Handles a communication error. + /// + /// The data received. + /// Call this function when communicating and the response is not a success response. This + /// function checks if the response contains an error message. In any case a + /// or an exception derived from it is thrown based on the type of error. + /// + /// Always thrown based on type of error. + private void HandleCommError(string input) + { + if (!this.IsCommError(input)) + { + if (!this.IsMessageServiceError(input)) + { + if (!this.IsMobileEquipmentError(input)) + { + if (input.Length != 0) + { + this.LogIt(LogLevel.Error, "Failed. Unexpected response."); + this.LogIt(LogLevel.Error, "Received input:"); + this.LogItShow(LogLevel.Error, input, ""); + throw new CommException(string.Concat("Unexpected response received from phone:", Environment.NewLine, Environment.NewLine, input)); + } + else + { + this.LogIt(LogLevel.Error, "Failed. No answer from phone."); + throw new CommException("No answer from phone."); + } + } + else + { + int mobileEquipmentErrorCode = this.GetMobileEquipmentErrorCode(input); + this.LogIt(LogLevel.Error, string.Concat("Failed. Phone reports mobile equipment (ME) error ", mobileEquipmentErrorCode.ToString(), ".")); + this.LogItShow(LogLevel.Error, input, ""); + throw new MobileEquipmentErrorException(string.Concat("Mobile equipment error ", mobileEquipmentErrorCode.ToString(), " occurred."), mobileEquipmentErrorCode, input); + } + } + else + { + int messageServiceErrorCode = this.GetMessageServiceErrorCode(input); + this.LogIt(LogLevel.Error, string.Concat("Failed. Phone reports message service (MS) error ", messageServiceErrorCode.ToString(), ".")); + this.LogItShow(LogLevel.Error, input, ""); + throw new MessageServiceErrorException(string.Concat("Message service error ", messageServiceErrorCode.ToString(), " occurred."), messageServiceErrorCode, input); + } + } + else + { + string str = "The phone reports an unspecified error. This typically happens when a command is not supported by the device, a command is not valid for the current state or if a parameter is incorrect."; + this.LogIt(LogLevel.Error, string.Concat("Failed. ", str, " The response received was: ")); + this.LogItShow(LogLevel.Error, input, ""); + throw new CommException(str, input); + } + } + + private void HandleRecvError(string input, string userMessage) + { + if (input.Length <= 0) + { + this.LogIt(LogLevel.Error, "Failed. Receiving error."); + } + else + { + this.LogIt(LogLevel.Error, "Failed. Receiving error. Data until error:"); + this.LogItShow(LogLevel.Error, input, ""); + } + throw new CommException(userMessage); + } + + private bool HandleUnsolicitedMessages(ref string bigInput) + { + string str = null; + MessageIndicationHandlers messageIndicationHandler = new MessageIndicationHandlers(); + if (!messageIndicationHandler.IsUnsolicitedMessage(bigInput)) + { + return false; + } + else + { + this.LogItShow(LogLevel.Verbose, bigInput, ">> "); + IMessageIndicationObject messageIndicationObject = messageIndicationHandler.HandleUnsolicitedMessage(ref bigInput, out str); + this.LogIt(LogLevel.Info, string.Concat("Unsolicited message: ", str)); + this.OnMessageReceived(messageIndicationObject); + return true; + } + } + + private bool IsCommError(string input) + { + return input.IndexOf("\r\nERROR\r\n") >= 0; + } + + private bool IsCommThreadRunning() + { + if (this.commThread == null) + { + return false; + } + else + { + return this.commThread.IsAlive; + } + } + + /// + /// Checks if there is a device connected and responsive. + /// + /// true if there is really a device connected and it responds to commands, false otherwise. + /// + /// You can use this function after opening the port with to verify that there is really a device connected + /// before processding. + /// + /// + /// + public bool IsConnected() + { + bool flag; + lock (this) + { + flag = this.connectionState; + } + return flag; + } + + /// + /// AT. Checks if there is a device connected and responsive. + /// + /// true if there is really a device connected and it responds to commands, false otherwise. + /// + /// Bypasses the communication thread and does a direct send/receive. + /// + private bool IsConnectedInternal() + { + bool flag; + lock (this) + { + try + { + this.ExecCommandInternal("AT", "No phone connected."); + } + catch (Exception exception) + { + flag = false; + return flag; + } + flag = true; + } + return flag; + } + + private bool IsMessageServiceError(string input) + { + return Regex.IsMatch(input, "\\r\\n\\+CMS ERROR: (\\d+)\\r\\n"); + } + + /// + /// Checks if there is a mobile equipment error message in the input string. + /// + /// The data received + /// true if there is a mobile equipment error message in the string, otherwiese false. + private bool IsMobileEquipmentError(string input) + { + return Regex.IsMatch(input, "\\r\\n\\+CME ERROR: (\\d+)\\r\\n"); + } + + /// + /// Checks if the communication to the device is open. + /// + /// true if the port is open, false otherwise. + /// The port is open after a auccessful call to and must be closed with + /// . + /// This function does not check if there is actually a device connected, use the + /// function for that. + /// + /// + /// + /// + public bool IsOpen() + { + bool flag; + bool isOpen; + lock (this) + { + if (this.port == null) + { + isOpen = false; + } + else + { + isOpen = this.port.IsOpen; + } + flag = isOpen; + } + return flag; + } + + private bool IsSuccess(string input) + { + return input.IndexOf("\r\nOK\r\n") >= 0; + } + + /// + /// AT+CMGL. Reads SMS messages from the current read/delete storage using the PDU mode. + /// + /// The message status + /// An array of objects representing the messages read. + /// Always switches to PDU mode at the beginning. + public ShortMessageFromPhone[] ListMessages(PhoneMessageStatus status) + { + ShortMessageFromPhone[] shortMessageFromPhoneArray; + lock (this) + { + this.VerifyValidConnection(); + this.ActivatePduMode(); + this.LogIt(LogLevel.Info, string.Concat("Reading messages, requesting status \"", status.ToString(), "\"...")); + string str = string.Concat("AT+CMGL=", (int)status); + string str1 = this.ExecAndReceiveMultiple(str); + ArrayList arrayLists = new ArrayList(); + Regex regex = new Regex("\\+CMGL: (\\d+),(\\d+),(?:\"(\\w*)\")?,(\\d+)\\r\\n(\\w+)"); + for (Match i = regex.Match(str1); i.Success; i = i.NextMatch()) + { + int num = int.Parse(i.Groups[1].Value); + int num1 = int.Parse(i.Groups[2].Value); + string value = i.Groups[3].Value; + int num2 = int.Parse(i.Groups[4].Value); + string value1 = i.Groups[5].Value; + string[] strArrays = new string[8]; + strArrays[0] = "index="; + strArrays[1] = num.ToString(); + strArrays[2] = ", stat="; + strArrays[3] = num1.ToString(); + strArrays[4] = ", alpha=\""; + strArrays[5] = value; + strArrays[6] = "\", length="; + strArrays[7] = num2.ToString(); + this.LogIt(LogLevel.Info, string.Concat(strArrays)); + ShortMessageFromPhone shortMessageFromPhone = new ShortMessageFromPhone(num, num1, value, num2, value1); + arrayLists.Add(shortMessageFromPhone); + } + int count = arrayLists.Count; + this.LogIt(LogLevel.Info, string.Concat(count.ToString(), " message(s) read.")); + ShortMessageFromPhone[] shortMessageFromPhoneArray1 = new ShortMessageFromPhone[arrayLists.Count]; + arrayLists.CopyTo(shortMessageFromPhoneArray1, 0); + shortMessageFromPhoneArray = shortMessageFromPhoneArray1; + } + return shortMessageFromPhoneArray; + } + + /// + /// Lists the network operators detected by the phone. + /// + /// An array of objects containing the data of each operator. + /// If you want to determine the current operator, use the method. + public OperatorInfo2[] ListOperators() + { + OperatorInfo2[] operatorInfo2Array; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Listing operators..."); + string str = this.ExecAndReceiveMultiple("AT+COPS=?"); + ArrayList arrayLists = new ArrayList(); + if (Regex.IsMatch(str, "\\+COPS: .*")) + { + Regex regex = new Regex("\\((\\d+),(?:\"([^\\(\\)\\,]+)\")?,(?:\"([^\\(\\)\\,]+)\")?,(?:\"(\\d+)\")?(?:,([^\\(\\)\\,]+))?\\)"); + Match match = regex.Match(str); + while (match.Success) + { + int num = int.Parse(match.Groups[1].Value); + string value = match.Groups[2].Value; + string value1 = match.Groups[3].Value; + string str1 = match.Groups[4].Value; + string value2 = match.Groups[5].Value; + object[] objArray = new object[5]; + objArray[0] = num; + objArray[1] = value; + objArray[2] = value1; + objArray[3] = str1; + objArray[4] = value2; + this.LogIt(LogLevel.Info, "stat={0}, longAlpha=\"{1}\", shortAlpha=\"{2}\", numeric=\"{3}\", act=\"{4}\"", objArray); + if (Enum.IsDefined(typeof(OperatorStatus), num)) + { + OperatorStatus operatorStatu = (OperatorStatus)Enum.Parse(typeof(OperatorStatus), num.ToString()); + OperatorInfo2 operatorInfo2 = new OperatorInfo2(operatorStatu, value, value1, str1, value2); + arrayLists.Add(operatorInfo2); + match = match.NextMatch(); + } + else + { + throw new CommException(string.Concat("Unknown operator status ", num.ToString(), "."), str); + } + } + int count = arrayLists.Count; + this.LogIt(LogLevel.Info, string.Concat(count.ToString(), " operator(s) enumerated.")); + OperatorInfo2[] operatorInfo2Array1 = new OperatorInfo2[arrayLists.Count]; + arrayLists.CopyTo(operatorInfo2Array1, 0); + operatorInfo2Array = operatorInfo2Array1; + } + else + { + this.HandleCommError(str); + operatorInfo2Array = null; + } + } + return operatorInfo2Array; + } + + private void LogIt(LogLevel level, string text) + { + if (this.LoglineAdded != null && this.logEnabled && level <= this.logLevel) + { + DateTime now = DateTime.Now; + string str = string.Format("{0} [gsmphone] {1}", now.ToString("HH:mm:ss.fff"), text); + LoglineAddedEventArgs loglineAddedEventArg = new LoglineAddedEventArgs(level, str); + lock (this.logQueue) + { + this.logQueue.Enqueue(loglineAddedEventArg); + } + } + } + + private void LogIt(LogLevel level, string text, params object[] args) + { + if (this.LoglineAdded != null && this.logEnabled && level <= this.logLevel) + { + string str = string.Format(text, args); + DateTime now = DateTime.Now; + string str1 = string.Format("{0} [gsmphone] {1}", now.ToString("HH:mm:ss.fff"), str); + LoglineAddedEventArgs loglineAddedEventArg = new LoglineAddedEventArgs(level, str1); + lock (this.logQueue) + { + this.logQueue.Enqueue(loglineAddedEventArg); + } + } + } + + private void LogItShow(LogLevel level, string data, string prefix, bool hideEmptyLines) + { + string[] strArrays = this.SplitLines(data); + if ((int)strArrays.Length != 0) + { + bool flag = false; + for (int i = 0; i < (int)strArrays.Length; i++) + { + if (!hideEmptyLines || hideEmptyLines && strArrays[i].Length > 0) + { + if (flag) + { + this.LogIt(level, strArrays[i].PadLeft(strArrays[i].Length + prefix.Length, ' ')); + } + else + { + this.LogIt(level, string.Concat(prefix, strArrays[i])); + flag = true; + } + } + } + return; + } + else + { + return; + } + } + + private void LogItShow(LogLevel level, string data, string prefix) + { + this.LogItShow(level, data, prefix, false); + } + + private void LogMemoryStatus(MemoryStatus status) + { + int used; + if (status.Total > 0) + { + used = (int)((double)status.Used / (double)status.Total * 100); + } + else + { + used = 0; + } + int num = used; + this.LogIt(LogLevel.Info, string.Format("Memory status: {0}/{1} ({2}% used)", status.Used, status.Total, num)); + } + + private void LogThread() + { + this.logQueue.Clear(); + this.logThreadInitialized.Set(); + while (!this.terminateLogThread.WaitOne(100, false)) + { + while (this.DispatchLog()) + { + } + } + while (this.DispatchLog()) + { + } + this.logQueue.Clear(); + } + + private string MakeArrayString(string[] array) + { + StringBuilder stringBuilder = new StringBuilder(); + string[] strArrays = array; + for (int i = 0; i < (int)strArrays.Length; i++) + { + string str = strArrays[i]; + if (stringBuilder.Length > 0) + { + stringBuilder.Append(", "); + } + stringBuilder.Append(str); + } + return stringBuilder.ToString(); + } + + private string MakeQueueString(Queue queue) + { + StringBuilder stringBuilder = new StringBuilder(); + lock (queue.SyncRoot) + { + foreach (string str in queue) + { + stringBuilder.Append(str); + } + } + return stringBuilder.ToString(); + } + + private PinStatus MapPinStatusStringToStatus(string s) + { + PinStatus pinStatu = PinStatus.Ready; + Dictionary strs = new Dictionary(); + strs["READY"] = PinStatus.Ready; + strs["SIM PIN"] = PinStatus.SimPin; + strs["SIM PUK"] = PinStatus.SimPuk; + strs["PH-SIM PIN"] = PinStatus.PhoneToSimPin; + strs["PH-FSIM PIN"] = PinStatus.PhoneToFirstSimPin; + strs["PH-FSIM PUK"] = PinStatus.PhoneToFirstSimPuk; + strs["SIM PIN2"] = PinStatus.SimPin2; + strs["SIM PUK2"] = PinStatus.SimPuk2; + strs["PH-NET PIN"] = PinStatus.PhoneToNetworkPin; + strs["PH-NET PUK"] = PinStatus.PhoneToNetworkPuk; + strs["PH-NETSUB PIN"] = PinStatus.PhoneToNetworkSubsetPin; + strs["PH-NETSUB PUK"] = PinStatus.PhoneToNetworkSubsetPuk; + strs["PH-SP PIN"] = PinStatus.PhoneToServiceProviderPin; + strs["PH-SP PUK"] = PinStatus.PhoneToServiceProviderPuk; + strs["PH-CORP PIN"] = PinStatus.PhoneToCorporatePin; + strs["PH-CORP PUK"] = PinStatus.PhoneToCorporatePuk; + if (!strs.TryGetValue(s, out pinStatu)) + { + throw new CommException(string.Concat("Unknown PIN status \"", s, "\".")); + } + else + { + return pinStatu; + } + } + + private void OnMessageReceived(IMessageIndicationObject obj) + { + if (this.MessageReceived == null) + { + this.LogIt(LogLevel.Info, "No event handlers for MessageReceived event, message is ignored."); + return; + } + else + { + this.LogIt(LogLevel.Info, "Firing async MessageReceived event."); + MessageReceivedEventArgs messageReceivedEventArg = new MessageReceivedEventArgs(obj); + this.MessageReceived.BeginInvoke(this, messageReceivedEventArg, new AsyncCallback(this.AsyncCallback), null); + return; + } + } + + private void OnPhoneConnected() + { + if (this.PhoneConnected != null) + { + this.LogIt(LogLevel.Info, "Firing async PhoneConnected event."); + this.PhoneConnected.BeginInvoke(this, EventArgs.Empty, new AsyncCallback(this.AsyncCallback), null); + } + } + + private void OnPhoneDisconnected() + { + if (this.PhoneDisconnected != null) + { + this.LogIt(LogLevel.Info, "Firing async PhoneDisconnected event."); + this.PhoneDisconnected.BeginInvoke(this, EventArgs.Empty, new AsyncCallback(this.AsyncCallback), null); + } + } + + private void OnReceiveComplete(int bytesReceived) + { + if (this.ReceiveComplete != null) + { + ProgressEventArgs progressEventArg = new ProgressEventArgs(bytesReceived); + this.ReceiveComplete.BeginInvoke(this, progressEventArg, new AsyncCallback(this.AsyncCallback), null); + } + } + + private void OnReceiveProgress(int bytesReceived) + { + if (this.ReceiveProgress != null) + { + ProgressEventArgs progressEventArg = new ProgressEventArgs(bytesReceived); + this.ReceiveProgress.BeginInvoke(this, progressEventArg, new AsyncCallback(this.AsyncCallback), null); + } + } + + /// + /// Opens the connection to the device. + /// + /// You can check the current connection state with the method. + /// + /// + /// + /// Connection to device already open. + /// Unable to open the port. + public void Open() + { + lock (this) + { + if (!this.IsOpen()) + { + this.CreateLogThread(); + try + { + this.OpenPort(this.portName, this.baudRate, this.timeout); + this.LogIt(LogLevel.Info, string.Concat("Port ", this.portName.ToString(), " now open.")); + } + catch (Exception exception1) + { + Exception exception = exception1; + string str = string.Concat("Unable to open port ", this.portName, ": ", exception.Message); + this.LogIt(LogLevel.Error, str); + this.TerminateLogThread(); + throw new CommException(str, exception); + } + this.CheckConnection(); + try + { + this.CreateCommThread(); + } + catch (Exception exception3) + { + Exception exception2 = exception3; + this.ClosePort(); + this.TerminateLogThread(); + throw new CommException("Unable to create communication thread.", exception2); + } + } + else + { + throw new InvalidOperationException("Port already open."); + } + } + } + + private void OpenPort(string portName, int baudRate, int timeout) + { + this.LogIt(LogLevel.Info, "Initializing serial connection..."); + this.LogIt(LogLevel.Info, string.Concat(" Port = ", portName)); + this.LogIt(LogLevel.Info, string.Concat(" Baud rate = ", baudRate.ToString())); + this.LogIt(LogLevel.Info, string.Concat(" Timeout = ", timeout.ToString())); + if (this.port != null) + { + if (this.port.IsOpen) + { + throw new CommException("Port already open."); + } + } + else + { + SerialPortFixer.Execute(portName); + this.port = new SerialPort(); + } + try + { + this.port.PortName = portName; + this.port.BaudRate = baudRate; + this.port.DataBits = 8; + this.port.StopBits = StopBits.One; + this.port.Parity = Parity.None; + this.port.ReadTimeout = timeout; + this.port.WriteTimeout = timeout; + this.port.Encoding = Encoding.GetEncoding(1252); + this.port.DataReceived += new SerialDataReceivedEventHandler(this.port_DataReceived); + this.port.Open(); + this.port.DtrEnable = true; + this.port.RtsEnable = true; + } + catch (Exception exception) + { + if (this.port.IsOpen) + { + this.port.Close(); + } + throw; + } + } + + /// + /// Parses the memory status response of the CPMS command. + /// + /// A response to the +CPMS set command + /// A object containing the status information of the storages. + /// + /// Data for the ReceiveStorage (mem3) or for the WriteStorage (mem2) is null if there is no information available about it. + /// + private MessageMemoryStatus ParseMessageMemoryStatus(string input) + { + MessageMemoryStatus messageMemoryStatu; + Regex regex = new Regex("\\+CPMS: (\\d+),(\\d+)(?:,(\\d+),(\\d+))?(?:,(\\d+),(\\d+))?"); + Match match = regex.Match(input); + if (!match.Success) + { + messageMemoryStatu = this.TryParseMessageMemoryStatus2(input); + if (messageMemoryStatu == null) + { + this.HandleCommError(input); + return null; + } + } + else + { + messageMemoryStatu = new MessageMemoryStatus(); + int num = int.Parse(match.Groups[1].Value); + int num1 = int.Parse(match.Groups[2].Value); + messageMemoryStatu.ReadStorage = new MemoryStatus(num, num1); + if (match.Groups[3].Captures.Count > 0 && match.Groups[4].Captures.Count > 0) + { + int num2 = int.Parse(match.Groups[3].Value); + int num3 = int.Parse(match.Groups[4].Value); + messageMemoryStatu.WriteStorage = new MemoryStatus(num2, num3); + } + if (match.Groups[5].Captures.Count > 0 && match.Groups[6].Captures.Count > 0) + { + int num4 = int.Parse(match.Groups[5].Value); + int num5 = int.Parse(match.Groups[6].Value); + messageMemoryStatu.ReceiveStorage = new MemoryStatus(num4, num5); + } + } + return messageMemoryStatu; + } + + /// + /// Parses the memory status response of the CPBS command. + /// + /// A response to the +CPBS set command + /// A object containing the memory details. + private MemoryStatusWithStorage ParsePhonebookMemoryStatus(string input) + { + Regex regex = new Regex("\\+CPBS: \"(\\w+)\",(\\d+),(\\d+)"); + Match match = regex.Match(input); + if (!match.Success) + { + this.HandleCommError(input); + return null; + } + else + { + string value = match.Groups[1].Value; + int num = int.Parse(match.Groups[2].Value); + int num1 = int.Parse(match.Groups[3].Value); + MemoryStatusWithStorage memoryStatusWithStorage = new MemoryStatusWithStorage(value, num, num1); + return memoryStatusWithStorage; + } + } + + private void port_DataReceived(object sender, SerialDataReceivedEventArgs e) + { + if (e.EventType == SerialData.Chars) + { + this.receiveNow.Set(); + } + } + + /// + /// AT+CMGR. Reads a single SMS message from the current read/delete storage using the PDU mode. + /// + /// The index of the message to read. + /// A object containing the message at the index specified. + /// Always switches to PDU mode at the beginning. + public ShortMessageFromPhone ReadMessage(int index) + { + ShortMessageFromPhone shortMessageFromPhone; + lock (this) + { + this.VerifyValidConnection(); + this.ActivatePduMode(); + this.LogIt(LogLevel.Info, string.Concat("Reading message from index ", index.ToString(), "...")); + string str = this.ExecAndReceiveMultiple(string.Concat("AT+CMGR=", index.ToString())); + Regex regex = new Regex("\\+CMGR: (\\d+),(?:\"(\\w*)\")?,(\\d+)\\r\\n(\\w+)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + throw new CommException("Unexpected response.", str); + } + else + { + int num = int.Parse(match.Groups[1].Value); + string value = match.Groups[2].Value; + int num1 = int.Parse(match.Groups[3].Value); + string value1 = match.Groups[4].Value; + string[] strArrays = new string[6]; + strArrays[0] = "stat="; + strArrays[1] = num.ToString(); + strArrays[2] = ", alpha=\""; + strArrays[3] = value; + strArrays[4] = "\", length="; + strArrays[5] = num1.ToString(); + this.LogIt(LogLevel.Info, string.Concat(strArrays)); + ShortMessageFromPhone shortMessageFromPhone1 = new ShortMessageFromPhone(index, num, value, num1, value1); + shortMessageFromPhone = shortMessageFromPhone1; + } + } + return shortMessageFromPhone; + } + + /// + /// Gets the entire phonebook of the currently selected phonebook storage. + /// + /// An array of objects. + public PhonebookEntry[] ReadPhonebookEntries() + { + int num = 0; + int num1 = 0; + int num2 = 0; + int num3 = 0; + PhonebookEntry[] phonebookEntryArray; + lock (this) + { + this.GetPhonebookSize(out num, out num1, out num2, out num3); + phonebookEntryArray = this.ReadPhonebookEntries(num, num1); + } + return phonebookEntryArray; + } + + /// + /// AT+CPBR. Gets the specified range of phonebook entries. + /// + /// The first entry to get + /// The last entry to get + /// An array of objects + public PhonebookEntry[] ReadPhonebookEntries(int lowerBound, int upperBound) + { + PhonebookEntry[] phonebookEntryArray; + lock (this) + { + this.VerifyValidConnection(); + object[] objArray = new object[5]; + objArray[0] = "Getting phonebook from index "; + objArray[1] = lowerBound; + objArray[2] = " to "; + objArray[3] = upperBound; + objArray[4] = "..."; + this.LogIt(LogLevel.Info, string.Concat(objArray)); + string str = string.Concat("AT+CPBR=", lowerBound.ToString(), ",", upperBound.ToString()); + string str1 = this.ExecAndReceiveMultiple(str); + phonebookEntryArray = this.DecodePhonebookStream(str1, "+CPBR: "); + } + return phonebookEntryArray; + } + + /// + /// Receives raw data as a string. + /// + /// Receives the data received. + /// true if data was received, otherwise false. + private bool ReceiveInternal(out string input) + { + StringBuilder stringBuilder = new StringBuilder(); + for (string i = this.port.ReadExisting(); i.Length > 0; i = this.port.ReadExisting()) + { + stringBuilder.Append(i); + } + input = stringBuilder.ToString(); + return input.Length > 0; + } + + /// + /// Disables access to the protocol level of the current connection. + /// + /// This method must be called as soon as the execution of the custom commands initiated + /// by is completed and allows for normal operations to continue. + /// + public void ReleaseProtocol() + { + Monitor.Exit(this); + } + + /// + /// AT+CGMI. Requests manufacturer identification. + /// + /// The product manufacturer identification. + public string RequestManufacturer() + { + string str; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Requesting manufacturer..."); + string str1 = this.ExecAndReceiveMultiple("AT+CGMI"); + string str2 = this.TrimLineBreaks(str1); + this.LogIt(LogLevel.Info, string.Concat("Manufacturer = \"", str2, "\"")); + str = str2; + } + return str; + } + + /// + /// AT+CGMM. Requests model identification. + /// + /// The product model identification. + public string RequestModel() + { + string str; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Requesting model..."); + string str1 = this.ExecAndReceiveMultiple("AT+CGMM"); + string str2 = this.TrimLineBreaks(str1); + this.LogIt(LogLevel.Info, string.Concat("Model = \"", str2, "\"")); + str = str2; + } + return str; + } + + /// + /// AT+CGMR. Requests revision identification. + /// + /// The product revision identification. + public string RequestRevision() + { + string str; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Requesting revision..."); + string str1 = this.ExecAndReceiveMultiple("AT+CGMR"); + string str2 = this.TrimLineBreaks(str1); + this.LogIt(LogLevel.Info, string.Concat("Revision = \"", str2, "\"")); + str = str2; + } + return str; + } + + /// + /// AT+CGSN. Requests serial number identification. + /// + /// The product serial number. + public string RequestSerialNumber() + { + string str; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Requesting serial number..."); + string str1 = this.ExecAndReceiveMultiple("AT+CGSN"); + string str2 = this.TrimLineBreaks(str1); + this.LogIt(LogLevel.Info, string.Concat("Serial Number = \"", str2, "\"")); + str = str2; + } + return str; + } + + /// + /// ATZ. Settings that are not stored in a profile will be reset to their factory defaults. + /// + public void ResetToDefaultConfig() + { + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Resetting to default configuration..."); + this.ExecAndReceiveMultiple("ATZ"); + } + } + + /// + /// AT+CSCS. Selects the character set. + /// + /// The character set to use. + /// This command informs the data card of which character set is used by the TE. The data card is + /// then able to convert character strings correctly between TE and ME character sets. When the data card-TE + /// interface is set to 8-bit operation and the TE uses a 7-bit alphabet, the highest bit shall be set to + /// zero. This setting affects text mode SMS data and alpha fields in the phone book memory. If the ME is + /// using the GSM default alphabet, its characters shall be padded with the 8th bit (zero) before + /// converting them to hexadecimal numbers (that is, a 7-bit alphabet is not packed in the SMS-style + /// packing). + /// + /// + /// + public void SelectCharacterSet(string charset) + { + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, string.Concat("Selecting character set \"", charset, "\"...")); + this.ExecAndReceiveMultiple(string.Concat("AT+CSCS=\"", charset, "\"")); + } + } + + /// + /// AT+CSMS. Selects the specified messaging service. + /// + /// The service to select. Specifies the compatibility level of the SMS AT commands. + /// The requirement of service setting 1 depends on specific commands. + /// + /// ME supports mobile terminated messages + /// ME supports mobile originated messages + /// ME supports broadcast type messages + public void SelectMessageService(int service, out int mt, out int mo, out int bm) + { + lock (this) + { + this.VerifyValidConnection(); + string str = this.ExecAndReceiveMultiple(string.Concat("AT+CSMS=", service.ToString())); + Regex regex = new Regex("\\+CSMS: (\\d+),(\\d+),(\\d+)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.HandleCommError(str); + throw new CommException("Unexpected response.", str); + } + else + { + mt = int.Parse(match.Groups[1].Value); + mo = int.Parse(match.Groups[2].Value); + bm = int.Parse(match.Groups[3].Value); + } + } + } + + /// + /// AT+CPBS. Selects the storage to use for phonebook operations. + /// + /// The storage to use. + /// The memory status of the selected storage. + public void SelectPhonebookStorage(string storage) + { + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, string.Concat("Selecting \"", storage, "\" phonebook memory...")); + this.ExecAndReceiveMultiple(string.Concat("AT+CPBS=\"", storage, "\"")); + } + } + + /// + /// AT+CPMS. Selects the storage to use for read and delete operations. + /// + /// The storage to use + /// The memory status of the selected storage. + /// This selects the preferred message storage "mem1" on the device. + public MemoryStatus SelectReadStorage(string storage) + { + MemoryStatus memoryStatu; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, string.Concat("Selecting \"", storage, "\" as read storage...")); + string str = this.ExecAndReceiveMultiple(string.Concat("AT+CPMS=\"", storage, "\"")); + MessageMemoryStatus messageMemoryStatu = this.ParseMessageMemoryStatus(str); + MemoryStatus readStorage = messageMemoryStatu.ReadStorage; + this.LogMemoryStatus(readStorage); + memoryStatu = readStorage; + } + return memoryStatu; + } + + /// + /// AT+CPMS. Selects the storage to use for write and send operations. + /// + /// The storage to use + /// The memory status of the selected storage + /// This selects the preferred message storage "mem2" on the device. Additionaly, the "mem1" storage + /// is also set to the same storage since the read storage must also be set when selecting the + /// write storage. + public MemoryStatus SelectWriteStorage(string storage) + { + MemoryStatus memoryStatu; + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, string.Concat("Selecting \"", storage, "\" as write storage...")); + string str = storage; + string str1 = storage; + string[] strArrays = new string[5]; + strArrays[0] = "AT+CPMS=\""; + strArrays[1] = str; + strArrays[2] = "\",\""; + strArrays[3] = str1; + strArrays[4] = "\""; + string str2 = string.Concat(strArrays); + string str3 = this.ExecAndReceiveMultiple(str2); + MessageMemoryStatus messageMemoryStatu = this.ParseMessageMemoryStatus(str3); + MemoryStatus writeStorage = messageMemoryStatu.WriteStorage; + this.LogMemoryStatus(writeStorage); + memoryStatu = writeStorage; + } + return memoryStatu; + } + + /// + /// Sends raw data as a string. + /// + /// The data to send. + /// Specifies if the data sent should be logged. + /// Send and receive buffers are cleared before sending. + private void SendInternal(string output, bool logIt) + { + if (logIt) + { + this.LogItShow(LogLevel.Verbose, output, "<< "); + } + this.port.DiscardOutBuffer(); + this.port.DiscardInBuffer(); + this.port.Write(output); + } + + /// + /// AT+CMGS. Sends an SMS message using PDU mode. + /// + /// The PDU stream to send + /// The actual length of the PDU (not counting the SMSC data) + /// The message reference + /// Always switches to PDU mode at the beginning. + public byte SendMessage(string pdu, int actualLength) + { + byte num; + lock (this) + { + this.VerifyValidConnection(); + this.ActivatePduMode(); + this.LogIt(LogLevel.Info, "Sending message..."); + this.ExecAndReceiveAnything(string.Concat("AT+CMGS=", actualLength.ToString()), "\\r\\n> "); + this.Send(string.Concat(pdu, (char)26)); + string str = this.ReceiveMultiple(); + byte num1 = 0; + Regex regex = new Regex("\\+CMGS: (\\d+)(,(\\w+))*"); + Match match = regex.Match(str); + if (!match.Success) + { + this.LogIt(LogLevel.Warning, string.Concat("Could not get message reference. Answer received was: \"", str, "\"")); + } + else + { + num1 = (byte)int.Parse(match.Groups[1].Value); + this.LogIt(LogLevel.Info, string.Concat("Message reference = ", num1.ToString())); + } + num = num1; + } + return num; + } + + /// + /// AT+CNMI. Selects the procedure for indicating new messages received from the network. + /// + /// A structure containing the + /// detailed settings. + /// The function switches to the PDU mode before setting the notifications. This + /// causes all short messages, that are directly routed, to be presented in PDU mode. If the mode + /// is changed (such as a switch to the text mode), all indications (containing a message) following the + /// change are sent in the new mode. + /// + public void SetMessageIndications(MessageIndicationSettings settings) + { + lock (this) + { + this.VerifyValidConnection(); + this.ActivatePduMode(); + this.LogIt(LogLevel.Info, "Setting message notifications..."); + object[] mode = new object[5]; + mode[0] = settings.Mode; + mode[1] = settings.DeliverStyle; + mode[2] = settings.CellBroadcastStyle; + mode[3] = settings.StatusReportStyle; + mode[4] = settings.BufferSetting; + string str = string.Format("AT+CNMI={0},{1},{2},{3},{4}", mode); + this.ExecAndReceiveMultiple(str); + } + } + + /// + /// AT+CMMS. Sets the SMS batch mode. + /// + /// The new mode to set. + public void SetMoreMessagesToSend(MoreMessagesMode mode) + { + lock (this) + { + this.VerifyValidConnection(); + object[] objArray = new object[1]; + objArray[0] = mode; + this.LogIt(LogLevel.Info, "Setting more messages mode {0}...", objArray); + string str = string.Format("AT+CMMS={0}", (int)mode); + this.ExecAndReceiveMultiple(str); + } + } + + private void SetNewConnectionState(bool newState) + { + if (newState != this.connectionState) + { + this.connectionState = newState; + if (!this.connectionState) + { + this.LogIt(LogLevel.Info, "Phone disconnected."); + this.OnPhoneDisconnected(); + } + else + { + this.LogIt(LogLevel.Info, "Phone connected."); + this.OnPhoneConnected(); + return; + } + } + } + + /// + /// AT+CSCA. Sets the SMS Service Center Address. + /// + /// An object containing the new address + /// This command changes the SMSC address, through which SMS messages are transmitted. + /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is + /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals + /// zero. + public void SetSmscAddress(AddressData data) + { + lock (this) + { + this.VerifyValidConnection(); + object[] address = new object[2]; + address[0] = data.Address; + int typeOfAddress = data.TypeOfAddress; + address[1] = typeOfAddress.ToString(); + this.LogIt(LogLevel.Info, "Setting SMSC address to \"{0}\", type {1}...", address); + int num = data.TypeOfAddress; + string str = string.Format("AT+CSCA=\"{0}\",{1}", data.Address, num.ToString()); + this.ExecAndReceiveMultiple(str); + } + } + + /// + /// AT+CSCA. Sets the SMS Service Center Address. + /// + /// The new SMSC address + /// This command changes the SMSC address, through which SMS messages are transmitted. + /// In text mode, setting is used by send and write commands. In PDU mode, setting is used by the same + /// commands, but only when the length of the SMSC address coded into the PDU data equals zero. + public void SetSmscAddress(string address) + { + lock (this) + { + this.VerifyValidConnection(); + object[] objArray = new object[1]; + objArray[0] = address; + this.LogIt(LogLevel.Info, "Setting SMSC address to \"{0}\"...", objArray); + string str = string.Format("AT+CSCA=\"{0}\"", address); + this.ExecAndReceiveMultiple(str); + } + } + + private string[] SplitLines(string data) + { + char chr = '\r'; + char chr1 = '\n'; + char chr2 = '\r'; + data = data.Replace(string.Concat(chr.ToString(), chr1.ToString()), chr2.ToString()); + char[] chrArray = new char[1]; + chrArray[0] = '\r'; + return data.Split(chrArray); + } + + private void TerminateCommThread() + { + if (this.IsCommThreadRunning()) + { + this.terminateCommThread.Set(); + try + { + if (!this.commThread.Join(10000)) + { + this.LogIt(LogLevel.Warning, "Communication thread did not exit within the timeout, aborting thread."); + this.commThread.Abort(); + } + } + catch (Exception exception1) + { + Exception exception = exception1; + this.LogIt(LogLevel.Error, string.Concat("Error while terminating comm thread: ", exception.Message)); + } + this.commThread = null; + } + } + + private void TerminateLogThread() + { + if (this.logThread != null && this.logThread.IsAlive) + { + this.LogIt(LogLevel.Info, "Log thread is terminating."); + this.terminateLogThread.Set(); + try + { + if (!this.logThread.Join(10000)) + { + this.logThread.Abort(); + } + } + catch (Exception exception) + { + } + this.logThread = null; + } + } + + /// + /// Removes all leading and trailing line termination characters from a string. + /// + /// The string to trim. + /// The modified string. + private string TrimLineBreaks(string input) + { + char[] chrArray = new char[2]; + chrArray[0] = '\r'; + chrArray[1] = '\n'; + return input.Trim(chrArray); + } + + /// + /// Tries to parse an alternative memory status response of the CPMS command. + /// + /// A response to the +CPMS set command + /// A object containing the status information of the storages + /// if successful, otherwise null. + private MessageMemoryStatus TryParseMessageMemoryStatus2(string input) + { + MessageMemoryStatus messageMemoryStatu = null; + Regex regex = new Regex("\\+CPMS: \"(\\w+)\",(\\d+),(\\d+),\"(\\w+)\",(\\d+),(\\d+)(?:,\"(\\w+)\",(\\d+),(\\d+))?"); + Match match = regex.Match(input); + if (match.Success) + { + messageMemoryStatu = new MessageMemoryStatus(); + string value = match.Groups[1].Value; + int num = int.Parse(match.Groups[2].Value); + int num1 = int.Parse(match.Groups[3].Value); + string str = match.Groups[4].Value; + int num2 = int.Parse(match.Groups[5].Value); + int num3 = int.Parse(match.Groups[6].Value); + messageMemoryStatu.ReadStorage = new MemoryStatusWithStorage(value, num, num1); + messageMemoryStatu.WriteStorage = new MemoryStatusWithStorage(str, num2, num3); + if (match.Groups[7].Captures.Count > 0) + { + string value1 = match.Groups[7].Value; + int num4 = int.Parse(match.Groups[8].Value); + int num5 = int.Parse(match.Groups[9].Value); + messageMemoryStatu.ReceiveStorage = new MemoryStatusWithStorage(value1, num4, num5); + } + } + return messageMemoryStatu; + } + + private void VerifyValidConnection() + { + if (this.IsOpen()) + { + if (this.connectionState) + { + return; + } + else + { + this.LogIt(LogLevel.Error, "No phone connected."); + throw new CommException("No phone connected."); + } + } + else + { + this.LogIt(LogLevel.Error, "Port not open."); + throw new InvalidOperationException("Port not open."); + } + } + + /// + /// AT+CMGW. Stores an SMS message in the current write/send storage using PDU mode. + /// + /// The message in PDU format + /// The actual length of the PDU (not counting the SMSC data) + /// The status that the message should get when saved. + /// The index of the message. If the index could not be retrieved, zero is returned. + /// The message is saved with a status predefined by the phone + /// This function always switches to the PDU mode at the beginning. + public int WriteMessageToMemory(string pdu, int actualLength, int status) + { + int memoryPart2; + lock (this) + { + this.VerifyValidConnection(); + this.ActivatePduMode(); + this.LogIt(LogLevel.Info, "Writing message to memory..."); + this.ExecAndReceiveAnything(string.Concat("AT+CMGW=", actualLength.ToString(), ",", status.ToString()), "\\r\\n> "); + memoryPart2 = this.WriteMessageToMemoryPart2(pdu); + } + return memoryPart2; + } + + /// + /// AT+CMGW. Stores an SMS message in the current write/send storage using PDU mode. + /// + /// The message in PDU format + /// The actual length of the PDU (not counting the SMSC data) + /// The index of the message. If the index could not be retrieved, zero is returned. + /// The message is saved with a status predefined by the phone + /// This function always switches to the PDU mode at the beginning. + public int WriteMessageToMemory(string pdu, int actualLength) + { + int memoryPart2; + lock (this) + { + this.VerifyValidConnection(); + this.ActivatePduMode(); + this.LogIt(LogLevel.Info, "Writing message to memory..."); + this.ExecAndReceiveAnything(string.Concat("AT+CMGW=", actualLength.ToString()), "\\r\\n> "); + memoryPart2 = this.WriteMessageToMemoryPart2(pdu); + } + return memoryPart2; + } + + private int WriteMessageToMemoryPart2(string pdu) + { + this.Send(string.Concat(pdu, (char)26)); + string str = this.ReceiveMultiple(); + int num = 0; + Regex regex = new Regex("\\+CMGW: (\\d+)"); + Match match = regex.Match(str); + if (!match.Success) + { + this.LogIt(LogLevel.Warning, string.Concat("Could not get message index. Answer received was: \"", str, "\"")); + } + else + { + num = int.Parse(match.Groups[1].Value); + this.LogIt(LogLevel.Info, string.Concat("Message index = ", num.ToString())); + } + return num; + } + + /// + /// AT+CPBW. Creates a new phonebook entry. + /// + /// The entry to write. + /// The property of the entry is ignored, + /// the entry is always saved in the first free location. All other properties must be set + /// correctly. + public void WritePhonebookEntry(PhonebookEntry entry) + { + lock (this) + { + this.VerifyValidConnection(); + this.LogIt(LogLevel.Info, "Writing phonebook entry..."); + object[] number = new object[7]; + number[0] = "AT+CPBW=,\""; + number[1] = entry.Number; + number[2] = "\","; + number[3] = entry.Type; + number[4] = ",\""; + number[5] = entry.Text; + number[6] = "\""; + string str = string.Concat(number); + this.ExecAndReceiveMultiple(str); + } + } + + /// + /// The event that occurs when a new line was added to the log. + /// + public event LoglineAddedEventHandler LoglineAdded; + + /// + /// The event that occurs when a new SMS message was received. + /// + public event MessageReceivedEventHandler MessageReceived; + + /// The event that occurs when the phone is connected. + public event EventHandler PhoneConnected; + + /// The event that occurs when the phone is disconnected. + public event EventHandler PhoneDisconnected; + + /// The event that occurs when receiving from the phone is completed. + /// This event is only fired by reading operations that may take longer to complete. + public event ProgressEventHandler ReceiveComplete; + + /// The event that occurs when new data was received from the phone. + /// This event is only fired by reading operations that may take longer to complete. + public event ProgressEventHandler ReceiveProgress; + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/IMessageIndicationObject.cs b/GSMCommunication/GsmCommunication/IMessageIndicationObject.cs new file mode 100644 index 0000000..836ed48 --- /dev/null +++ b/GSMCommunication/GsmCommunication/IMessageIndicationObject.cs @@ -0,0 +1,10 @@ +/// +/// The interface identifying an object as being used for indicating new incoming messages. +/// +namespace GsmComm.GsmCommunication +{ + public interface IMessageIndicationObject + { + + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/IProtocol.cs b/GSMCommunication/GsmCommunication/IProtocol.cs new file mode 100644 index 0000000..ea9da43 --- /dev/null +++ b/GSMCommunication/GsmCommunication/IProtocol.cs @@ -0,0 +1,91 @@ +using System; + +/// +/// Provides an interface for low-level access to the device. +/// +namespace GsmComm.GsmCommunication +{ + public interface IProtocol + { + /// + /// Executes the specified command and reads multiple times from the phone + /// until a specific pattern is detected in the response. + /// + /// The command to execute. + /// The regular expression pattern that the received data must match to stop + /// reading. + /// The response received. + /// + /// + /// + string ExecAndReceiveAnything(string command, string pattern); + + /// Executes the specified command and reads multiple times from the phone + /// until one of the defined message termination patterns is detected in the response. + /// The command to execute. + /// The response received. + /// + /// + /// + string ExecAndReceiveMultiple(string command); + + /// Executes the specified command and reads a single response. + /// The command to execute. + /// The response received. + /// + /// This method returns whatever response comes in from the phone during a single read operation. + /// The response received may not be complete. + /// If you want to ensure that always complete responses are read, use instead. + /// + /// + /// + /// + /// + string ExecCommand(string command); + + /// + /// Executes the specified command and reads a single response. + /// The command to execute. + /// The message text for the exception if no data is received. + /// The response received. + /// + /// This method returns whatever response comes in from the phone during a single read operation. + /// The response received may not be complete. + /// If you want to ensure that always complete responses are read, use instead. + /// + /// + /// + /// + /// + string ExecCommand(string command, string receiveErrorMessage); + + /// + /// Receives raw string data. + /// The data received. + /// true if reception was successful, otherwise false. + /// + bool Receive(out string input); + + /// Reads multiple times from the phone until a specific pattern is detected in the response. + /// The response received. + /// The regular expression pattern that the received data must match to + /// stop reading. Can be an empty string if the pattern should not be checked. + /// + /// + /// + string ReceiveAnything(string pattern); + + /// Reads multiple times from the phone until one of the defined + /// message termination patterns is detected in the response. + /// The response received. + /// + /// + /// + string ReceiveMultiple(); + + /// Sends raw string data. + /// The data to send. + /// + void Send(string output); + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/IdentificationInfo.cs b/GSMCommunication/GsmCommunication/IdentificationInfo.cs new file mode 100644 index 0000000..84d6369 --- /dev/null +++ b/GSMCommunication/GsmCommunication/IdentificationInfo.cs @@ -0,0 +1,79 @@ +using System; + +/// +/// Contains information that identify a mobile phone. +/// +namespace GsmComm.GsmCommunication +{ + public struct IdentificationInfo + { + private string manufacturer; + + private string model; + + private string revision; + + private string serialNumber; + + /// + /// Gets or sets the manufacturer. + /// + public string Manufacturer + { + get + { + return this.manufacturer; + } + set + { + this.manufacturer = value; + } + } + + /// + /// Gets or sets the model. + /// + public string Model + { + get + { + return this.model; + } + set + { + this.model = value; + } + } + + /// + /// Gets or sets the revision. + /// + public string Revision + { + get + { + return this.revision; + } + set + { + this.revision = value; + } + } + + /// + /// Gets or sets the serial number. + /// + public string SerialNumber + { + get + { + return this.serialNumber; + } + set + { + this.serialNumber = value; + } + } + + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/IndicationBufferSetting.cs b/GSMCommunication/GsmCommunication/IndicationBufferSetting.cs new file mode 100644 index 0000000..83f3621 --- /dev/null +++ b/GSMCommunication/GsmCommunication/IndicationBufferSetting.cs @@ -0,0 +1,19 @@ +/// +/// Specifies what should happen to the TA's indication buffer. +/// +namespace GsmComm.GsmCommunication +{ + public enum IndicationBufferSetting + { + /// + /// The TA buffer of unsolicited result codes is flushed to the TE when an + /// other than is entered. + /// + Flush, + /// + /// TA buffer of unsolicited result codes defined within this command is cleared when an + /// other than is entered. + /// + Clear + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/LogLevel.cs b/GSMCommunication/GsmCommunication/LogLevel.cs new file mode 100644 index 0000000..b38e28b --- /dev/null +++ b/GSMCommunication/GsmCommunication/LogLevel.cs @@ -0,0 +1,25 @@ +/// +/// Specifies the level of an entry written to the log. +/// +namespace GsmComm.GsmCommunication +{ + public enum LogLevel + { + /// + /// Error + /// + Error, + /// + /// Warning + /// + Warning, + /// + /// Information + /// + Info, + /// + /// Additional information + /// + Verbose + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/LoglineAddedEventArgs.cs b/GSMCommunication/GsmCommunication/LoglineAddedEventArgs.cs new file mode 100644 index 0000000..3520c08 --- /dev/null +++ b/GSMCommunication/GsmCommunication/LoglineAddedEventArgs.cs @@ -0,0 +1,62 @@ +using System; + +/// +/// Provides data for the event. +/// +namespace GsmComm.GsmCommunication +{ + public class LoglineAddedEventArgs + { + private LogLevel level; + + private string text; + + /// + /// Gets the log level. + /// + public LogLevel Level + { + get + { + return this.level; + } + } + + /// + /// Gets the log text. + /// + public string Text + { + get + { + return this.text; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The log level. + /// The log text. + public LoglineAddedEventArgs(LogLevel level, string text) + { + if (Enum.IsDefined(typeof(LogLevel), level)) + { + if (text != null) + { + this.level = level; + this.text = text; + return; + } + else + { + throw new ArgumentNullException("text"); + } + } + else + { + throw new ArgumentException(string.Concat("Invalid log level \"", level, "\".")); + } + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/LoglineAddedEventHandler.cs b/GSMCommunication/GsmCommunication/LoglineAddedEventHandler.cs new file mode 100644 index 0000000..960bc9d --- /dev/null +++ b/GSMCommunication/GsmCommunication/LoglineAddedEventHandler.cs @@ -0,0 +1,11 @@ +using System; + +/// +/// The method that handles the event. +/// +/// The origin of the event. +/// The data for the event. +namespace GsmComm.GsmCommunication +{ + public delegate void LoglineAddedEventHandler(object sender, LoglineAddedEventArgs e); +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MemoryLocation.cs b/GSMCommunication/GsmCommunication/MemoryLocation.cs new file mode 100644 index 0000000..32b6a97 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MemoryLocation.cs @@ -0,0 +1,47 @@ +using System; + +/// +/// Contains the memory location of a saved message. +/// +namespace GsmComm.GsmCommunication +{ + public class MemoryLocation : IMessageIndicationObject + { + private string storage; + + private int index; + + /// + /// Gets the message index within the specified . + /// + public int Index + { + get + { + return this.index; + } + } + + /// + /// Gets the storage where the message is saved. + /// + public string Storage + { + get + { + return this.storage; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The storage where the message is saved. + /// The message index within the specified storage. + public MemoryLocation(string storage, int index) + { + this.storage = storage; + this.index = index; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MemoryStatus.cs b/GSMCommunication/GsmCommunication/MemoryStatus.cs new file mode 100644 index 0000000..8e99aef --- /dev/null +++ b/GSMCommunication/GsmCommunication/MemoryStatus.cs @@ -0,0 +1,47 @@ +using System; + +/// +/// Contains the memory status of a specific storage. +/// +namespace GsmComm.GsmCommunication +{ + public class MemoryStatus + { + private int used; + + private int total; + + /// + /// Gets the total capacity of the storage. + /// + public int Total + { + get + { + return this.total; + } + } + + /// + /// Gets the number of messages in the storage. + /// + public int Used + { + get + { + return this.used; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of messages in the storage + /// The total capacity of the storage + public MemoryStatus(int used, int total) + { + this.used = used; + this.total = total; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MemoryStatusWithStorage.cs b/GSMCommunication/GsmCommunication/MemoryStatusWithStorage.cs new file mode 100644 index 0000000..b14e0ad --- /dev/null +++ b/GSMCommunication/GsmCommunication/MemoryStatusWithStorage.cs @@ -0,0 +1,34 @@ +using System; + +/// +/// Contains the memory status of a specific storage, including the storage type. +/// +namespace GsmComm.GsmCommunication +{ + public class MemoryStatusWithStorage : MemoryStatus + { + private string storage; + + /// + /// Gets the storage that this memory status applies to. + /// + public string Storage + { + get + { + return this.storage; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The storage that this memory status applies to + /// The number of messages in the storage + /// The total capacity of the storage + public MemoryStatusWithStorage(string storage, int used, int total) : base(used, total) + { + this.storage = storage; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageErrorEventArgs.cs b/GSMCommunication/GsmCommunication/MessageErrorEventArgs.cs new file mode 100644 index 0000000..1b96df5 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageErrorEventArgs.cs @@ -0,0 +1,34 @@ +using GsmComm.PduConverter; +using System; + +/// +/// Provides data for the error events that deal with message sending. +/// +namespace GsmComm.GsmCommunication +{ + public class MessageErrorEventArgs : MessageEventArgs + { + private Exception exception; + + /// + /// Gets the exception that caused the error. + /// + public Exception Exception + { + get + { + return this.exception; + } + } + + /// + /// Initializes a new instance of the . + /// + /// The message that failed sending. + /// The exception that caused the error. + public MessageErrorEventArgs(OutgoingSmsPdu pdu, Exception exception) : base(pdu) + { + this.exception = exception; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageEventArgs.cs b/GSMCommunication/GsmCommunication/MessageEventArgs.cs new file mode 100644 index 0000000..1ef02d5 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageEventArgs.cs @@ -0,0 +1,33 @@ +using GsmComm.PduConverter; +using System; + +/// +/// Provides data for the events that deal with message sending. +/// +namespace GsmComm.GsmCommunication +{ + public class MessageEventArgs : EventArgs + { + private OutgoingSmsPdu pdu; + + /// + /// The message that was dealt with. + /// + public OutgoingSmsPdu Pdu + { + get + { + return this.pdu; + } + } + + /// + /// Initializes a new instance of the . + /// + /// The message that was dealt with. + public MessageEventArgs(OutgoingSmsPdu pdu) + { + this.pdu = pdu; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageIndicationHandlers.cs b/GSMCommunication/GsmCommunication/MessageIndicationHandlers.cs new file mode 100644 index 0000000..9c6a1c4 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageIndicationHandlers.cs @@ -0,0 +1,349 @@ +using GsmComm.PduConverter; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +/// +/// This class contains the handlers for unsolicited messages sent by the phone. It is for use by the GsmPhone +/// class only and must not be made public. +/// +namespace GsmComm.GsmCommunication +{ + internal class MessageIndicationHandlers + { + private const string deliverMemoryIndication = "\\+CMTI: \"(\\w+)\",(\\d+)"; + + private const string deliverMemoryIndicationStart = "\\+CMTI: "; + + private const string deliverPduModeIndication = "\\+CMT: (\\w*),(\\d+)\\r\\n(\\w+)"; + + private const string deliverPduModeIndicationStart = "\\+CMT: "; + + private const string statusReportMemoryIndication = "\\+CDSI: \"(\\w+)\",(\\d+)"; + + private const string statusReportMemoryIndicationStart = "\\+CDSI: "; + + private const string statusReportPduModeIndication = "\\+CDS: (\\d+)\\r\\n(\\w+)"; + + private const string statusReportPduModeIndicationStart = "\\+CDS: "; + + private List messages; + + public MessageIndicationHandlers() + { + this.messages = new List(); + MessageIndicationHandlers.UnsoMessage unsoMessage = new MessageIndicationHandlers.UnsoMessage("\\+CMTI: \"(\\w+)\",(\\d+)", new MessageIndicationHandlers.UnsoHandler(this.HandleDeliverMemoryIndication)); + unsoMessage.StartPattern = "\\+CMTI: "; + unsoMessage.Description = "New SMS-DELIVER received (indicated by memory location)"; + this.messages.Add(unsoMessage); + MessageIndicationHandlers.UnsoMessage unsoCompleteChecker = new MessageIndicationHandlers.UnsoMessage("\\+CMT: (\\w*),(\\d+)\\r\\n(\\w+)", new MessageIndicationHandlers.UnsoHandler(this.HandleDeliverPduModeIndication)); + unsoCompleteChecker.StartPattern = "\\+CMT: "; + unsoCompleteChecker.Description = "New SMS-DELIVER received (indicated by PDU mode version)"; + unsoCompleteChecker.CompleteChecker = new MessageIndicationHandlers.UnsoCompleteChecker(this.IsCompleteDeliverPduModeIndication); + this.messages.Add(unsoCompleteChecker); + MessageIndicationHandlers.UnsoMessage unsoMessage1 = new MessageIndicationHandlers.UnsoMessage("\\+CDSI: \"(\\w+)\",(\\d+)", new MessageIndicationHandlers.UnsoHandler(this.HandleStatusReportMemoryIndication)); + unsoMessage1.StartPattern = "\\+CDSI: "; + unsoMessage1.Description = "New SMS-STATUS-REPORT received (indicated by memory location)"; + this.messages.Add(unsoMessage1); + MessageIndicationHandlers.UnsoMessage unsoCompleteChecker1 = new MessageIndicationHandlers.UnsoMessage("\\+CDS: (\\d+)\\r\\n(\\w+)", new MessageIndicationHandlers.UnsoHandler(this.HandleStatusReportPduModeIndication)); + unsoCompleteChecker1.StartPattern = "\\+CDS: "; + unsoCompleteChecker1.Description = "New SMS-STATUS-REPORT received (indicated by PDU mode version)"; + unsoCompleteChecker1.CompleteChecker = new MessageIndicationHandlers.UnsoCompleteChecker(this.IsCompleteStatusReportPduModeIndication); + this.messages.Add(unsoCompleteChecker1); + } + + private IMessageIndicationObject HandleDeliverMemoryIndication(ref string input) + { + Regex regex = new Regex("\\+CMTI: \"(\\w+)\",(\\d+)"); + Match match = regex.Match(input); + if (match.Success) + { + string value = match.Groups[1].Value; + int num = int.Parse(match.Groups[2].Value); + MemoryLocation memoryLocation = new MemoryLocation(value, num); + input = input.Remove(match.Index, match.Length); + return memoryLocation; + } + else + { + throw new ArgumentException("Input string does not contain an SMS-DELIVER memory location indication."); + } + } + + private IMessageIndicationObject HandleDeliverPduModeIndication(ref string input) + { + Regex regex = new Regex("\\+CMT: (\\w*),(\\d+)\\r\\n(\\w+)"); + Match match = regex.Match(input); + if (match.Success) + { + string value = match.Groups[1].Value; + int num = int.Parse(match.Groups[2].Value); + string str = match.Groups[3].Value; + ShortMessage shortMessage = new ShortMessage(value, num, str); + input = input.Remove(match.Index, match.Length); + return shortMessage; + } + else + { + throw new ArgumentException("Input string does not contain an SMS-DELIVER PDU mode indication."); + } + } + + private IMessageIndicationObject HandleStatusReportMemoryIndication(ref string input) + { + Regex regex = new Regex("\\+CDSI: \"(\\w+)\",(\\d+)"); + Match match = regex.Match(input); + if (match.Success) + { + string value = match.Groups[1].Value; + int num = int.Parse(match.Groups[2].Value); + MemoryLocation memoryLocation = new MemoryLocation(value, num); + input = input.Remove(match.Index, match.Length); + return memoryLocation; + } + else + { + throw new ArgumentException("Input string does not contain an SMS-STATUS-REPORT memory location indication."); + } + } + + private IMessageIndicationObject HandleStatusReportPduModeIndication(ref string input) + { + Regex regex = new Regex("\\+CDS: (\\d+)\\r\\n(\\w+)"); + Match match = regex.Match(input); + if (match.Success) + { + int num = int.Parse(match.Groups[1].Value); + string value = match.Groups[2].Value; + ShortMessage shortMessage = new ShortMessage(string.Empty, num, value); + input = input.Remove(match.Index, match.Length); + return shortMessage; + } + else + { + throw new ArgumentException("Input string does not contain an SMS-STATUS-REPORT PDU mode indication."); + } + } + + /// + /// Handles an unsolicited message of the specified input string. + /// + /// The input string to handle, the unsolicited message will be removed + /// Receives a textual description of the message, may be empty + /// The message indication object generated from the message + /// Input string does not match any of the supported + /// unsolicited messages + public IMessageIndicationObject HandleUnsolicitedMessage(ref string input, out string description) + { + IMessageIndicationObject messageIndicationObject; + List.Enumerator enumerator = this.messages.GetEnumerator(); + try + { + while (enumerator.MoveNext()) + { + MessageIndicationHandlers.UnsoMessage current = enumerator.Current; + if (!current.IsMatch(input)) + { + continue; + } + IMessageIndicationObject messageIndicationObject1 = current.Handler(ref input); + description = current.Description; + messageIndicationObject = messageIndicationObject1; + return messageIndicationObject; + } + throw new ArgumentException("Input string does not match any of the supported unsolicited messages."); + } + finally + { + enumerator.Dispose(); + } + return messageIndicationObject; + } + + private bool IsCompleteDeliverPduModeIndication(string input) + { + Regex regex = new Regex("\\+CMT: (\\w*),(\\d+)\\r\\n(\\w+)"); + Match match = regex.Match(input); + if (match.Success) + { + match.Groups[1].Value; + int num = int.Parse(match.Groups[2].Value); + string value = match.Groups[3].Value; + if (BcdWorker.CountBytes(value) <= 0) + { + return false; + } + else + { + int num1 = BcdWorker.GetByte(value, 0); + int num2 = num * 2 + num1 * 2 + 2; + return value.Length >= num2; + } + } + else + { + return false; + } + } + + private bool IsCompleteStatusReportPduModeIndication(string input) + { + Regex regex = new Regex("\\+CDS: (\\d+)\\r\\n(\\w+)"); + Match match = regex.Match(input); + if (match.Success) + { + int num = int.Parse(match.Groups[1].Value); + string value = match.Groups[2].Value; + if (BcdWorker.CountBytes(value) <= 0) + { + return false; + } + else + { + int num1 = BcdWorker.GetByte(value, 0); + int num2 = num * 2 + num1 * 2 + 2; + return value.Length >= num2; + } + } + else + { + return false; + } + } + + public bool IsIncompleteUnsolicitedMessage(string input) + { + bool flag = false; + foreach (MessageIndicationHandlers.UnsoMessage message in this.messages) + { + if (!message.IsStartMatch(input) || message.IsMatch(input)) + { + continue; + } + flag = true; + break; + } + return flag; + } + + public bool IsUnsolicitedMessage(string input) + { + bool flag = false; + foreach (MessageIndicationHandlers.UnsoMessage message in this.messages) + { + if (!message.IsMatch(input)) + { + continue; + } + flag = true; + break; + } + return flag; + } + + private delegate bool UnsoCompleteChecker(string input); + + private delegate IMessageIndicationObject UnsoHandler(ref string input); + + private class UnsoMessage + { + private string pattern; + + private string startPattern; + + private string description; + + private MessageIndicationHandlers.UnsoHandler handler; + + private MessageIndicationHandlers.UnsoCompleteChecker completeChecker; + + public MessageIndicationHandlers.UnsoCompleteChecker CompleteChecker + { + get + { + return this.completeChecker; + } + set + { + this.completeChecker = value; + } + } + + public string Description + { + get + { + return this.description; + } + set + { + this.description = value; + } + } + + public MessageIndicationHandlers.UnsoHandler Handler + { + get + { + return this.handler; + } + set + { + this.handler = value; + } + } + + public string Pattern + { + get + { + return this.pattern; + } + set + { + this.pattern = value; + } + } + + public string StartPattern + { + get + { + return this.startPattern; + } + set + { + this.startPattern = value; + } + } + + public UnsoMessage(string pattern, MessageIndicationHandlers.UnsoHandler handler) + { + this.pattern = pattern; + this.startPattern = pattern; + this.description = string.Empty; + this.handler = handler; + this.completeChecker = null; + } + + public bool IsMatch(string input) + { + bool flag; + if (this.completeChecker == null) + { + flag = Regex.IsMatch(input, this.pattern); + } + else + { + flag = this.completeChecker(input); + } + return flag; + } + + public bool IsStartMatch(string input) + { + return Regex.IsMatch(input, this.startPattern); + } + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageIndicationMode.cs b/GSMCommunication/GsmCommunication/MessageIndicationMode.cs new file mode 100644 index 0000000..424459a --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageIndicationMode.cs @@ -0,0 +1,30 @@ +/// +/// Specifies the possible modes for for new message indications. +/// +namespace GsmComm.GsmCommunication +{ + public enum MessageIndicationMode + { + /// + /// Buffer unsolicited result codes in the TA. If TA result code buffer is full, indications can be + /// buffered in some other place or the oldest indications may be discarded and replaced with the new + /// received indications. + /// + DoNotForward, + /// + /// Discard indication and reject new received message unsolicited result codes when TA-TE link is + /// reserved (e.g. in on-line data mode). Otherwise forward them directly to the TE. + /// + SkipWhenReserved, + /// + /// Buffer unsolicited result codes in the TA when TA-TE link is reserved (e.g. in on-line data mode) and + /// flush them to the TE after reservation. Otherwise forward them directly to the TE. + /// + BufferAndFlush, + /// + /// Forward unsolicited result codes directly to the TE. TA-TE link specific inband technique used to + /// embed result codes and data when TA is in on-line data mode. + /// + ForwardAlways + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageIndicationSettings.cs b/GSMCommunication/GsmCommunication/MessageIndicationSettings.cs new file mode 100644 index 0000000..6f25138 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageIndicationSettings.cs @@ -0,0 +1,142 @@ +using System; + +/// +/// Specifies the settings for new message notifications. +/// +namespace GsmComm.GsmCommunication +{ + public struct MessageIndicationSettings + { + private int mode; + + private int mt; + + private int bm; + + private int ds; + + private int bfr; + + /// + /// Specifies how the indication buffer should be handled when indications are activated, i.e. + /// when is set to any value except . + /// + /// + /// You can use one of the values to set this property. + /// + public int BufferSetting + { + get + { + return this.bfr; + } + set + { + this.bfr = value; + } + } + + /// + /// Specifies how new Cell Broadcast messages should be indicated. + /// + /// + /// You can use one of the values to set this property. + /// + public int CellBroadcastStyle + { + get + { + return this.bm; + } + set + { + this.bm = value; + } + } + + /// + /// Specifies how new SMS-DELIVER messages should be indicated. + /// + /// + /// You can use one of the values to set this property. + /// + public int DeliverStyle + { + get + { + return this.mt; + } + set + { + this.mt = value; + } + } + + /// + /// Specifies the general indication mode. + /// + /// + /// You can use one of the values to set this property. + /// + public int Mode + { + get + { + return this.mode; + } + set + { + this.mode = value; + } + } + + /// + /// Specifies how new SMS-STATUS-REPORT messages should be indicated. + /// + /// + /// You can use one of the values to set this property. + /// + public int StatusReportStyle + { + get + { + return this.ds; + } + set + { + this.ds = value; + } + } + + /// + /// Initializes a new instance of the structure. + /// + /// Specifies the general indication mode. + /// Specifies how new SMS-DELIVER messages should be indicated. + /// Specifies how new Cell Broadcast messages should be indicated. + /// Specifies how new SMS-STATUS-REPORT messages should be indicated. + /// Specifies how the indication buffer should be handled when indications are activated, i.e. + /// when is set to any value except . + public MessageIndicationSettings(int mode, int mt, int bm, int ds, int bfr) + { + this.mode = mode; + this.mt = mt; + this.bm = bm; + this.ds = ds; + this.bfr = bfr; + } + + /// + /// Initializes a new instance of the structure. + /// + /// Specifies the general indication mode. + /// Specifies how new SMS-DELIVER messages should be indicated. + /// Specifies how new Cell Broadcast messages should be indicated. + /// Specifies how new SMS-STATUS-REPORT messages should be indicated. + /// Specifies how the indication buffer should be handled when indications are activated, i.e. + /// when is set to any value except . + public MessageIndicationSettings(MessageIndicationMode mode, SmsDeliverIndicationStyle mt, CbmIndicationStyle bm, SmsStatusReportIndicationStyle ds, IndicationBufferSetting bfr) : this(mode, mt, bm, ds, bfr) + { + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageIndicationSupport.cs b/GSMCommunication/GsmCommunication/MessageIndicationSupport.cs new file mode 100644 index 0000000..7ddae21 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageIndicationSupport.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections; +using System.Text.RegularExpressions; + +/// +/// Contains information about the supported new message indications of the phone. +/// +namespace GsmComm.GsmCommunication +{ + public class MessageIndicationSupport + { + private string mode; + + private string deliver; + + private string cellBroadcast; + + private string statusReport; + + private string buffer; + + /// + /// Gets a string representation of the supported buffer handling settings. + /// + public string BufferHandling + { + get + { + return this.buffer; + } + } + + /// + /// Gets a string representation of the supported cell broadcast indication styles. + /// + public string CellBroadcastStyles + { + get + { + return this.cellBroadcast; + } + } + + /// + /// Gets a string representation of the supported deliver indication modes. + /// + public string DeliverStyles + { + get + { + return this.deliver; + } + } + + /// + /// Gets a string representation of the supported indication modes. + /// + public string Modes + { + get + { + return this.mode; + } + } + + /// + /// Gets a string representation of the supported status report indication styles. + /// + public string StatusReportStyles + { + get + { + return this.statusReport; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// A string representation of the phone's supported indication modes + /// A string representation of the phones's supported standard SMS (SMS-DELIVER) styles + /// A string representation of the phones's supported cell broadcast styles + /// The phones's supported status report (SMS-STATUS-REPORT) styles + /// The phones's supported buffer handling settings + public MessageIndicationSupport(string mode, string deliver, string cellBroadcast, string statusReport, string buffer) + { + this.mode = mode; + this.deliver = deliver; + this.cellBroadcast = cellBroadcast; + this.statusReport = statusReport; + this.buffer = buffer; + } + + private ArrayList ParseArrayAsString(string s) + { + ArrayList arrayLists = new ArrayList(); + Regex regex = new Regex("(?:(\\d+),?)+"); + Match match = regex.Match(s); + if (match.Success) + { + foreach (Capture capture in match.Groups[1].Captures) + { + arrayLists.Add(int.Parse(capture.Value)); + } + } + Regex regex1 = new Regex("(\\d+)-(\\d+)"); + Match match1 = regex1.Match(s); + if (match1.Success) + { + int num = int.Parse(match1.Groups[1].Value); + int num1 = int.Parse(match1.Groups[2].Value); + for (int i = num; i <= num1; i++) + { + arrayLists.Add(i); + } + } + return arrayLists; + } + + /// + /// Checks if a specific buffer handling setting is supported. + /// + /// The setting to check + /// true if the setting is supported, false otherwise. + public bool SupportsBufferSetting(int setting) + { + ArrayList arrayLists = this.ParseArrayAsString(this.buffer); + return arrayLists.Contains(setting); + } + + /// + /// Checks if a specific buffer handling setting is supported. + /// + /// The setting to check + /// true if the setting is supported, false otherwise. + public bool SupportsBufferSetting(IndicationBufferSetting setting) + { + return this.SupportsBufferSetting(setting); + } + + /// + /// Checks if a specific cell broadcast indication style is supported. + /// + /// The style to check + /// true if the style is supported, false otherwise. + public bool SupportsCellBroadcastStyle(int style) + { + ArrayList arrayLists = this.ParseArrayAsString(this.cellBroadcast); + return arrayLists.Contains(style); + } + + /// + /// Checks if a specific cell broadcast indication style is supported. + /// + /// The style to check + /// true if the style is supported, false otherwise. + public bool SupportsCellBroadcastStyle(CbmIndicationStyle style) + { + return this.SupportsCellBroadcastStyle(style); + } + + /// + /// Checks if a specific SMS-DELIVER indication style is supported. + /// + /// The style to check + /// true if the style is supported, false otherwise. + public bool SupportsDeliverStyle(int style) + { + ArrayList arrayLists = this.ParseArrayAsString(this.deliver); + return arrayLists.Contains(style); + } + + /// + /// Checks if a specific SMS-DELIVER indication style is supported. + /// + /// The style to check + /// true if the style is supported, false otherwise. + public bool SupportsDeliverStyle(SmsDeliverIndicationStyle style) + { + return this.SupportsDeliverStyle(style); + } + + /// + /// Checks if a specific indication mode is supported. + /// + /// The mode to check + /// true if the mode is supported, false otherwise. + public bool SupportsMode(int mode) + { + ArrayList arrayLists = this.ParseArrayAsString(this.mode); + return arrayLists.Contains(mode); + } + + /// + /// Checks if a specific indication mode is supported. + /// + /// The mode to check + /// true if the mode is supported, false otherwise. + public bool SupportsMode(MessageIndicationMode mode) + { + return this.SupportsMode(mode); + } + + /// + /// Checks if a specific status report (SMS-STATUS-REPORT) indication style is supported. + /// + /// The style to check + /// true if the style is supported, false otherwise. + public bool SupportsStatusReportStyle(int style) + { + ArrayList arrayLists = this.ParseArrayAsString(this.statusReport); + return arrayLists.Contains(style); + } + + /// + /// Checks if a specific status report (SMS-STATUS-REPORT) indication style is supported. + /// + /// The style to check + /// true if the style is supported, false otherwise. + public bool SupportsStatusReportStyle(SmsStatusReportIndicationStyle style) + { + return this.SupportsStatusReportStyle(style); + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageMemoryStatus.cs b/GSMCommunication/GsmCommunication/MessageMemoryStatus.cs new file mode 100644 index 0000000..840effd --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageMemoryStatus.cs @@ -0,0 +1,79 @@ +/// +/// Contains status information of all message memories. +/// +namespace GsmComm.GsmCommunication +{ + public class MessageMemoryStatus + { + private MemoryStatus readStorage; + + private MemoryStatus writeStorage; + + private MemoryStatus receiveStorage; + + /// + /// Gets or sets the status of the current read storage. + /// + public MemoryStatus ReadStorage + { + get + { + return this.readStorage; + } + set + { + this.readStorage = value; + } + } + + /// + /// Gets or sets the status of the current receive storage. + /// + public MemoryStatus ReceiveStorage + { + get + { + return this.receiveStorage; + } + set + { + this.receiveStorage = value; + } + } + + /// + /// Gets or sets the status of the current write storage. + /// + public MemoryStatus WriteStorage + { + get + { + return this.writeStorage; + } + set + { + this.writeStorage = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public MessageMemoryStatus() + { + } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// Status of the current read storage + /// Status of the current write storage + /// Status of the current receive storage + public MessageMemoryStatus(MemoryStatus readStorage, MemoryStatus writeStorage, MemoryStatus receiveStorage) + { + this.readStorage = readStorage; + this.writeStorage = writeStorage; + this.receiveStorage = receiveStorage; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageReceivedEventArgs.cs b/GSMCommunication/GsmCommunication/MessageReceivedEventArgs.cs new file mode 100644 index 0000000..0e12289 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageReceivedEventArgs.cs @@ -0,0 +1,30 @@ +/// +/// Provides data for the events. +/// +namespace GsmComm.GsmCommunication +{ + public class MessageReceivedEventArgs + { + private IMessageIndicationObject indicationObject; + + /// + /// The object that indicates a new received message. + /// + public IMessageIndicationObject IndicationObject + { + get + { + return this.indicationObject; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The object that indicates a new received message. + public MessageReceivedEventArgs(IMessageIndicationObject obj) + { + this.indicationObject = obj; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageReceivedEventHandler.cs b/GSMCommunication/GsmCommunication/MessageReceivedEventHandler.cs new file mode 100644 index 0000000..0d7857f --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageReceivedEventHandler.cs @@ -0,0 +1,11 @@ +using System; + +/// +/// The method that handles the event. +/// +/// The origin of the event. +/// The arguments containing more information. +namespace GsmComm.GsmCommunication +{ + public delegate void MessageReceivedEventHandler(object sender, MessageReceivedEventArgs e); +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MessageStorageInfo.cs b/GSMCommunication/GsmCommunication/MessageStorageInfo.cs new file mode 100644 index 0000000..5711932 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MessageStorageInfo.cs @@ -0,0 +1,19 @@ +using System; + +/// +/// Provides a structure that contains details about supported message storages. +/// +namespace GsmComm.GsmCommunication +{ + public struct MessageStorageInfo + { + /// Specifies the storages that can be used for reading and deleting. + public string[] ReadStorages; + + /// Speicifies the storages that can be used for writing and sending. + public string[] WriteStorages; + + /// Specifies the storages that can be used as the preferred receive storage. + public string[] ReceiveStorages; + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/MoreMessagesMode.cs b/GSMCommunication/GsmCommunication/MoreMessagesMode.cs new file mode 100644 index 0000000..d89e352 --- /dev/null +++ b/GSMCommunication/GsmCommunication/MoreMessagesMode.cs @@ -0,0 +1,23 @@ +/// +/// Contains the possible modes for the AT+CMMS command to set the high-speed SMS sending behaviour. +/// +namespace GsmComm.GsmCommunication +{ + public enum MoreMessagesMode + { + /// The function is disabled, the SMS link is not kept open. + Disabled, + /// + /// Keep enabled until the time between the response of the latest message send command (+CMGS, +CMSS, etc.) + /// and the next send command exceeds 1-5 seconds (the exact value is up to ME implementation), then ME shall + /// close the link and TA switches the mode automatically back to disabled (0). + /// + Temporary, + /// + /// Enables (if the time between the response of the latest message send command and the next send command + /// exceeds 1-5 seconds (the exact value is up to ME implementation), ME shall close the link but TA shall + /// not switch automatically back to disabled (0)). + /// + Permanent + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/OperatorFormat.cs b/GSMCommunication/GsmCommunication/OperatorFormat.cs new file mode 100644 index 0000000..1f95fba --- /dev/null +++ b/GSMCommunication/GsmCommunication/OperatorFormat.cs @@ -0,0 +1,18 @@ +/// +/// Contains the possible formats in which a network operator can be returned. +/// +namespace GsmComm.GsmCommunication +{ + public enum OperatorFormat + { + /// Long format, alphanumeric + LongFormatAlphanumeric, + /// Short format, alphanumeric + ShortFormatAlphanumeric, + /// + /// Numeric format, GSM Location Area Identification number (BCD encoded, 3 digits country code, + /// 2 digits network code) + /// + Numeric + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/OperatorInfo.cs b/GSMCommunication/GsmCommunication/OperatorInfo.cs new file mode 100644 index 0000000..287f096 --- /dev/null +++ b/GSMCommunication/GsmCommunication/OperatorInfo.cs @@ -0,0 +1,80 @@ +using System; + +/// +/// Contains information about a GSM network operator. +/// +namespace GsmComm.GsmCommunication +{ + public class OperatorInfo + { + private OperatorFormat format; + + private string theOperator; + + private string accessTechnology; + + /// + /// Gets the access technology registered to. + /// + /// This is optional, as it is only useful for terminals capable to register to more than + /// one access technology. + public string AccessTechnology + { + get + { + return this.accessTechnology; + } + } + + /// + /// Gets the format in which is specified in. + /// + public OperatorFormat Format + { + get + { + return this.format; + } + } + + /// + /// Gets the operator in the format specified by . + /// + public string TheOperator + { + get + { + return this.theOperator; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The format in which theOperator is specified in. See + /// for a list of possible values. + /// + /// The operator in the format specified by format + public OperatorInfo(OperatorFormat format, string theOperator) + { + this.format = format; + this.theOperator = theOperator; + this.accessTechnology = string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// The format in which theOperator is specified in. See + /// for a list of possible values. + /// + /// The operator in the format specified by format + /// The access technology registered to. + public OperatorInfo(OperatorFormat format, string theOperator, string accessTechnology) + { + this.format = format; + this.theOperator = theOperator; + this.accessTechnology = accessTechnology; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/OperatorInfo2.cs b/GSMCommunication/GsmCommunication/OperatorInfo2.cs new file mode 100644 index 0000000..b6b291c --- /dev/null +++ b/GSMCommunication/GsmCommunication/OperatorInfo2.cs @@ -0,0 +1,121 @@ +using System; + +/// +/// Contains information about a GSM network operator. +/// +namespace GsmComm.GsmCommunication +{ + public class OperatorInfo2 + { + private OperatorStatus stat; + + private string longAlpha; + + private string shortAlpha; + + private string numeric; + + private string act; + + /// + /// Gets the access technology the operator uses. + /// + /// This is optional, as it is only useful for terminals capable to register to more than + /// one access technology. + public string AccessTechnology + { + get + { + return this.act; + } + } + + /// + /// Gets the operator name in long alphanumeric format. + /// + /// If the phone does not support this format, the string will be empty. + public string LongAlphanumeric + { + get + { + return this.longAlpha; + } + } + + /// + /// Gets the operator in numeric format. + /// + /// If the phone does not support this format, the string will be empty. + public string Numeric + { + get + { + return this.numeric; + } + } + + /// + /// Gets the operator name in short alphanumic format. + /// + /// If the phone does not support this format, the string will be empty. + public string ShortAlphanumeric + { + get + { + return this.shortAlpha; + } + } + + /// + /// Gets the availability of the operator. + /// + public OperatorStatus Status + { + get + { + return this.stat; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The operator availability + /// The operator name in long alphanumeric format + /// The operator name in short alphanumeric format + /// The operator in numeric format + /// If the phone does not support one of the formats longAlphanumeric, + /// shortAlphanumeric, numeric, the curresponding string is left empty. + public OperatorInfo2(OperatorStatus status, string longAlphanumeric, string shortAlphanumeric, string numeric) + { + this.stat = status; + this.longAlpha = longAlphanumeric; + this.shortAlpha = shortAlphanumeric; + this.numeric = numeric; + this.act = string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// The operator availability + /// The operator name in long alphanumeric format + /// The operator name in short alphanumeric format + /// The operator in numeric format + /// The access technology the operator uses. + /// + /// If the phone does not support one of the formats longAlphanumeric, + /// shortAlphanumeric, numeric, the curresponding string is left empty. + /// The accessTechnology is optional, as it is only useful for terminals capable + /// to register to more than one access technology. + /// + public OperatorInfo2(OperatorStatus status, string longAlphanumeric, string shortAlphanumeric, string numeric, string accessTechnology) + { + this.stat = status; + this.longAlpha = longAlphanumeric; + this.shortAlpha = shortAlphanumeric; + this.numeric = numeric; + this.act = accessTechnology; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/OperatorSelectionMode.cs b/GSMCommunication/GsmCommunication/OperatorSelectionMode.cs new file mode 100644 index 0000000..54fd6c1 --- /dev/null +++ b/GSMCommunication/GsmCommunication/OperatorSelectionMode.cs @@ -0,0 +1,25 @@ +/// +/// Contains the possible values for the operator selection mode. +/// +namespace GsmComm.GsmCommunication +{ + public enum OperatorSelectionMode + { + /// + /// The phone selects the operator automatically. + /// + Automatic = 0, + /// + /// A specific operator is selected. The phone does not attempt to select the operator automatically. + /// + Manual = 1, + /// + /// The phone is not registered to the network. + /// + Deregistered = 2, + /// + /// If manual selection fails, automatic mode is entered. + /// + ManualAutomatic = 4 + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/OperatorStatus.cs b/GSMCommunication/GsmCommunication/OperatorStatus.cs new file mode 100644 index 0000000..c70f816 --- /dev/null +++ b/GSMCommunication/GsmCommunication/OperatorStatus.cs @@ -0,0 +1,17 @@ +/// +/// Conatins the operator availability values. +/// +namespace GsmComm.GsmCommunication +{ + public enum OperatorStatus + { + /// The operator status is unknown. + Unknown, + /// The operator is available for selection. + Available, + /// Denotes that this is the currently selected operator. + Current, + /// The phone must not connect to this operator. + Forbidden + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/PhoneMessageStatus.cs b/GSMCommunication/GsmCommunication/PhoneMessageStatus.cs new file mode 100644 index 0000000..12ae1a4 --- /dev/null +++ b/GSMCommunication/GsmCommunication/PhoneMessageStatus.cs @@ -0,0 +1,19 @@ +/// +/// The message status to request from the phone or the actual type. +/// +namespace GsmComm.GsmCommunication +{ + public enum PhoneMessageStatus + { + /// The message was received, but not yet read. + ReceivedUnread, + /// The message was received and has been read. + ReceivedRead, + /// The message was stored, but had not been sent yet. + StoredUnsent, + /// The message was stored and sent. + StoredSent, + /// Specifies all status. + All + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/PhoneNumberService.cs b/GSMCommunication/GsmCommunication/PhoneNumberService.cs new file mode 100644 index 0000000..8c4dc85 --- /dev/null +++ b/GSMCommunication/GsmCommunication/PhoneNumberService.cs @@ -0,0 +1,21 @@ +/// +/// Contains services related to a subscriber phone number. +/// +namespace GsmComm.GsmCommunication +{ + public enum PhoneNumberService + { + /// Asynchronous modem + AsynchronousModem, + /// Synchronous modem + SynchronousModem, + /// PAD Access (asynchronous) + PadAccess, + /// Packet Access (synchronous) + PacketAccess, + /// Voice + Voice, + /// Fax + Fax + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/PhoneStorageType.cs b/GSMCommunication/GsmCommunication/PhoneStorageType.cs new file mode 100644 index 0000000..2a05fb4 --- /dev/null +++ b/GSMCommunication/GsmCommunication/PhoneStorageType.cs @@ -0,0 +1,20 @@ +using System; + +/// +/// Lists common storage types. +/// +namespace GsmComm.GsmCommunication +{ + public class PhoneStorageType + { + /// SIM storage + public const string Sim = "SM"; + + /// Phone storage + public const string Phone = "ME"; + + public PhoneStorageType() + { + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/PhonebookEntry.cs b/GSMCommunication/GsmCommunication/PhonebookEntry.cs new file mode 100644 index 0000000..b1059dc --- /dev/null +++ b/GSMCommunication/GsmCommunication/PhonebookEntry.cs @@ -0,0 +1,119 @@ +using System; +using System.Xml.Serialization; + +/// +/// Represents a phonebook entry. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + [XmlInclude(typeof(PhonebookEntryWithStorage))] + public class PhonebookEntry + { + private int index; + + private string number; + + private int type; + + private string text; + + /// + /// The index where the entry is saved in the phone. + /// + [XmlAttribute] + public int Index + { + get + { + return this.index; + } + set + { + this.index = value; + } + } + + /// + /// The phone number. + /// + [XmlAttribute] + public string Number + { + get + { + return this.number; + } + set + { + this.number = value; + } + } + + /// + /// The text (name) associated with the . + /// + [XmlAttribute] + public string Text + { + get + { + return this.text; + } + set + { + this.text = value; + } + } + + /// + /// The 's address type. + /// + [XmlAttribute] + public int Type + { + get + { + return this.type; + } + set + { + this.type = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public PhonebookEntry() + { + } + + /// + /// Initializes a new instance of the class using the specified values. + /// + /// The index where the entry is saved in the phone. + /// The phone number. + /// The 's address type. + /// The text (name) associated with the . + public PhonebookEntry(int index, string number, int type, string text) + { + this.index = index; + this.number = number; + this.type = type; + this.text = text; + } + + /// + /// Initializes a new instance of the class to copy an existing . + /// + /// The entry to copy. + public PhonebookEntry(PhonebookEntry entry) + { + this.index = entry.Index; + this.number = entry.Number; + this.type = entry.type; + this.text = entry.Text; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/PhonebookEntryWithStorage.cs b/GSMCommunication/GsmCommunication/PhonebookEntryWithStorage.cs new file mode 100644 index 0000000..b11bdb2 --- /dev/null +++ b/GSMCommunication/GsmCommunication/PhonebookEntryWithStorage.cs @@ -0,0 +1,47 @@ +using System; +using System.Xml.Serialization; + +/// +/// Represents a extended by the storage value. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class PhonebookEntryWithStorage : PhonebookEntry + { + private string storage; + + /// + /// The storage the entry was read from. + /// + [XmlAttribute] + public string Storage + { + get + { + return this.storage; + } + set + { + this.storage = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public PhonebookEntryWithStorage() + { + } + + /// + /// Initializes a new instance of the class using the specified values. + /// + /// The phonebook entry + /// The storage the entry was read from. + public PhonebookEntryWithStorage(PhonebookEntry entry, string storage) : base(entry) + { + this.storage = storage; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/PinStatus.cs b/GSMCommunication/GsmCommunication/PinStatus.cs new file mode 100644 index 0000000..0660784 --- /dev/null +++ b/GSMCommunication/GsmCommunication/PinStatus.cs @@ -0,0 +1,51 @@ +/// +/// Lists the possible PIN states of the phone. +/// +namespace GsmComm.GsmCommunication +{ + public enum PinStatus + { + /// Phone does not wait for any password + Ready, + /// Phone is waiting for SIM PIN to be given + SimPin, + /// Phone is waiting for SIM PUK to be given + SimPuk, + /// Phone is waiting for phone to SIM card password to be given + PhoneToSimPin, + /// Phone is waiting for phone-to-very first SIM card password to be given + PhoneToFirstSimPin, + /// Phone is waiting for phone-to-very first SIM card unblocking password to be given + PhoneToFirstSimPuk, + /// + /// Phone is waiting for SIM PIN2 to be given (this status should be expected to be returned + /// by phones only when the last executed command resulted in PIN2 authentication failure (i.e. device + /// error 17); if PIN2 is not entered right after the failure, the phone should be expected not to block + /// its operation) + /// + SimPin2, + /// + /// Phone is waiting for SIM PUK2 to be given (this status should be expected to be returned + /// by phones only when the last executed command resulted in PUK2 authentication failure (i.e. device + /// error 18); if PUK2 is not entered right after the failure, the phone should be expected not to block + /// its operation) + /// + SimPuk2, + /// Phone is waiting for network personalization password to be given + PhoneToNetworkPin, + /// Phone is waiting for network personalization unblocking password to be given + PhoneToNetworkPuk, + /// Phone is waiting for network subset personalization password to be given + PhoneToNetworkSubsetPin, + /// Phone is waiting for network subset personalization unblocking password to be given + PhoneToNetworkSubsetPuk, + /// Phone is waiting for service provider personalization password to be given + PhoneToServiceProviderPin, + /// Phone is waiting for service provider personalization unblocking password to be given + PhoneToServiceProviderPuk, + /// Phone is waiting for corporate personalization password to be given + PhoneToCorporatePin, + /// Phone is waiting for corporate personalization unblocking password to be given + PhoneToCorporatePuk + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/ProgressEventArgs.cs b/GSMCommunication/GsmCommunication/ProgressEventArgs.cs new file mode 100644 index 0000000..73656a6 --- /dev/null +++ b/GSMCommunication/GsmCommunication/ProgressEventArgs.cs @@ -0,0 +1,32 @@ +using System; + +/// +/// Provides data for the and events. +/// +namespace GsmComm.GsmCommunication +{ + public class ProgressEventArgs : EventArgs + { + private int progress; + + /// + /// Get the current progress value. + /// + public int Progress + { + get + { + return this.progress; + } + } + + /// + /// Initializes a new instance of the event args. + /// + /// The current progress value. + public ProgressEventArgs(int progress) + { + this.progress = progress; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/ProgressEventHandler.cs b/GSMCommunication/GsmCommunication/ProgressEventHandler.cs new file mode 100644 index 0000000..1e44c83 --- /dev/null +++ b/GSMCommunication/GsmCommunication/ProgressEventHandler.cs @@ -0,0 +1,11 @@ +using System; + +/// +/// The method that handles the and the events. +/// +/// The origin of the event. +/// The arguments containing more information. +namespace GsmComm.GsmCommunication +{ + public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/SerialPortFixer.cs b/GSMCommunication/GsmCommunication/SerialPortFixer.cs new file mode 100644 index 0000000..b8316d9 --- /dev/null +++ b/GSMCommunication/GsmCommunication/SerialPortFixer.cs @@ -0,0 +1,213 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace GsmComm.GsmCommunication +{ + internal class SerialPortFixer : IDisposable + { + private const int DcbFlagAbortOnError = 14; + + private const int CommStateRetries = 10; + + private SafeFileHandle m_Handle; + + private SerialPortFixer(string portName) + { + if (portName == null || !portName.StartsWith("COM", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Invalid Serial Port", "portName"); + } + else + { + SafeFileHandle safeFileHandle = SerialPortFixer.CreateFile(string.Concat("\\\\.\\", portName), -1073741824, 0, IntPtr.Zero, 3, 1073741824, IntPtr.Zero); + if (safeFileHandle.IsInvalid) + { + SerialPortFixer.WinIoError(); + } + try + { + int fileType = SerialPortFixer.GetFileType(safeFileHandle); + if (fileType == 2 || fileType == 0) + { + this.m_Handle = safeFileHandle; + this.InitializeDcb(); + } + else + { + throw new ArgumentException("Invalid Serial Port", "portName"); + } + } + catch + { + safeFileHandle.Close(); + this.m_Handle = null; + throw; + } + return; + } + } + + [DllImport("kernel32.dll", CharSet=CharSet.Auto)] + private static extern bool ClearCommError(SafeFileHandle hFile, ref int lpErrors, ref SerialPortFixer.Comstat lpStat); + + [DllImport("kernel32.dll", CharSet=CharSet.Auto)] + private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr securityAttrs, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); + + public void Dispose() + { + if (this.m_Handle != null) + { + this.m_Handle.Close(); + this.m_Handle = null; + } + } + + public static void Execute(string portName) + { + using (SerialPortFixer serialPortFixer = new SerialPortFixer(portName)) + { + } + } + + [DllImport("kernel32.dll", CharSet=CharSet.Auto)] + private static extern int FormatMessage(int dwFlags, HandleRef lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr arguments); + + [DllImport("kernel32.dll", CharSet=CharSet.Auto)] + private static extern bool GetCommState(SafeFileHandle hFile, ref SerialPortFixer.Dcb lpDcb); + + private void GetCommStateNative(ref SerialPortFixer.Dcb lpDcb) + { + int num = 0; + SerialPortFixer.Comstat comstat = new SerialPortFixer.Comstat(); + int num1 = 0; + while (num1 < 10) + { + if (!SerialPortFixer.ClearCommError(this.m_Handle, ref num, ref comstat)) + { + SerialPortFixer.WinIoError(); + } + if (!SerialPortFixer.GetCommState(this.m_Handle, ref lpDcb)) + { + if (num1 == 9) + { + SerialPortFixer.WinIoError(); + } + num1++; + } + else + { + return; + } + } + } + + [DllImport("kernel32.dll", CharSet=CharSet.None)] + private static extern int GetFileType(SafeFileHandle hFile); + + private static string GetMessage(int errorCode) + { + StringBuilder stringBuilder = new StringBuilder(512); + if (SerialPortFixer.FormatMessage(12800, new HandleRef(null, IntPtr.Zero), errorCode, 0, stringBuilder, stringBuilder.Capacity, IntPtr.Zero) == 0) + { + return "Unknown Error"; + } + else + { + return stringBuilder.ToString(); + } + } + + private void InitializeDcb() + { + SerialPortFixer.Dcb flags = new SerialPortFixer.Dcb(); + this.GetCommStateNative(ref flags); + flags.Flags = flags.Flags & -16385; + this.SetCommStateNative(ref flags); + } + + private static int MakeHrFromErrorCode(int errorCode) + { + return -2147024896 | errorCode; + } + + [DllImport("kernel32.dll", CharSet=CharSet.Auto)] + private static extern bool SetCommState(SafeFileHandle hFile, ref SerialPortFixer.Dcb lpDcb); + + private void SetCommStateNative(ref SerialPortFixer.Dcb lpDcb) + { + int num = 0; + SerialPortFixer.Comstat comstat = new SerialPortFixer.Comstat(); + int num1 = 0; + while (num1 < 10) + { + if (!SerialPortFixer.ClearCommError(this.m_Handle, ref num, ref comstat)) + { + SerialPortFixer.WinIoError(); + } + if (!SerialPortFixer.SetCommState(this.m_Handle, ref lpDcb)) + { + if (num1 == 9) + { + SerialPortFixer.WinIoError(); + } + num1++; + } + else + { + return; + } + } + } + + private static void WinIoError() + { + int lastWin32Error = Marshal.GetLastWin32Error(); + throw new IOException(SerialPortFixer.GetMessage(lastWin32Error), SerialPortFixer.MakeHrFromErrorCode(lastWin32Error)); + } + + private struct Comstat + { + public readonly uint Flags; + + public readonly uint cbInQue; + + public readonly uint cbOutQue; + } + + private struct Dcb + { + public readonly uint DCBlength; + + public readonly uint BaudRate; + + public uint Flags; + + public readonly ushort wReserved; + + public readonly ushort XonLim; + + public readonly ushort XoffLim; + + public readonly byte ByteSize; + + public readonly byte Parity; + + public readonly byte StopBits; + + public readonly byte XonChar; + + public readonly byte XoffChar; + + public readonly byte ErrorChar; + + public readonly byte EofChar; + + public readonly byte EvtChar; + + public readonly ushort wReserved1; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/ShortMessage.cs b/GSMCommunication/GsmCommunication/ShortMessage.cs new file mode 100644 index 0000000..bc5f431 --- /dev/null +++ b/GSMCommunication/GsmCommunication/ShortMessage.cs @@ -0,0 +1,90 @@ +using System; +using System.Xml.Serialization; + +/// +/// Represents a short message in undecoded PDU format. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + [XmlInclude(typeof(ShortMessageFromPhone))] + public class ShortMessage : IMessageIndicationObject + { + private string alpha; + + private int length; + + private string data; + + /// + /// The alphabet in which the message is encoded. + /// + [XmlAttribute] + public string Alpha + { + get + { + return this.alpha; + } + set + { + this.alpha = value; + } + } + + /// + /// The actual message. + /// + [XmlElement] + public string Data + { + get + { + return this.data; + } + set + { + this.data = value; + } + } + + /// + /// The length of the message. In PDU format, this is the actual length without the SMSC header. + /// + [XmlAttribute] + public int Length + { + get + { + return this.length; + } + set + { + this.length = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public ShortMessage() + { + this.alpha = string.Empty; + this.length = 0; + this.data = string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// The alphabet in which the message is encoded. + /// The length of the data. + /// The message. + public ShortMessage(string alpha, int length, string data) + { + this.Alpha = alpha; + this.Length = length; + this.Data = data; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/ShortMessageFromPhone.cs b/GSMCommunication/GsmCommunication/ShortMessageFromPhone.cs new file mode 100644 index 0000000..e94bb1d --- /dev/null +++ b/GSMCommunication/GsmCommunication/ShortMessageFromPhone.cs @@ -0,0 +1,73 @@ +using System; +using System.Xml.Serialization; + +/// +/// Represents a short message read from the phone in undecoded PDU format. +/// +namespace GsmComm.GsmCommunication +{ + [Serializable] + public class ShortMessageFromPhone : ShortMessage + { + private int index; + + private int status; + + /// + /// The index of the message. + /// + [XmlAttribute] + public int Index + { + get + { + return this.index; + } + set + { + this.index = value; + } + } + + /// + /// The message status (e.g. read, unread, etc.) + /// + [XmlAttribute] + public int Status + { + get + { + return this.status; + } + set + { + this.status = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public ShortMessageFromPhone() + { + this.index = 0; + this.status = 0; + } + + /// + /// Initializes a new instance of the class. + /// + /// The index where the message is saved in the device in the currently active storage. + /// The message status (e.g. read or unread) + /// The alphabet in which the message is encoded. + /// The length of the data. + /// The actual message. + /// The object contains all data returned by the phone. + /// + public ShortMessageFromPhone(int index, int status, string alpha, int length, string data) : base(alpha, length, data) + { + this.Index = index; + this.Status = status; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/SignalQualityInfo.cs b/GSMCommunication/GsmCommunication/SignalQualityInfo.cs new file mode 100644 index 0000000..612e8e5 --- /dev/null +++ b/GSMCommunication/GsmCommunication/SignalQualityInfo.cs @@ -0,0 +1,51 @@ +using System; + +/// +/// Contains the signal strength as calculcated by the ME. +/// +namespace GsmComm.GsmCommunication +{ + public class SignalQualityInfo + { + private int signalStrength; + + private int bitErrorRate; + + /// + /// Gets the bit error rate. + /// + /// Usually 99 is used if the bit error rate is not known. + public int BitErrorRate + { + get + { + return this.bitErrorRate; + } + } + + /// + /// Gets the signal strength. + /// + /// Usual value is an RSSI value in the range of 0 (no signal) to 31 (best signal), + /// 99 if not known. + public int SignalStrength + { + get + { + return this.signalStrength; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The signal strength, usual as an RSSI value in the range of 0 (no signal) + /// to 31 (best signal), 99 if not known. + /// The bit error rate, 99 if not known. + public SignalQualityInfo(int signalStrength, int bitErrorRate) + { + this.signalStrength = signalStrength; + this.bitErrorRate = bitErrorRate; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/SmsDeliverIndicationStyle.cs b/GSMCommunication/GsmCommunication/SmsDeliverIndicationStyle.cs new file mode 100644 index 0000000..034bf21 --- /dev/null +++ b/GSMCommunication/GsmCommunication/SmsDeliverIndicationStyle.cs @@ -0,0 +1,30 @@ +/// +/// Specifies the possible indication styles for new SMS-DELIVER messages. +/// +namespace GsmComm.GsmCommunication +{ + public enum SmsDeliverIndicationStyle + { + /// + /// No SMS-DELIVER indications are routed to the TE. + /// + Disabled, + /// + /// If SMS-DELIVER is stored into ME/TA, indication of the memory location is routed to the TE. + /// + RouteMemoryLocation, + /// + /// SMS-DELIVERs (except class 2 messages and messages in the message waiting indication + /// group (store message)) are routed directly to the TE. Depending on the currently selected message + /// format, this is done in either PDU or text mode. + /// Class 2 messages and messages in the message waiting indication group (store message) result in + /// the same indication as with . + /// + RouteMessage, + /// + /// Class 3 SMS-DELIVERs are routed directly to TE with the same format as with . + /// Messages of other data coding schemes result in indication as with . + /// + RouteSpecial + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/SmsStatusReportIndicationStyle.cs b/GSMCommunication/GsmCommunication/SmsStatusReportIndicationStyle.cs new file mode 100644 index 0000000..66a01e1 --- /dev/null +++ b/GSMCommunication/GsmCommunication/SmsStatusReportIndicationStyle.cs @@ -0,0 +1,22 @@ +/// +/// Specifies the possible indication settings for new status report messages. +/// +namespace GsmComm.GsmCommunication +{ + public enum SmsStatusReportIndicationStyle + { + /// + /// No SMS-STATUS-REPORTs are routed to the TE. + /// + Disabled, + /// + /// SMS-STATUS-REPORTs are routed to the TE using unsolicited result code. Depending on the currently + /// selected message format, this is done in either PDU or text mode. + /// + RouteMessage, + /// + /// If SMS-STATUS-REPORT is stored into ME/TA, indication of the memory location is routed to the TE. + /// + RouteMemoryLocation + } +} \ No newline at end of file diff --git a/GSMCommunication/GsmCommunication/SubscriberInfo.cs b/GSMCommunication/GsmCommunication/SubscriberInfo.cs new file mode 100644 index 0000000..6db8b07 --- /dev/null +++ b/GSMCommunication/GsmCommunication/SubscriberInfo.cs @@ -0,0 +1,133 @@ +using System; + +/// +/// Contains network subscriber info retrieved from the phone. +/// +namespace GsmComm.GsmCommunication +{ + public class SubscriberInfo + { + private string alpha; + + private string number; + + private int type; + + private int speed; + + private int service; + + private int itc; + + /// + /// Gets an optional alphanumeric string associated with ; + /// used character set is the one selected with . + /// + /// If the string is not defined, it is empty. + public string Alpha + { + get + { + return this.alpha; + } + } + + /// + /// Gets a value for the information transfer capability. + /// + /// Valid values are zero or greater, -1 means this info is not available. + public int Itc + { + get + { + return this.itc; + } + } + + /// + /// Gets the phone number of format specified by . + /// + public string Number + { + get + { + return this.number; + } + } + + /// + /// Gets the service related to the phone number. + /// + /// Valid values are zero or greater, -1 means this info is not available. + /// Some defined values can be found in the enumeration. + /// + public int Service + { + get + { + return this.service; + } + } + + /// + /// Gets a value for the speed for data calls. + /// + /// Valid values are zero or greater, -1 means this info is not available. + public int Speed + { + get + { + return this.speed; + } + } + + /// + /// Gets the type of Address in integer format. + /// + public int Type + { + get + { + return this.type; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Phone number of format specified by type. + /// Type of address in integer format. + public SubscriberInfo(string number, int type) + { + this.alpha = string.Empty; + this.number = number; + this.type = type; + this.speed = -1; + this.service = -1; + this.itc = -1; + } + + /// + /// Initializes a new instance of the class. + /// + /// An optional alphanumeric string associated with number + /// Phone number of format specified by type + /// Type of address in integer format + /// A value for the speed of data calls + /// The service related to the phone number + /// A value for the information transfer capability + /// + /// Valid values for speed, service and itc are zero or greater, + /// set values to -1 where the information is not available. + /// + public SubscriberInfo(string alpha, string number, int type, int speed, int service, int itc) + { + this.alpha = alpha; + this.number = number; + this.type = type; + this.speed = speed; + this.service = service; + this.itc = itc; + } + } +} \ No newline at end of file diff --git a/GSMCommunication/Properties/AssemblyInfo.cs b/GSMCommunication/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1986f90 --- /dev/null +++ b/GSMCommunication/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("Stefan Mayr")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCopyright("Copyright © 2004-2011 Stefan Mayr")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyFileVersion("1.21.0.0")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyTitle("GSM Communication")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("1.21.0.0")] +[assembly: CompilationRelaxations(8)] +[assembly: ComVisible(false)] +[assembly: Guid("eec0C65f-a333-42d5-82b9-163a57814507")] +[assembly: RuntimeCompatibility(WrapNonExceptionThrows=true)] diff --git a/PDUConverter/GsmComm.PduConverter/AddressType.cs b/PDUConverter/GsmComm.PduConverter/AddressType.cs new file mode 100644 index 0000000..1a8067c --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/AddressType.cs @@ -0,0 +1,155 @@ +using System; + +/// +/// Indicates the format of a phone number. +/// +/// +/// The most common value of this octet is 91 hex (10010001 bin), which indicates international format. +/// A phone number in international format looks like 46708251358 (where the country code is 46). +/// In the national (or unknown) format the same phone number would look like 0708251358. The international +/// format is the most generic, and it has to be accepted also when the message is destined to a recipient +/// in the same country as the MSC or as the SGSN. +/// +namespace GsmComm.PduConverter +{ + public class AddressType + { + /// + /// Unknown type of number and numbering plan. + /// + public const byte Unknown = 0; + + /// + /// Unknown type of number, telephone numbering plan. + /// + public const byte UnknownPhone = 129; + + /// + /// International number, telephone numbering plan. + /// + public const byte InternationalPhone = 145; + + private bool bit7; + + private byte ton; + + private byte npi; + + /// + /// The Numbering Plan Identification. + /// + /// + /// The Numbering-plan-identification applies for Type-of-number = 000, 001 and 010. + /// For Type-of-number = 101 bits 3,2,1,0 are reserved and shall be transmitted as 0000. + /// Note that for addressing any of the entities SC, MSC, SGSN or MS, Numbering-plan-identification = 0001 + /// will always be used. However, for addressing the SME, any specified Numbering-plan-identification + /// value may be used. + /// + public byte Npi + { + get + { + return this.npi; + } + set + { + this.npi = value; + } + } + + /// + /// The Type of number. + /// + public byte Ton + { + get + { + return this.ton; + } + set + { + this.ton = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public AddressType() + { + this.bit7 = true; + this.ton = 0; + this.npi = 0; + } + + /// + /// Initializes a new instance of the class using the given value. + /// + /// The Type-of-Address octet to initialize the object with. + public AddressType(byte toa) + { + this.bit7 = (toa & 128) > 0; + this.ton = (byte)(toa >> 4 & 7); + this.npi = (byte)(toa & 15); + } + + public static implicit operator AddressType(byte toa) + { + return new AddressType(toa); + } + + public static implicit operator Byte(AddressType a) + { + return a.ToByte(); + } + + /// + /// Returns the byte equivalent of this instance. + /// + /// The byte value. + public byte ToByte() + { + int num; + if (this.bit7) + { + num = 128; + } + else + { + num = 0; + } + byte num1 = (byte)(num | this.ton << 4 | this.npi); + return num1; + } + + /// + /// Indicates the Numbering Plan Identification (NPI) of the phone number. + /// + public enum NumberingPlan : byte + { + Unknown, + Telephone, + Data, + Telex, + National, + Private, + Ermes, + Reserved + } + + /// + /// Indicates the type of the phone number (TON). + /// + public enum TypeOfNumber : byte + { + Unknown, + International, + National, + NetworkSpecific, + Subscriber, + Alphanumeric, + Abbreviated, + Reserved + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/BcdWorker.cs b/PDUConverter/GsmComm.PduConverter/BcdWorker.cs new file mode 100644 index 0000000..7c62d56 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/BcdWorker.cs @@ -0,0 +1,151 @@ +using System; + +/// +/// A class for working with BCD encoded data strings. +/// +namespace GsmComm.PduConverter +{ + public class BcdWorker + { + public BcdWorker() + { + } + + /// + /// Counts the number of bytes in a BCD encoded string. + /// + /// The string containing the BCD data. + /// The byte count. + public static int CountBytes(string s) + { + return s.Length / 2; + } + + /// + /// Swaps the semi-octets of a BCD encoded string and checks the length. + /// + /// The string to decode. Must be of even length. + /// The converted value. + /// String length is not even. + /// 21436587 becomes 12345678. + public static string DecodeSemiOctets(string data) + { + if (data.Length % 2 == 0) + { + string empty = string.Empty; + for (int i = 0; i < data.Length; i = i + 2) + { + empty = string.Concat(empty, data.Substring(i + 1, 1), data.Substring(i, 1)); + } + return empty; + } + else + { + throw new ArgumentException("String length must be even."); + } + } + + /// + /// Swaps the semi-octets of a BCD encoded string. + /// + /// The string to convert. + /// + /// If the string is not of even length, it is padded with a + /// hexadecimal "F" before converting. + /// This method does not verify the actual contents of the string. + /// + /// The converted value. + /// + /// A string containing "12345678" will become "21436587". + /// A string containing "1234567" will become "214365F7". + /// + public static string EncodeSemiOctets(string data) + { + if (data.Length % 2 != 0) + { + data = string.Concat(data, "F"); + } + string empty = string.Empty; + for (int i = 0; i < data.Length; i = i + 2) + { + empty = string.Concat(empty, data.Substring(i + 1, 1), data.Substring(i, 1)); + } + return empty; + } + + /// + /// Swaps the semi-octets of a BCD encoded string. + /// + /// The string to convert. + /// The width to pad the string to before converting. + /// Padding character is hexadecimal "F". + /// + /// This method does not verify the actual contents of the string. + /// + /// The converted value. + /// totalWidth is not even. + public static string EncodeSemiOctets(string data, int totalWidth) + { + if (totalWidth % 2 == 0) + { + return BcdWorker.EncodeSemiOctets(data.PadRight(totalWidth, 'F')); + } + else + { + throw new ArgumentException("totalWidth must be even.", "totalWidth"); + } + } + + /// + /// Gets a single byte out of a BCD encoded string. + /// + /// The string containing the BCD data. + /// The position in the string to start. + /// The byte at the specified position. + /// No range checking is performed. + public static byte GetByte(string s, int index) + { + string str = s.Substring(index * 2, 2); + return Calc.HexToInt(str)[0]; + } + + /// + /// Gets multiple bytes out of a BCD encoded string. + /// + /// The string containing the BCD data. + /// The position in the string to start. + /// The number of bytes to read. + /// The bytes within the specified range. + /// No range checking is performed. + public static byte[] GetBytes(string s, int index, int length) + { + string str = s.Substring(index * 2, length * 2); + return Calc.HexToInt(str); + } + + /// + /// Gets multiple bytes as string out of a BCD encoded string. + /// + /// The string containing the BCD data. + /// The position in the string to start. + /// The number of bytes to read. + /// The bytes within the specified range. + /// No range checking is performed. + public static string GetBytesString(string s, int index, int length) + { + return s.Substring(index * 2, length * 2); + } + + /// + /// Gets a single byte as string out of a BCD encoded string. + /// + /// The string containing the BCD data. + /// The byte at the specified position. + /// The byte at the specified position. + /// No range checking is performed. + public static string GetByteString(string s, int index) + { + return s.Substring(index * 2, 2); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/Calc.cs b/PDUConverter/GsmComm.PduConverter/Calc.cs new file mode 100644 index 0000000..53e5994 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/Calc.cs @@ -0,0 +1,173 @@ +using System; + +/// +/// Performs various numerical conversions and calculations. +/// +namespace GsmComm.PduConverter +{ + public class Calc + { + private static char[] hexDigits; + + static Calc() + { + char[] chrArray = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + Calc.hexDigits = chrArray; + } + + public Calc() + { + } + + /// + /// Converts a bit string into a byte. + /// + /// The string to convert. + /// The converted value. + public static byte BinToInt(string s) + { + return Convert.ToByte(s, 2); + } + + /// + /// Converts a BCD encoded string (hexadecimal) into its byte representation. + /// + /// The string to convert. + /// + /// The length of the string should be even. This is not checked + /// here to be able to process truncated strings. + /// + /// The converted value. + /// + /// A string containing "41" will become {0x41}, which equals + /// the character 'A'. + /// A string containing "414242" will become {0x41, 0x42, 0x43} + /// which equals the string "ABC". + /// + public static byte[] HexToInt(string s) + { + byte[] num = new byte[s.Length / 2]; + for (int i = 0; i < s.Length / 2; i++) + { + string str = s.Substring(i * 2, 2); + num[i] = Convert.ToByte(str, 16); + } + return num; + } + + /// + /// Converts a byte into a bit string. + /// + /// The byte to convert. + /// + /// The final length the string should have. If the resulting string is + /// shorter than this value, it is padded with leading zeroes. + /// + /// The converted value. + public static string IntToBin(byte b, byte size) + { + return Convert.ToString(b, 2).PadLeft(size, '0'); + } + + /// + /// Converts a byte array into its hexadecimal representation (BCD encoding). + /// + /// The byte array to convert. + /// The converted value. + public static string IntToHex(byte[] bytes) + { + char[] chrArray = new char[(int)bytes.Length * 2]; + for (int i = 0; i < (int)bytes.Length; i++) + { + int num = bytes[i]; + chrArray[i * 2] = Calc.hexDigits[num >> 4]; + chrArray[i * 2 + 1] = Calc.hexDigits[num & 15]; + } + return new string(chrArray); + } + + /// + /// Converts a byte array into its hexadecimal representation (BCD encoding). + /// + /// The byte array to convert. + /// The starting index of the byte array to convert. + /// The number of bytes to convert. + /// The converted value. + public static string IntToHex(byte[] bytes, int index, int count) + { + char[] chrArray = new char[count * 2]; + for (int i = 0; i < count; i++) + { + int num = bytes[index + i]; + chrArray[i * 2] = Calc.hexDigits[num >> 4]; + chrArray[i * 2 + 1] = Calc.hexDigits[num & 15]; + } + return new string(chrArray); + } + + /// + /// Converts a byte into its BCD (hexadecimal) representation. + /// + /// The byte to convert. + /// The converted value. + public static string IntToHex(byte b) + { + return string.Concat(Calc.hexDigits[b >> 4].ToString(), Calc.hexDigits[b & 15].ToString()); + } + + /// + /// Determines if a string is a hexadecimal character. + /// + /// The character to check. + /// true if the character is a hex char, false otherwise. + public static bool IsHexDigit(char c) + { + char upper = char.ToUpper(c); + char[] chrArray = Calc.hexDigits; + int num = 0; + while (num < (int)chrArray.Length) + { + char chr = chrArray[num]; + if (upper != chr) + { + num++; + } + else + { + bool flag = true; + return flag; + } + } + return false; + } + + /// + /// Determines if a string consists only of hexadecimal characters. + /// + /// The string to check. + /// true if the string is a hex string, false otherwise. + public static bool IsHexString(string s) + { + if (s.Length != 0) + { + int num = 0; + while (num < s.Length) + { + if (Calc.IsHexDigit(s[num])) + { + num++; + } + else + { + return false; + } + } + return true; + } + else + { + return false; + } + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/DataCodingScheme.cs b/PDUConverter/GsmComm.PduConverter/DataCodingScheme.cs new file mode 100644 index 0000000..4ab29a8 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/DataCodingScheme.cs @@ -0,0 +1,268 @@ +using System; + +/// +/// Indicates how the user data is encoded, this class represents the TP-DCS field. +/// +/// +/// The TP-Data-Coding-Scheme field, defined in GSM 03.40, indicates the data coding scheme of the TP-UD field, +/// and may indicate a message class. Any reserved codings shall be assumed to be the GSM default alphabet +/// (the same as codepoint 00000000) by a receiving entity. The octet is used according to a coding group +/// which is indicated in bits 7..4 +/// +namespace GsmComm.PduConverter +{ + public abstract class DataCodingScheme + { + private const byte classValid = 16; + + /// + /// Specifies no message class and 7-bit default alphabet. + /// + public const byte NoClass_7Bit = 0; + + /// + /// Specifies message class 0 (immediate display) and 7-bit default alphabet. + /// + public const byte Class0_7Bit = 16; + + /// + /// Specifies message class 1 (ME specific) and 7-bit default alphabet. + /// + public const byte Class1_7Bit = 17; + + /// + /// Specifies message class 2 (SIM specific) and 7-bit default alphabet. + /// + public const byte Class2_7Bit = 18; + + /// + /// Specifies message class 3 (TE specific) and 7-bit default alphabet. + /// + public const byte Class3_7Bit = 19; + + /// + /// Specifies no message class and 8-bit data. + /// + public const byte NoClass_8Bit = 4; + + /// + /// Specifies message class 0 (immediate display) and 8-bit data. + /// + public const byte Class0_8Bit = 20; + + /// + /// Specifies message class 1 (ME specific) and 8-bit data. + /// + public const byte Class1_8Bit = 21; + + /// + /// Specifies message class 2 (SIM specific) and 8-bit data. + /// + public const byte Class2_8Bit = 22; + + /// + /// Specifies message class 3 (TE specific) and 8-bit data. + /// + public const byte Class3_8Bit = 23; + + /// + /// Specifies no message class and UCS2 (16-bit) alphabet. + /// + public const byte NoClass_16Bit = 8; + + /// + /// Specifies message class 0 (immediate display) and UCS2 (16-bit) alphabet. + /// + public const byte Class0_16Bit = 24; + + /// + /// Specifies message class 1 (ME specific) and UCS2 (16-bit) alphabet. + /// + public const byte Class1_16Bit = 25; + + /// + /// Specifies message class 2 (SIM specific) and UCS2 (16-bit) alphabet. + /// + public const byte Class2_16Bit = 26; + + /// + /// Specifies message class 3 (TE specific) and UCS2 (16-bit) alphabet. + /// + public const byte Class3_16Bit = 27; + + /// Offset for bit 7 value. + protected const byte bit7offset = 128; + + /// Offset for bit 6 value. + protected const byte bit6offset = 64; + + /// Offset for bit 5 value. + protected const byte bit5offset = 32; + + /// Offset for bit 4 value. + protected const byte bit4offset = 16; + + /// Offset for bit 3 value. + protected const byte bit3offset = 8; + + /// Offset for bit 2 value. + protected const byte bit2offset = 4; + + /// Offset for bit 1 value. + protected const byte bit1offset = 2; + + /// Offset for bit 0 value. + protected const byte bit0offset = 1; + + private byte codingGroup; + + /// + /// Gets the alphabet being used. + /// + public abstract byte Alphabet + { + get; + } + + /// + /// Gets the coding group, that tells about the further contents of the data coding scheme. + /// + public byte CodingGroup + { + get + { + return this.codingGroup; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + protected DataCodingScheme(byte dcs) + { + this.codingGroup = (byte)(dcs >> 4 & 15); + } + + /// + /// Decodes the given DCS byte. + /// + /// The DCS octet to decode. + /// An object of type or one of its derived classes. + public static DataCodingScheme Decode(byte dcs) + { + DataCodingScheme messageWaitingDiscard; + byte num = (byte)(dcs >> 4 & 15); + if ((dcs & 64) != 0 || (dcs & 128) != 0) + { + byte num1 = num; + switch (num1) + { + case 12: + { + messageWaitingDiscard = new MessageWaitingDiscard(dcs); + break; + } + case 13: + { + messageWaitingDiscard = new MessageWaitingStore(dcs); + break; + } + case 14: + { + messageWaitingDiscard = new MessageWaitingStoreUcs2(dcs); + break; + } + case 15: + { + messageWaitingDiscard = new MessageCoding(dcs); + break; + } + default: + { + messageWaitingDiscard = new ReservedCodingGroup(dcs); + break; + } + } + } + else + { + messageWaitingDiscard = new GeneralDataCoding(dcs); + } + return messageWaitingDiscard; + } + + /// + /// Lists the available alphabets within a general data coding indication DCS. + /// + public enum Alphabets : byte + { + DefaultAlphabet, + EightBit, + Ucs2, + Reserved + } + + /// + /// Data coding/message class. Members can be combined. + /// + /// At least a "Group" member must be specified. + [Flags] + public enum DataCoding + { + Alpha7BitDefault = 0, + Class0 = 0, + Class1 = 1, + Class2 = 2, + Class3 = 3, + Alpha8Bit = 4, + Group_DataCoding = 240 + } + + /// + /// General data coding indication. Members can be combined. + /// + [Flags] + public enum GeneralCoding : byte + { + Uncompressed, + Alpha7BitDefault, + NoClass, + Alpha8Bit, + Alpha16Bit, + AlphaReserved, + Class0, + Class1, + Class2, + Class3, + Compressed + } + + /// + /// Lists the available message codings within a data coding/message class DCS. + /// + public enum MessageCodings : byte + { + DefaultAlphabet, + EightBit + } + + /// + /// Message waiting indication. Members can be combined. + /// + /// At least a "Group" member must be specified. + [Flags] + public enum MessageWaiting : byte + { + SetIndicationInactive, + VoicemailMsgWaiting, + FaxMsgWaiting, + EMailMsgWaiting, + OtherMsgWaiting, + SetIndicationActive, + Group_Discard_7BitDefault, + Group_Store_7BitDefault, + Group_Store_16Bit + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/GeneralDataCoding.cs b/PDUConverter/GsmComm.PduConverter/GeneralDataCoding.cs new file mode 100644 index 0000000..aff04c9 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/GeneralDataCoding.cs @@ -0,0 +1,75 @@ +using System; + +/// +/// General Data Coding indication +/// +namespace GsmComm.PduConverter +{ + public class GeneralDataCoding : DataCodingScheme + { + private bool compressed; + + private bool classSpecified; + + private byte alphabet; + + private byte messageClass; + + /// + /// Gets the alphabet being used. + /// + public override byte Alphabet + { + get + { + return this.alphabet; + } + } + + /// + /// Determines if the property has a message class meaning. If not, + /// the property contains a reserved value and has no message class meaning. + /// + public bool ClassSpecified + { + get + { + return this.classSpecified; + } + } + + /// + /// Gets whether the text is compressed. + /// + public bool Compressed + { + get + { + return this.compressed; + } + } + + /// + /// Gets the message class. + /// + public byte MessageClass + { + get + { + return this.messageClass; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + public GeneralDataCoding(byte dcs) : base(dcs) + { + this.compressed = (dcs & 32) > 0; + this.classSpecified = (dcs & 16) > 0; + this.alphabet = (byte)(dcs >> 2 & 3); + this.messageClass = (byte)(dcs & 3); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/ITimestamp.cs b/PDUConverter/GsmComm.PduConverter/ITimestamp.cs new file mode 100644 index 0000000..480c13c --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/ITimestamp.cs @@ -0,0 +1,15 @@ +/// +/// Represents a common interface for messages to return their relevant +/// timestamp. +/// +namespace GsmComm.PduConverter +{ + public interface ITimestamp + { + /// + /// Returns the relevant timestamp. + /// + /// A structure representing the relevant message timestamp. + SmsTimestamp GetTimestamp(); + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/IncomingMessageFlags.cs b/PDUConverter/GsmComm.PduConverter/IncomingMessageFlags.cs new file mode 100644 index 0000000..f50cf76 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/IncomingMessageFlags.cs @@ -0,0 +1,49 @@ +using System; + +/// +/// The base class for the message flags of incoming messages. +/// +namespace GsmComm.PduConverter +{ + public abstract class IncomingMessageFlags + { + /// + /// Gets the message type. + /// + public abstract IncomingMessageType MessageType + { + get; + } + + protected IncomingMessageFlags() + { + } + + /// + /// In derived classes, converts the specified value into a new instance of the class. + /// + /// A value. + protected abstract void FromByte(byte b); + + public static implicit operator Byte(IncomingMessageFlags flags) + { + return flags.ToByte(); + } + + /// + /// In derived classes, returns the byte equivalent of this instance. + /// + /// The byte value. + public abstract byte ToByte(); + + /// + /// Returns the string equivalent of this instance. + /// + /// The string. + public override string ToString() + { + byte num = this.ToByte(); + return num.ToString(); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/IncomingMessageType.cs b/PDUConverter/GsmComm.PduConverter/IncomingMessageType.cs new file mode 100644 index 0000000..6d18471 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/IncomingMessageType.cs @@ -0,0 +1,15 @@ +/// +/// Specifies the type of the incoming message. +/// +namespace GsmComm.PduConverter +{ + public enum IncomingMessageType + { + /// Specifies that the message is an SMS-DELIVER. + SmsDeliver, + /// Specifies that the message is an SMS-STATUS-REPORT. + SmsStatusReport, + /// Specifies that the message is an SMS-SUBMIT-REPORT. + SmsSubmitReport + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/IncomingSmsPdu.cs b/PDUConverter/GsmComm.PduConverter/IncomingSmsPdu.cs new file mode 100644 index 0000000..c341a8b --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/IncomingSmsPdu.cs @@ -0,0 +1,131 @@ +using System; + +/// +/// Represents an incoming SMS PDU. +/// +namespace GsmComm.PduConverter +{ + public abstract class IncomingSmsPdu : SmsPdu + { + private const byte TP_MTI_SMS_Deliver = 0; + + private const byte TP_MTI_SMS_Submit_Report = 1; + + private const byte TP_MTI_SMS_Status_Report = 2; + + /// + /// The flags for this message. + /// + protected IncomingMessageFlags messageFlags; + + /// + /// Gets the message type. + /// + public IncomingMessageType MessageType + { + get + { + return this.messageFlags.MessageType; + } + } + + protected IncomingSmsPdu() + { + } + + /// + /// Decodes an incoming SMS PDU stream. + /// + /// The PDU string to decode. + /// Specify true if the PDU data contains an SMSC header, otherwise false. + /// The size of the PDU data in bytes, not counting the SMSC header. Set to -1 if unknown. + /// An object representing the decoded message. + public static IncomingSmsPdu Decode(string pdu, bool includesSmscData, int actualLength) + { + if (pdu != string.Empty) + { + int num = 0; + if (includesSmscData) + { + int num1 = num; + num = num1 + 1; + byte num2 = BcdWorker.GetByte(pdu, num1); + if (num2 > 0) + { + num = num + num2; + } + } + int num3 = num; + IncomingMessageType messageType = IncomingSmsPdu.GetMessageType(BcdWorker.GetByte(pdu, num3)); + IncomingMessageType incomingMessageType = messageType; + switch (incomingMessageType) + { + case IncomingMessageType.SmsDeliver: + { + return new SmsDeliverPdu(pdu, includesSmscData, actualLength); + } + case IncomingMessageType.SmsStatusReport: + { + return new SmsStatusReportPdu(pdu, includesSmscData, actualLength); + } + } + throw new NotSupportedException(string.Concat("Message type ", messageType.ToString(), " recognized, but not supported by the SMS decoder.")); + } + else + { + throw new ArgumentException("pdu must not be an empty string."); + } + } + + /// + /// Decodes an incoming SMS PDU stream. + /// + /// The PDU string to decode. + /// Specify true if the PDU data contains an SMSC header, otherwise false. + /// An object representing the decoded message. + /// Use this overload only if you do not know the size of the PDU data. + public static IncomingSmsPdu Decode(string pdu, bool includesSmscData) + { + return IncomingSmsPdu.Decode(pdu, includesSmscData, -1); + } + + private static IncomingMessageType GetMessageType(byte flags) + { + IncomingMessageType incomingMessageType; + int num; + if ((flags & 2) > 0) + { + num = 1; + } + else + { + num = 0; + } + byte num1 = (byte)(num * 2 + (flags & 1)); + byte num2 = num1; + if (num2 == 0) + { + incomingMessageType = IncomingMessageType.SmsDeliver; + } + else if (num2 == 1) + { + incomingMessageType = IncomingMessageType.SmsSubmitReport; + } + else if (num2 == 2) + { + incomingMessageType = IncomingMessageType.SmsStatusReport; + } + else + { + string[] str = new string[5]; + str[0] = "Unknown message type "; + str[1] = num1.ToString(); + str[2] = " (flags="; + str[3] = flags.ToString(); + str[4] = ")"; + throw new ArgumentException(string.Concat(str), "flags"); + } + return incomingMessageType; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/KnownMessageStatus.cs b/PDUConverter/GsmComm.PduConverter/KnownMessageStatus.cs new file mode 100644 index 0000000..23595eb --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/KnownMessageStatus.cs @@ -0,0 +1,63 @@ +using System; + +/// +/// This enumarator represents the known status codes of a TP-ST octet. +/// Reserved and SC specific values are not part of this list. +/// +namespace GsmComm.PduConverter +{ + public enum KnownMessageStatus : byte + { + /// Short message received by the SME. + OK_Received, + /// Short message forwarded by the SC to the SME but the SC is unable to confirm delivery. + OK_NotConfirmed, + /// Short message replaced by the SC. + OK_Replaced, + /// Congestion. + Temp_Congestion, + /// SME busy. + Temp_SmeBusy, + /// No response from SME. + Temp_NoResponseFromSme, + /// Service Rejected. + Temp_ServiceRejected, + /// Quality of service not available. + Temp_QosNotAvailable, + /// Error in SME. + Temp_ErrorInSme, + /// Remote procedure error. + Perm_RemoteProcedureError, + /// Incompatible destination. + Perm_IncompatibleDestination, + /// Connection rejected by SME. + Perm_ConnectionRejectedBySme, + /// Not obtainable. + Perm_NotObtainable, + /// Quality of service not available. + Perm_QosNotAvailable, + /// No interworking available. + Perm_NoInterworkingAvailable, + /// SM Validity Period expired. + Perm_SMValidityPeriodExpired, + /// SM Deleted by originating SME. + Perm_SMDeletedByOriginatingSme, + /// SM Deleted by SC Administration. + Perm_SMDeletedBySCAdministration, + /// SM does not exist (The SM may have previously existed in the SC but the + /// SC no longer has knowledge of it or the SM may never have previously existed in the SC). + Perm_SMDoesNotExist, + /// Congestion. + Ntemp_Congestion, + /// SME busy. + Ntemp_SmeBusy, + /// No response from SME. + Ntemp_NoResponseFromSme, + /// Service rejected. + Ntemp_ServiceRejected, + /// Quality of service not available. + Ntemp_QosNotAvailable, + /// Error in SME. + Ntemp_ErrorInSme + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/MessageCoding.cs b/PDUConverter/GsmComm.PduConverter/MessageCoding.cs new file mode 100644 index 0000000..39a4b7c --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/MessageCoding.cs @@ -0,0 +1,70 @@ +using System; + +/// +/// Data coding/Message class +/// +namespace GsmComm.PduConverter +{ + public class MessageCoding : DataCodingScheme + { + private bool bit3; + + private byte dataCoding; + + private byte messageClass; + + /// + /// Gets the alphabet being used. + /// + public override byte Alphabet + { + get + { + return this.dataCoding; + } + } + + /// + /// Gets the data coding. + /// + public byte DataCoding + { + get + { + return this.dataCoding; + } + } + + /// + /// Gets the message class. + /// + public byte MessageClass + { + get + { + return this.messageClass; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + public MessageCoding(byte dcs) : base(dcs) + { + object obj; + this.bit3 = (dcs & 8) > 0; + MessageCoding messageCoding = this; + if ((dcs & 4) > 0) + { + obj = 1; + } + else + { + obj = null; + } + messageCoding.dataCoding = (byte)obj; + this.messageClass = (byte)(dcs & 3); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/MessageStatus.cs b/PDUConverter/GsmComm.PduConverter/MessageStatus.cs new file mode 100644 index 0000000..a83b6a7 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/MessageStatus.cs @@ -0,0 +1,141 @@ +using System; + +/// +/// TP-ST / TP-Status. +/// +namespace GsmComm.PduConverter +{ + public struct MessageStatus + { + private byte status; + + /// + /// Gets the status category. + /// + /// + /// If the valus does not fall into one of the predefined categories, + /// is returned. + /// + public StatusCategory Category + { + get + { + if (this.status < 0 || this.status > 31) + { + if (this.status < 32 || this.status > 47) + { + if (this.status < 64 || this.status > 95) + { + if (this.status < 96 || this.status > 127) + { + return StatusCategory.Reserved; + } + else + { + return StatusCategory.TemporaryErrorNoRetry; + } + } + else + { + return StatusCategory.PermanentError; + } + } + else + { + return StatusCategory.TemporaryErrorWithRetry; + } + } + else + { + return StatusCategory.Success; + } + } + } + + /// + /// Initializes a new instance of the . + /// + /// The status code. + public MessageStatus(byte status) + { + this.status = status; + } + + /// + /// Initializes a new instance of the . + /// + /// One of the values. + public MessageStatus(KnownMessageStatus status) + { + this.status = (byte)status; + } + + /// + /// Retrieves the known status of the current message status. + /// + /// Ae representing the message status. + /// Check first with before calling this method. + /// Message status is not a known message status. + public KnownMessageStatus GetKnownStatus() + { + if (!this.IsKnownStatus()) + { + throw new ArgumentException(string.Concat(this.status.ToString(), " is not a known message status.")); + } + else + { + return (KnownMessageStatus)Enum.Parse(typeof(KnownMessageStatus), this.status.ToString()); + } + } + + /// + /// Checks if the message status exists within the known status list. + /// + /// true if the status is a known status, otherwise false. + public bool IsKnownStatus() + { + return Enum.IsDefined(typeof(KnownMessageStatus), this.status); + } + + public static implicit operator Byte(MessageStatus s) + { + return s.ToByte(); + } + + public static implicit operator MessageStatus(byte b) + { + return new MessageStatus(b); + } + + public static implicit operator MessageStatus(KnownMessageStatus s) + { + return new MessageStatus(s); + } + + /// + /// Returns the byte representation of the status. + /// + /// A value representing the object's value. + public byte ToByte() + { + return this.status; + } + + /// + /// Returns the string representation of the status. + /// + /// The string representation of the known status if it is a known status, + /// the numerical status value otherwise. + public override string ToString() + { + if (!this.IsKnownStatus()) + { + return this.status.ToString(); + } + else + { + return this.GetKnownStatus().ToString(); + } + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/MessageWaitingDiscard.cs b/PDUConverter/GsmComm.PduConverter/MessageWaitingDiscard.cs new file mode 100644 index 0000000..8281c2a --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/MessageWaitingDiscard.cs @@ -0,0 +1,29 @@ +using System; + +/// +/// Message Waiting Indication Group: Discard Message +/// +namespace GsmComm.PduConverter +{ + public class MessageWaitingDiscard : MessageWaitingIndication + { + /// + /// Gets the alphabet being used. + /// + public override byte Alphabet + { + get + { + return 0; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + public MessageWaitingDiscard(byte dcs) : base(dcs) + { + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/MessageWaitingIndication.cs b/PDUConverter/GsmComm.PduConverter/MessageWaitingIndication.cs new file mode 100644 index 0000000..30cef91 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/MessageWaitingIndication.cs @@ -0,0 +1,50 @@ +using System; + +/// +/// Message waiting indication. This class is abstract. +/// +namespace GsmComm.PduConverter +{ + public abstract class MessageWaitingIndication : DataCodingScheme + { + private bool indicationActive; + + private bool bit2; + + private byte indicationType; + + /// + /// Gets if the indication should be set active. + /// + /// If true, the indication should be set active, if false, the indication should be set inactive. + public bool IndicationActive + { + get + { + return this.indicationActive; + } + } + + /// + /// Gets the indication type, how the indication should be shown. + /// + public byte IndicationType + { + get + { + return this.indicationType; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + public MessageWaitingIndication(byte dcs) : base(dcs) + { + this.indicationType = (byte)(dcs & 3); + this.bit2 = (dcs & 4) > 0; + this.indicationActive = (dcs & 8) > 0; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/MessageWaitingStore.cs b/PDUConverter/GsmComm.PduConverter/MessageWaitingStore.cs new file mode 100644 index 0000000..5d372ec --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/MessageWaitingStore.cs @@ -0,0 +1,29 @@ +using System; + +/// +/// Message Waiting Indication Group: Store Message +/// +namespace GsmComm.PduConverter +{ + public class MessageWaitingStore : MessageWaitingIndication + { + /// + /// Gets the alphabet being used. + /// + public override byte Alphabet + { + get + { + return 0; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + public MessageWaitingStore(byte dcs) : base(dcs) + { + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/MessageWaitingStoreUcs2.cs b/PDUConverter/GsmComm.PduConverter/MessageWaitingStoreUcs2.cs new file mode 100644 index 0000000..7494a86 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/MessageWaitingStoreUcs2.cs @@ -0,0 +1,29 @@ +using System; + +/// +/// Message Waiting Indication Group: Store Message (UCS2) +/// +namespace GsmComm.PduConverter +{ + public class MessageWaitingStoreUcs2 : MessageWaitingIndication + { + /// + /// Gets the alphabet being used. + /// + public override byte Alphabet + { + get + { + return 2; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + public MessageWaitingStoreUcs2(byte dcs) : base(dcs) + { + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/OutgoingMessageFlags.cs b/PDUConverter/GsmComm.PduConverter/OutgoingMessageFlags.cs new file mode 100644 index 0000000..2017877 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/OutgoingMessageFlags.cs @@ -0,0 +1,49 @@ +using System; + +/// +/// The base class for the message flags of outgoing messages. +/// +namespace GsmComm.PduConverter +{ + public abstract class OutgoingMessageFlags + { + /// + /// Gets the message type. + /// + public abstract OutgoingMessageType MessageType + { + get; + } + + protected OutgoingMessageFlags() + { + } + + /// + /// In derived classes, converts the specified value into a new instance of the class. + /// + /// A value. + protected abstract void FromByte(byte b); + + public static implicit operator Byte(OutgoingMessageFlags flags) + { + return flags.ToByte(); + } + + /// + /// In derived classes, returns the byte equivalent of this instance. + /// + /// The byte value. + public abstract byte ToByte(); + + /// + /// Returns the string equivalent of this instance. + /// + /// The string. + public override string ToString() + { + byte num = this.ToByte(); + return num.ToString(); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/OutgoingMessageType.cs b/PDUConverter/GsmComm.PduConverter/OutgoingMessageType.cs new file mode 100644 index 0000000..7ab3bc0 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/OutgoingMessageType.cs @@ -0,0 +1,15 @@ +/// +/// Specifies the type of the outgoing message. +/// +namespace GsmComm.PduConverter +{ + public enum OutgoingMessageType + { + /// Specifies that the message is an SMS-SUBMIT. + SmsSubmit, + /// Specifies that the message is an SMS-COMMAND. + SmsCommand, + /// Specifies that the message is an SMS-DELIVER-REPORT. + SmsDeliverReport + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/OutgoingSmsPdu.cs b/PDUConverter/GsmComm.PduConverter/OutgoingSmsPdu.cs new file mode 100644 index 0000000..a268141 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/OutgoingSmsPdu.cs @@ -0,0 +1,155 @@ +using System; + +/// +/// Represents an outgoing SMS PDU. +/// +namespace GsmComm.PduConverter +{ + public abstract class OutgoingSmsPdu : SmsPdu + { + private const byte TP_MTI_SMS_Deliver_Report = 0; + + private const byte TP_MTI_SMS_Submit = 1; + + private const byte TP_MTI_SMS_Command = 2; + + /// + /// The flags for this message. + /// + protected OutgoingMessageFlags messageFlags; + + /// + /// The message reference. + /// + protected byte messageReference; + + /// + /// Gets or sets the message reference. + /// + /// Represents the TP-Message-Reference octet of the PDU. + /// Normally there is no need to change this property because + /// the reference is set by the sending device.. + /// + public byte MessageReference + { + get + { + return this.messageReference; + } + set + { + this.messageReference = value; + } + } + + /// + /// Gets the message type. + /// + public OutgoingMessageType MessageType + { + get + { + return this.messageFlags.MessageType; + } + } + + /// + /// Initializes a new instance. + /// + protected OutgoingSmsPdu() + { + this.messageReference = 0; + } + + /// + /// Decodes an outgoing SMS PDU stream. + /// + /// The PDU string to decode + /// Specify true if the PDU data contains an SMSC header, otherwise false. + /// The length of the PDU in bytes, not including the SMSC header. + /// An object representing the decoded message. + public static OutgoingSmsPdu Decode(string pdu, bool includesSmscData, int actualLength) + { + if (pdu != string.Empty) + { + int num = 0; + if (includesSmscData) + { + int num1 = num; + num = num1 + 1; + byte num2 = BcdWorker.GetByte(pdu, num1); + if (num2 > 0) + { + num = num + num2; + } + } + int num3 = num; + OutgoingMessageType messageType = OutgoingSmsPdu.GetMessageType(BcdWorker.GetByte(pdu, num3)); + OutgoingMessageType outgoingMessageType = messageType; + if (outgoingMessageType != OutgoingMessageType.SmsSubmit) + { + throw new NotSupportedException(string.Concat("Message type ", messageType.ToString(), " recognized, but not supported by the SMS decoder.")); + } + else + { + return new SmsSubmitPdu(pdu, includesSmscData, actualLength); + } + } + else + { + throw new ArgumentException("pdu must not be an empty string."); + } + } + + /// + /// Decodes an outgoing SMS PDU stream. + /// + /// The PDU string to decode. + /// Specify true if the PDU data contains an SMSC header, otherwise false. + /// An object representing the decoded message. + /// Use this method when the actual length of the message is not known. + public static OutgoingSmsPdu Decode(string pdu, bool includesSmscData) + { + return OutgoingSmsPdu.Decode(pdu, includesSmscData, -1); + } + + private static OutgoingMessageType GetMessageType(byte flags) + { + OutgoingMessageType outgoingMessageType; + int num; + if ((flags & 2) > 0) + { + num = 1; + } + else + { + num = 0; + } + byte num1 = (byte)(num * 2 + (flags & 1)); + byte num2 = num1; + if (num2 == 0) + { + outgoingMessageType = OutgoingMessageType.SmsDeliverReport; + } + else if (num2 == 1) + { + outgoingMessageType = OutgoingMessageType.SmsSubmit; + } + else if (num2 == 2) + { + outgoingMessageType = OutgoingMessageType.SmsCommand; + } + else + { + string[] str = new string[5]; + str[0] = "Unknown message type "; + str[1] = num1.ToString(); + str[2] = " (flags="; + str[3] = flags.ToString(); + str[4] = ")"; + throw new ArgumentException(string.Concat(str), "flags"); + } + return outgoingMessageType; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/ParameterIndicator.cs b/PDUConverter/GsmComm.PduConverter/ParameterIndicator.cs new file mode 100644 index 0000000..e98f069 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/ParameterIndicator.cs @@ -0,0 +1,235 @@ +using System; + +/// +/// TP-PI / TP-Parameter-Indicator. Represents particular optional parameter +/// presence in the fields which follow. +/// +namespace GsmComm.PduConverter +{ + public class ParameterIndicator + { + private const byte bit0 = 1; + + private const byte bit1 = 2; + + private const byte bit2 = 4; + + private const byte bit3 = 8; + + private const byte bit4 = 16; + + private const byte bit5 = 32; + + private const byte bit6 = 64; + + private const byte bit7 = 128; + + private bool tp_pid; + + private bool tp_dcs; + + private bool tp_udl; + + private bool reserved_bit3; + + private bool reserved_bit4; + + private bool reserved_bit5; + + private bool reserved_bit6; + + private bool extension; + + /// + /// When set to true, will indicate that another TP-PI octet follows immediately afterwards. + /// + public bool Extension + { + get + { + return this.extension; + } + set + { + this.extension = value; + } + } + + /// + /// Reserved. If set to true, the receiving entity should ignore + /// this setting. + /// + public bool Reserved_Bit3 + { + get + { + return this.reserved_bit3; + } + set + { + this.reserved_bit3 = value; + } + } + + /// + /// Reserved. If set to true, the receiving entity should ignore + /// this setting. + /// + public bool Reserved_Bit4 + { + get + { + return this.reserved_bit4; + } + set + { + this.reserved_bit4 = value; + } + } + + /// + /// Reserved. If set to true, the receiving entity should ignore + /// this setting. + /// + public bool Reserved_Bit5 + { + get + { + return this.reserved_bit5; + } + set + { + this.reserved_bit5 = value; + } + } + + /// + /// Reserved. If set to true, the receiving entity should ignore + /// this setting. + /// + public bool Reserved_Bit6 + { + get + { + return this.reserved_bit6; + } + set + { + this.reserved_bit6 = value; + } + } + + /// + /// When true, a TP-DCS field is present. + /// + public bool TP_DCS + { + get + { + return this.tp_dcs; + } + set + { + this.tp_dcs = value; + } + } + + /// + /// When true, a TP-PID field is present. + /// + public bool TP_PID + { + get + { + return this.tp_pid; + } + set + { + this.tp_pid = value; + } + } + + /// + /// When false, neither TP-UDL nor TP-UD field can be present. + /// + public bool TP_UDL + { + get + { + return this.tp_udl; + } + set + { + this.tp_udl = value; + } + } + + /// + /// Initializes a new instance of the . + /// + /// The value to initialize the object with. + public ParameterIndicator(byte value) + { + this.tp_pid = (value & 1) > 0; + this.tp_dcs = (value & 2) > 0; + this.tp_udl = (value & 4) > 0; + this.reserved_bit3 = (value & 8) > 0; + this.reserved_bit4 = (value & 16) > 0; + this.reserved_bit5 = (value & 32) > 0; + this.reserved_bit6 = (value & 64) > 0; + this.extension = (value & 128) > 0; + } + + public static implicit operator Byte(ParameterIndicator pi) + { + return pi.ToByte(); + } + + public static implicit operator ParameterIndicator(byte b) + { + return new ParameterIndicator(b); + } + + /// + /// Returns the byte equivalent of this instance. + /// + /// The byte value. + public byte ToByte() + { + byte num = 0; + if (this.tp_pid) + { + num = (byte)(num | 1); + } + if (this.tp_dcs) + { + num = (byte)(num | 2); + } + if (this.tp_udl) + { + num = (byte)(num | 4); + } + if (this.reserved_bit3) + { + num = (byte)(num | 8); + } + if (this.reserved_bit4) + { + num = (byte)(num | 16); + } + if (this.reserved_bit5) + { + num = (byte)(num | 32); + } + if (this.reserved_bit6) + { + num = (byte)(num | 64); + } + if (this.extension) + { + num = (byte)(num | 128); + } + return num; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/PduParts.cs b/PDUConverter/GsmComm.PduConverter/PduParts.cs new file mode 100644 index 0000000..fdea4d0 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/PduParts.cs @@ -0,0 +1,264 @@ +using System; +using System.Text; + +/// +/// Implements decoding routines for SMS PDU parts that are not implemented as separate objects. +/// +namespace GsmComm.PduConverter +{ + public static class PduParts + { + /// + /// Decodes the text from 7-Bit user data. + /// + /// The user data to decode. Must contain an encoded GSM 7-Bit default text packed into octets. + /// The decoded user data. + public static string Decode7BitText(byte[] userData) + { + string septetsStr = TextDataConverter.OctetsToSeptetsStr(userData); + return TextDataConverter.SevenBitToString(septetsStr, true); + } + + /// + /// Decodes an address out of a PDU string. + /// + /// The PDU string to use. + /// The index where to start in the string. + /// The address (phone number) read. + /// The address type of the read address. + public static void DecodeGeneralAddress(string pdu, ref int index, out string address, out byte addressType) + { + int num; + int num1 = index; + int num2 = num1; + index = num1 + 1; + byte num3 = BcdWorker.GetByte(pdu, num2); + int num4 = index; + int num5 = num4; + index = num4 + 1; + addressType = BcdWorker.GetByte(pdu, num5); + if (num3 <= 0) + { + address = string.Empty; + return; + } + else + { + bool flag = false; + if (num3 % 2 != 0) + { + num = num3 + 1; + } + else + { + num = (int)num3; + } + int length = num / 2; + if (index * 2 + length * 2 > pdu.Length - length * 2) + { + length = (pdu.Length - index * 2) / 2; + flag = true; + } + AddressType addressType1 = new AddressType(addressType); + if (addressType1.Ton != 5) + { + string bytesString = BcdWorker.GetBytesString(pdu, index, length); + index = index + length; + if (flag) + { + address = BcdWorker.DecodeSemiOctets(bytesString).Substring(0, length * 2); + return; + } + else + { + address = BcdWorker.DecodeSemiOctets(bytesString).Substring(0, num3); + return; + } + } + else + { + byte[] bytes = BcdWorker.GetBytes(pdu, index, length); + index = index + length; + address = PduParts.Decode7BitText(bytes); + return; + } + } + } + + /// + /// Decodes an SMSC address out of a PDU string. + /// + /// The PDU string to use. + /// The index where to start in the string. + /// The address (phone number) read. + /// The address type of the read address. + public static void DecodeSmscAddress(string pdu, ref int index, out string address, out byte addressType) + { + int num = index; + int num1 = num; + index = num + 1; + byte num2 = BcdWorker.GetByte(pdu, num1); + if (num2 <= 0) + { + addressType = 0; + address = string.Empty; + return; + } + else + { + int num3 = index; + int num4 = num3; + index = num3 + 1; + byte num5 = BcdWorker.GetByte(pdu, num4); + int num6 = num2 - 1; + string bytesString = BcdWorker.GetBytesString(pdu, index, num6); + index = index + num6; + string str = BcdWorker.DecodeSemiOctets(bytesString); + if (str.EndsWith("F") || str.EndsWith("f")) + { + str = str.Substring(0, str.Length - 1); + } + addressType = num5; + address = str; + return; + } + } + + /// + /// Decodes text from user data in the specified data coding scheme. + /// + /// The user data to decode. Must contain text according to the specified data coding scheme. + /// The data coding scheme specified in the PDU. + /// The decoded user data. + public static string DecodeText(byte[] userData, byte dataCodingScheme) + { + string str; + byte alphabet = DataCodingScheme.Decode(dataCodingScheme).Alphabet; + byte num = alphabet; + switch (num) + { + case 0: + { + str = PduParts.Decode7BitText(userData); + break; + } + case 1: + { + Label0: + str = PduParts.Decode7BitText(userData); + break; + } + case 2: + { + str = PduParts.DecodeUcs2Text(userData); + break; + } + default: + { + goto Label0; + } + } + return str; + } + + /// + /// Decodes the text from UCS2 (16-Bit) user data. + /// + /// The user data to decode. Must contain an encoded UCS2 text. + /// The decoded user data. + public static string DecodeUcs2Text(byte[] userData) + { + Encoding bigEndianUnicode = Encoding.BigEndianUnicode; + return bigEndianUnicode.GetString(userData); + } + + /// + /// Gets the user data out of the string. + /// + /// The PDU string to use. + /// The index where to start in the string. + /// The coding that was used to encode the data. Required to determine the proper data length. + /// Receives the user data length in bytes. + /// Received the user data. + /// + /// If there's no data, userDataLength will be set to 0 and userData to null. + /// The decoded data might require further processing, for example 7-bit data (septets) packed + /// into octets, that must be converted back to septets before the data can be used. + /// Processing will stop at the first character that is not hex encountered or if the + /// string ends too early. It will not change the userDataLength read from the string. + /// + public static void DecodeUserData(string pdu, ref int index, byte dcs, out byte userDataLength, out byte[] userData) + { + int num = index; + int num1 = num; + index = num + 1; + byte num2 = BcdWorker.GetByte(pdu, num1); + if (num2 <= 0) + { + userDataLength = 0; + userData = new byte[0]; + return; + } + else + { + int remainingUserDataBytes = PduParts.GetRemainingUserDataBytes(num2, dcs); + int num3 = BcdWorker.CountBytes(pdu) - index; + if (num3 < remainingUserDataBytes) + { + remainingUserDataBytes = num3; + } + string bytesString = BcdWorker.GetBytesString(pdu, index, remainingUserDataBytes); + index = index + remainingUserDataBytes; + string empty = string.Empty; + for (int i = 0; i < bytesString.Length / 2; i++) + { + string byteString = BcdWorker.GetByteString(bytesString, i); + if (!Calc.IsHexString(byteString)) + { + break; + } + empty = string.Concat(empty, byteString); + } + userDataLength = num2; + userData = Calc.HexToInt(empty); + return; + } + } + + /// + /// Calculates the number of bytes that must be present in the user data portion of the PDU. + /// + /// The user data length specified in the PDU. + /// The data coding scheme specified in the PDU. + /// The number of bytes (octets) that must be present, or, if decoding the user data, the number + /// of remaining bytes that must be read. + /// The dataLength and dataCodingScheme parameters are used to + /// calculate the number of bytes that must be present in the user data. + internal static int GetRemainingUserDataBytes(byte dataLength, byte dataCodingScheme) + { + int num; + DataCodingScheme dataCodingScheme1 = DataCodingScheme.Decode(dataCodingScheme); + byte alphabet = dataCodingScheme1.Alphabet; + switch (alphabet) + { + case 0: + { + num = (int)Math.Ceiling((double)dataLength * 7 / 8); + break; + } + case 1: + case 2: + { + num = dataLength; + break; + } + default: + { + num = (int)Math.Ceiling((double)dataLength * 7 / 8); + break; + } + } + return num; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/ProtocolID.cs b/PDUConverter/GsmComm.PduConverter/ProtocolID.cs new file mode 100644 index 0000000..7301932 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/ProtocolID.cs @@ -0,0 +1,213 @@ +using System; + +/// +/// This class and its contained classes contain all possible values for the +/// Protocol Identifier. +/// +/// The members represent the TP-PID octet in the PDU. +namespace GsmComm.PduConverter +{ + public class ProtocolID + { + private const byte niwOffset = 0; + + private const byte iwOffset = 32; + + private const byte nuOffset = 64; + + private const byte resOffset = 128; + + private const byte scOffset = 192; + + private ProtocolID() + { + } + + /// + /// Extracts the "Reserved" part of the PID. + /// + /// The PID containing the value. + /// The value of the "Reserved" part. + /// Value is not from the "Reserved" range. + public static byte GetReservedValue(byte pid) + { + if (ProtocolID.IsReserved(pid)) + { + return (byte)(pid - 128); + } + else + { + throw new ArgumentException("Value is not from the \"Reserved\" range.", "pid"); + } + } + + /// + /// Gets the "SC Specific Use" part of the ProtocolID. + /// + /// The PID byte to decode. + /// The "SC Specific Use" value. + /// Value in pid is not from the "SC specific" range. + public static byte GetSCSpecificUseValue(byte pid) + { + if (ProtocolID.IsSCSpecificUse(pid)) + { + return (byte)(pid - 192); + } + else + { + throw new ArgumentException("Value is not from the \"SC specific\" range.", "pid"); + } + } + + /// + /// Determines if the specified value is from the "Reserved" part. + /// + /// The value to check. + /// true if the value is from the reserved part, false otherwise. + public static bool IsReserved(byte pid) + { + if (pid < 128) + { + return false; + } + else + { + return pid - 128 <= 63; + } + } + + /// + /// Determines if the specified PID is from the "SC Specific Use" part. + /// + /// The value to check. + /// true if the value is for SC specific use, false otherwise. + public static bool IsSCSpecificUse(byte pid) + { + if (pid < 192) + { + return false; + } + else + { + return pid - 192 <= 63; + } + } + + /// + /// Allows the "Reserved" part of the ProtocolID to be used. + /// + /// The value for this part. + /// Value is greater than 0x3F (63). + /// The encoded protocol ID. + public static byte Reserved(byte value) + { + if (value <= 63) + { + return (byte)(128 + value); + } + else + { + throw new ArgumentException("Value must not be greater than 0x3F (63)."); + } + } + + /// + /// Allows the "SC Specific Use" part of the ProtocolID to be used. + /// + /// The value for this part. + /// Value is greater than 0x3F (63). + /// The encoded Protocol ID. + public static byte SCSpecificUse(byte value) + { + if (value <= 63) + { + return (byte)(192 + value); + } + else + { + throw new ArgumentException("Value must not be greater than 0x3F (63)."); + } + } + + /// + /// Telematic interworking. + /// + /// + /// If an interworking protocol is specified in an SMS-SUBMIT PDU, + /// it indicates that the SME is a telematic device of the specified type, + /// and requests the SC to convert the SM into a form suited for that + /// device type. If the destination network is ISDN, the SC must also + /// select the proper service indicators for connecting to a device of + /// that type. + /// If an interworking protocol is specified in an SMS-DELIVER PDU, + /// it indicates that the SME is a telematic device of the specified type. + /// + /// + public enum Interworking : byte + { + Implicit, + Telex, + Group3Telefax, + Group4Telefax, + VoiceTelephone, + Ermes, + PagingSystem, + VideoTex, + Teletex, + TeletexPSPDN, + TeletexCSPDN, + TeletexPSTN, + TeletexISDN, + Uci, + Reserved0E, + Reserved0F, + MessageHandler, + X400BasedHandler, + InternetEMail, + Reserved13, + Reserved14, + Reserved15, + Reserved16, + Reserved17, + SCSpecific1, + SCSpecific2, + SCSpecific3, + SCSpecific4, + SCSpecific5, + SCSpecific6, + SCSpecific7, + GsmMobileStation + } + + /// + /// For network use + /// + /// + /// Details are written in the remarks section of the types. + /// + public enum NetworkUse : byte + { + ShortMessageType0, + ReplaceShortMessageType1, + ReplaceShortMessageType2, + ReplaceShortMessageType3, + ReplaceShortMessageType4, + ReplaceShortMessageType5, + ReplaceShortMessageType6, + ReplaceShortMessageType7, + ReturnCallMessage, + MEDataDownload, + MEDepersonalization, + SIMDataDownload + } + + /// + /// For the straightforward case of simple MS-to-SC short message + /// transfer. No interworking is performed. + /// + public enum NoInterworking : byte + { + SmeToSmeProtocol + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/RelativeValidityPeriod.cs b/PDUConverter/GsmComm.PduConverter/RelativeValidityPeriod.cs new file mode 100644 index 0000000..8b2a575 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/RelativeValidityPeriod.cs @@ -0,0 +1,174 @@ +using System; +using System.Text; + +/// +/// The relative validity period gives the length of the validity period +/// counted from when the SMS-SUBMIT is received by the SC. +/// +namespace GsmComm.PduConverter +{ + public class RelativeValidityPeriod : ValidityPeriod + { + private byte @value; + + /// + /// Initializes a new instance of the . + /// + /// The byte value of the validity period. Use this + /// if you have already calculated the validity yourself. + public RelativeValidityPeriod(byte value) + { + this.@value = value; + } + + /// + /// Initializes a new instance of the . + /// + /// The validity period. + /// + /// There are some rules to note: + /// + /// + /// The smallest validity period is 5 minutes, 63 weeks the largest. + /// + /// + /// Periods between 5 minutes and 12 hours can be specified in 5 minute steps. + /// + /// + /// Periods between 12h30min and 24 hours can be specified in 30 minute steps. + /// + /// + /// Periods between two days and 30 days can be specified in 1 day steps. + /// + /// + /// Periods between 5 weeks and 63 weeks can be specified in 1 week (=7 days) steps. + /// + /// + /// + /// Validity timespan is invalid. + public RelativeValidityPeriod(TimeSpan period) + { + byte num = 0; + while (num <= 255) + { + TimeSpan timeSpan = RelativeValidityPeriod.ToTimeSpan(num); + if (timeSpan.CompareTo(period) != 0) + { + num = (byte)(num + 1); + } + else + { + this.@value = num; + return; + } + } + throw new ArgumentException("Invalid validity timespan."); + } + + private void AppendIfNonzero(StringBuilder str, int val, string suffix) + { + if (val > 0) + { + if (str.Length != 0) + { + str.Append(" "); + } + str.Append(val.ToString()); + str.Append(suffix); + } + } + + public static explicit operator RelativeValidityPeriod(TimeSpan ts) + { + return new RelativeValidityPeriod(ts); + } + + public static implicit operator TimeSpan(RelativeValidityPeriod v) + { + return v.ToTimeSpan(); + } + + public static implicit operator Byte(RelativeValidityPeriod v) + { + return v.ToByte(); + } + + public static implicit operator RelativeValidityPeriod(byte b) + { + return new RelativeValidityPeriod(b); + } + + /// + /// Returns the byte equivalent of this instance. + /// + /// The byte value. + public byte ToByte() + { + return this.@value; + } + + /// + /// Returns the string equivalent of this instance. + /// + public override string ToString() + { + TimeSpan timeSpan = this.ToTimeSpan(); + if (timeSpan.TotalHours != 24) + { + StringBuilder stringBuilder = new StringBuilder(); + this.AppendIfNonzero(stringBuilder, timeSpan.Days, "d"); + this.AppendIfNonzero(stringBuilder, timeSpan.Hours, "h"); + this.AppendIfNonzero(stringBuilder, timeSpan.Minutes, "m"); + this.AppendIfNonzero(stringBuilder, timeSpan.Seconds, "s"); + this.AppendIfNonzero(stringBuilder, timeSpan.Milliseconds, "ms"); + return stringBuilder.ToString(); + } + else + { + return "24h"; + } + } + + /// + /// Returns the TimeSpan equivalent of this instance. + /// + /// The TimeSpan value. + public TimeSpan ToTimeSpan() + { + return RelativeValidityPeriod.ToTimeSpan(this.@value); + } + + private static TimeSpan ToTimeSpan(byte value) + { + if (value < 0 || value > 143) + { + if (value < 144 || value > 167) + { + if (value < 168 || value > 196) + { + if (value < 197 || value > 255) + { + return TimeSpan.Zero; + } + else + { + return new TimeSpan((value - 192) * 7, 0, 0, 0); + } + } + else + { + return new TimeSpan(value - 166, 0, 0, 0); + } + } + else + { + return new TimeSpan(12, (value - 143) * 30, 0); + } + } + else + { + return new TimeSpan(0, (value + 1) * 5, 0); + } + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/ReservedCodingGroup.cs b/PDUConverter/GsmComm.PduConverter/ReservedCodingGroup.cs new file mode 100644 index 0000000..b47f2f9 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/ReservedCodingGroup.cs @@ -0,0 +1,32 @@ +using System; + +/// +/// Reserved coding +/// +namespace GsmComm.PduConverter +{ + public class ReservedCodingGroup : DataCodingScheme + { + private byte dcs; + + /// + /// Gets the alphabet being used. + /// + public override byte Alphabet + { + get + { + return 3; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The DCS byte to decode. + public ReservedCodingGroup(byte dcs) : base(dcs) + { + this.dcs = dcs; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatInfoComparer.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatInfoComparer.cs new file mode 100644 index 0000000..4b66210 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatInfoComparer.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; + +/// +/// Implements a method to compare objects. +/// +/// This comparer is provided for performing sort order comparisons. It does not perform exact equality comparisons. +namespace GsmComm.PduConverter.SmartMessaging +{ + public class ConcatInfoComparer : IComparer + { + public ConcatInfoComparer() + { + } + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// + /// + /// Value + /// Condition + /// + /// + /// Less than zero + /// x is less than y. + /// + /// + /// Zero + /// x equals y. + /// + /// + /// Greater than zero + /// x is greater than y. + /// + /// + /// + /// + /// This method provides a sort order comparison for type . + /// Comparing null with any reference type is allowed and does not generate an exception. A null reference + /// is considered to be less than any reference that is not null. + /// + public int Compare(IConcatenationInfo x, IConcatenationInfo y) + { + int num; + if (x != null || y != null) + { + if (x != null || y == null) + { + if (x == null || y != null) + { + if (x.ReferenceNumber != y.ReferenceNumber) + { + ushort referenceNumber = x.ReferenceNumber; + num = referenceNumber.CompareTo(y.ReferenceNumber); + } + else + { + byte currentNumber = x.CurrentNumber; + num = currentNumber.CompareTo(y.CurrentNumber); + } + } + else + { + num = 1; + } + } + else + { + num = -1; + } + } + else + { + num = 0; + } + return num; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement16.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement16.cs new file mode 100644 index 0000000..c9670e1 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement16.cs @@ -0,0 +1,157 @@ +using System; + +/// +/// Implements a Concatenated Short Message Information Element (16-bit reference number) +/// +/// This element is used to indiate that a message is split into +/// multiple parts. +namespace GsmComm.PduConverter.SmartMessaging +{ + public class ConcatMessageElement16 : InformationElement, IConcatenationInfo + { + /// + /// The Information Element Identifier (IEI). + /// + public const byte Identifier = 8; + + private ushort referenceNumber; + + private byte totalMessages; + + private byte currentNumber; + + /// + /// Gets the current message number. + /// + public byte CurrentNumber + { + get + { + return this.currentNumber; + } + } + + /// + /// Gets the current message number. + /// + byte GsmComm.PduConverter.SmartMessaging.IConcatenationInfo.CurrentNumber + { + get + { + return this.currentNumber; + } + } + + /// + /// Gets the message reference number. + /// + ushort GsmComm.PduConverter.SmartMessaging.IConcatenationInfo.ReferenceNumber + { + get + { + return this.referenceNumber; + } + } + + /// + /// Gets the total number of parts of the message. + /// + byte GsmComm.PduConverter.SmartMessaging.IConcatenationInfo.TotalMessages + { + get + { + return this.totalMessages; + } + } + + /// + /// Gets the message reference number. + /// + public ushort ReferenceNumber + { + get + { + return this.referenceNumber; + } + } + + /// + /// Gets the total number of parts of the message. + /// + public byte TotalMessages + { + get + { + return this.totalMessages; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The message's reference number, must + /// be the same in all parts of the same message. + /// The total number of parts of the message. + /// The current message number. + public ConcatMessageElement16(ushort referenceNumber, byte totalMessages, byte currentNumber) + { + this.referenceNumber = referenceNumber; + this.totalMessages = totalMessages; + this.currentNumber = currentNumber; + } + + /// + /// Initializes a new instance of the class. + /// + /// The information element as a byte array. + public ConcatMessageElement16(byte[] element) + { + if (element != null) + { + if (element[0] == 8) + { + byte num = element[1]; + if (num >= 4) + { + byte[] numArray = new byte[2]; + numArray[0] = element[3]; + numArray[1] = element[2]; + this.referenceNumber = BitConverter.ToUInt16(numArray, 0); + this.totalMessages = element[4]; + this.currentNumber = element[5]; + return; + } + else + { + throw new FormatException("Information element data must be 4 bytes long."); + } + } + else + { + throw new ArgumentException("Element is not a Concatenated Short Message Information Element (16-bit reference number).", "element"); + } + } + else + { + throw new ArgumentNullException("element"); + } + } + + /// + /// Returns the byte array equivalent of this instance. + /// + /// The byte array. + public override byte[] ToByteArray() + { + byte[] bytes = BitConverter.GetBytes(this.referenceNumber); + byte[] numArray = new byte[6]; + numArray[0] = 8; + numArray[1] = 4; + numArray[2] = bytes[1]; + numArray[3] = bytes[0]; + numArray[4] = this.totalMessages; + numArray[5] = this.currentNumber; + return numArray; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement8.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement8.cs new file mode 100644 index 0000000..3db0431 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/ConcatMessageElement8.cs @@ -0,0 +1,151 @@ +using System; + +/// +/// Implements a Concatenated Short Message Information Element (8-bit reference number) +/// +/// This element is used to indiate that a message is split into +/// multiple parts. +namespace GsmComm.PduConverter.SmartMessaging +{ + public class ConcatMessageElement8 : InformationElement, IConcatenationInfo + { + /// + /// The Information Element Identifier (IEI). + /// + public const byte Identifier = 0; + + private byte referenceNumber; + + private byte totalMessages; + + private byte currentNumber; + + /// + /// Gets the current message number. + /// + public byte CurrentNumber + { + get + { + return this.currentNumber; + } + } + + /// + /// Gets the current message number. + /// + byte GsmComm.PduConverter.SmartMessaging.IConcatenationInfo.CurrentNumber + { + get + { + return this.currentNumber; + } + } + + /// + /// Gets the message reference number. + /// + ushort GsmComm.PduConverter.SmartMessaging.IConcatenationInfo.ReferenceNumber + { + get + { + return this.referenceNumber; + } + } + + /// + /// Gets the total number of parts of the message. + /// + byte GsmComm.PduConverter.SmartMessaging.IConcatenationInfo.TotalMessages + { + get + { + return this.totalMessages; + } + } + + /// + /// Gets the message reference number. + /// + public byte ReferenceNumber + { + get + { + return this.referenceNumber; + } + } + + /// + /// Gets the total number of parts of the message. + /// + public byte TotalMessages + { + get + { + return this.totalMessages; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The message's reference number, must + /// be the same in all parts of the same message. + /// The total number of parts of the message. + /// The current message number. + public ConcatMessageElement8(byte referenceNumber, byte totalMessages, byte currentNumber) + { + this.referenceNumber = referenceNumber; + this.totalMessages = totalMessages; + this.currentNumber = currentNumber; + } + + /// + /// Initializes a new instance of the class. + /// + /// The information element as a byte array. + public ConcatMessageElement8(byte[] element) + { + if (element != null) + { + if (element[0] == 0) + { + byte num = element[1]; + if (num >= 3) + { + this.referenceNumber = element[2]; + this.totalMessages = element[3]; + this.currentNumber = element[4]; + return; + } + else + { + throw new FormatException("Information element data must be 3 bytes long."); + } + } + else + { + throw new ArgumentException("Element is not a Concatenated Short Message Information Element (8-bit reference number).", "element"); + } + } + else + { + throw new ArgumentNullException("element"); + } + } + + /// + /// Returns the byte array equivalent of this instance. + /// + /// The byte array. + public override byte[] ToByteArray() + { + byte[] numArray = new byte[5]; + numArray[1] = 3; + numArray[2] = this.referenceNumber; + numArray[3] = this.totalMessages; + numArray[4] = this.currentNumber; + return numArray; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/IConcatenationInfo.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/IConcatenationInfo.cs new file mode 100644 index 0000000..fdead30 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/IConcatenationInfo.cs @@ -0,0 +1,35 @@ +using System; + +/// +/// A common interface for all information elements containing concatenation information. +/// +namespace GsmComm.PduConverter.SmartMessaging +{ + public interface IConcatenationInfo + { + /// + /// Gets the current message number. + /// + byte CurrentNumber + { + get; + } + + /// + /// Gets the message reference number. + /// + ushort ReferenceNumber + { + get; + } + + /// + /// Gets the total number of parts of the message. + /// + byte TotalMessages + { + get; + } + + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/InformationElement.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/InformationElement.cs new file mode 100644 index 0000000..10aa5f8 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/InformationElement.cs @@ -0,0 +1,33 @@ +using GsmComm.PduConverter; +using System; + +/// +/// Implements the base for an information element. +/// +namespace GsmComm.PduConverter.SmartMessaging +{ + public abstract class InformationElement + { + /// + /// Initializes a new instance of the class. + /// + protected InformationElement() + { + } + + /// + /// In the derived classes, returns the byte array equivalent of this instance. + /// + /// The byte array. + public abstract byte[] ToByteArray(); + + /// + /// Returns the string equivalent of this instance, which is a hexadecimal representation of the element. + /// + /// The string. + public override string ToString() + { + return Calc.IntToHex(this.ToByteArray()); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/OtaBitmap.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/OtaBitmap.cs new file mode 100644 index 0000000..9178712 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/OtaBitmap.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections; +using System.Drawing; + +/// +/// Represents an OTA (over-the-air) bitmap. +/// +namespace GsmComm.PduConverter.SmartMessaging +{ + public class OtaBitmap + { + private byte[] bitmap; + + private byte infoField; + + private byte width; + + private byte height; + + private byte grayscales; + + private int dataStart; + + private int dataLen; + + /// + /// Gets the actual bitmap data. + /// + public byte[] Data + { + get + { + byte[] numArray = new byte[this.dataLen]; + Array.Copy(this.bitmap, this.dataStart, numArray, 0, this.dataLen); + return numArray; + } + } + + /// + /// Gets the bitmap's height. + /// + public byte Height + { + get + { + return this.height; + } + } + + /// + /// Gets the bitmap's InfoField. + /// + public byte InfoField + { + get + { + return this.infoField; + } + } + + /// + /// Gets the bitmap's number of grayscales. + /// + public byte NumGrayscales + { + get + { + return this.grayscales; + } + } + + /// + /// Gets the bitmap's width. + /// + public byte Width + { + get + { + return this.width; + } + } + + /// + /// Creates a new OTA bitmap from an existing object. + /// + /// The to create the OTA bitmap from. + public OtaBitmap(Bitmap bitmap) : this(OtaBitmap.BitmapToOtaBitmap(bitmap)) + { + } + + /// + /// Creates a new OTA bitmap from an existing byte array. + /// + /// The byte array containing the OTA bitmap. + /// otaBitmap is null. + public OtaBitmap(byte[] otaBitmap) + { + if (otaBitmap != null) + { + int num = 0; + int num1 = num; + num = num1 + 1; + this.infoField = otaBitmap[num1]; + int num2 = num; + num = num2 + 1; + this.width = otaBitmap[num2]; + int num3 = num; + num = num3 + 1; + this.height = otaBitmap[num3]; + int num4 = num; + num = num4 + 1; + this.grayscales = otaBitmap[num4]; + this.dataStart = num; + this.dataLen = (int)otaBitmap.Length - num; + this.bitmap = new byte[(int)otaBitmap.Length]; + otaBitmap.CopyTo(this.bitmap, 0); + return; + } + else + { + throw new ArgumentException("otaBitmap"); + } + } + + /// + /// Converts a into an OTA (over-the-air) bitmap. + /// + /// The to convert. The maximum allowed + /// size is 255x255 pixels, minimum is 1x1. The bitmap can be any + /// pixel format, but only the black pixels are converted. + /// Can be null to get an empty header. + /// The converted image. If bitmap is null, an empty OTA bitmap + /// header and no data is returned. + /// bitmap is greater than 255x255 pixels. + private static byte[] BitmapToOtaBitmap(Bitmap bitmap) + { + byte[] numArray = null; + byte[] numArray1 = null; + if (bitmap == null) + { + numArray1 = new byte[0]; + byte[] numArray2 = new byte[4]; + numArray2[3] = 1; + numArray = numArray2; + } + else + { + if (bitmap.Height < 1 || bitmap.Width < 1 || bitmap.Height > 255 || bitmap.Width > 255) + { + throw new ArgumentException("Invalid bitmap dimensions. Maximum size is 255x255, minimum size is 1x1 pixels."); + } + else + { + int num = 7; + byte num1 = 0; + ArrayList arrayLists = new ArrayList(); + for (int i = 0; i < bitmap.Height; i++) + { + for (int j = 0; j < bitmap.Width; j++) + { + byte num2 = (byte)Math.Pow(2, (double)num); + Color pixel = bitmap.GetPixel(j, i); + Color black = Color.Black; + if (pixel.ToArgb() == black.ToArgb()) + { + num1 = (byte)(num1 | num2); + } + if (num != 0) + { + num--; + } + else + { + arrayLists.Add(num1); + num1 = 0; + num = 7; + } + } + } + if (num < 7) + { + arrayLists.Add(num1); + } + numArray1 = new byte[arrayLists.Count]; + arrayLists.CopyTo(numArray1); + byte[] width = new byte[4]; + width[1] = (byte)bitmap.Width; + width[2] = (byte)bitmap.Height; + width[3] = 1; + numArray = width; + } + } + byte[] numArray3 = new byte[(int)numArray.Length + (int)numArray1.Length]; + numArray.CopyTo(numArray3, 0); + numArray1.CopyTo(numArray3, (int)numArray.Length); + return numArray3; + } + + public static explicit operator Bitmap(OtaBitmap b) + { + return b.ToBitmap(); + } + + public static explicit operator OtaBitmap(Bitmap b) + { + return new OtaBitmap(b); + } + + public static explicit operator OtaBitmap(byte[] b) + { + return new OtaBitmap(b); + } + + public static implicit operator Byte[](OtaBitmap b) + { + return b.ToByteArray(); + } + + /// + /// Converts an OTA bitmap into a . + /// + /// The OTA bitmap to convert. Can be null. + /// The converted image. If otaBitmap is null, null is returned. + /// null is also returned, if the height or width of the OTA bitmap is 0. + /// + /// The grayscales attribute of the bitmap is ignored, always a monochrome bitmap is created. + /// + private static Bitmap OtaBitmapToBitmap(byte[] otaBitmap) + { + Color black; + if (otaBitmap != null) + { + int num = 0; + int num1 = num; + num = num1 + 1; + int num2 = num; + num = num2 + 1; + byte num3 = otaBitmap[num2]; + int num4 = num; + num = num4 + 1; + byte num5 = otaBitmap[num4]; + int num6 = num; + num = num6 + 1; + if (num3 == 0 || num5 == 0) + { + return null; + } + else + { + Bitmap bitmap = new Bitmap(num3, num5); + int num7 = 0; + byte num8 = 0; + for (int i = 0; i < num5; i++) + { + for (int j = 0; j < num3; j++) + { + if (num7 != 0) + { + num7--; + } + else + { + int num9 = num; + num = num9 + 1; + num8 = otaBitmap[num9]; + num7 = 7; + } + byte num10 = (byte)Math.Pow(2, (double)num7); + Bitmap bitmap1 = bitmap; + int num11 = j; + int num12 = i; + if ((num8 & num10) > 0) + { + black = Color.Black; + } + else + { + black = Color.White; + } + bitmap1.SetPixel(num11, num12, black); + } + } + return bitmap; + } + } + else + { + return null; + } + } + + /// + /// Returns the equivalent of this instance. + /// + /// The . + public Bitmap ToBitmap() + { + return OtaBitmap.OtaBitmapToBitmap(this.bitmap); + } + + /// + /// Returns the byte array equivalent of this instance. + /// + /// The byte array. + public byte[] ToByteArray() + { + byte[] numArray = new byte[(int)this.bitmap.Length]; + this.bitmap.CopyTo(numArray, 0); + return numArray; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/PortAddressElement16.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/PortAddressElement16.cs new file mode 100644 index 0000000..85ea0f5 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/PortAddressElement16.cs @@ -0,0 +1,111 @@ +using System; + +/// +/// Implements an Application Port Addressing Information Element (16 bit address). +/// +/// This element is used to indiate from which port a message +/// originated and to which port it should be directed to. +namespace GsmComm.PduConverter.SmartMessaging +{ + public class PortAddressElement16 : InformationElement + { + /// + /// The Information Element Identifier (IEI). + /// + public const byte Identifier = 5; + + private ushort destinationPort; + + private ushort originatorPort; + + /// + /// Gets the destination port. + /// + public ushort DestinationPort + { + get + { + return this.destinationPort; + } + } + + /// + /// Gets the originator port. + /// + public ushort OriginatorPort + { + get + { + return this.originatorPort; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The destination port, e.g. 0x1582. + /// The source port, e.g. 0x00. + public PortAddressElement16(ushort destinationPort, ushort originatorPort) + { + this.destinationPort = destinationPort; + this.originatorPort = originatorPort; + } + + /// + /// Initializes a new instance of the class. + /// + /// The information element as a byte array. + public PortAddressElement16(byte[] element) + { + if (element != null) + { + if (element[0] == 5) + { + byte num = element[1]; + if (num >= 4) + { + byte[] numArray = new byte[2]; + numArray[0] = element[3]; + numArray[1] = element[2]; + this.destinationPort = BitConverter.ToUInt16(numArray, 0); + byte[] numArray1 = new byte[2]; + numArray1[0] = element[5]; + numArray1[1] = element[4]; + this.originatorPort = BitConverter.ToUInt16(numArray1, 0); + return; + } + else + { + throw new FormatException("Information element data must be 4 bytes long."); + } + } + else + { + throw new ArgumentException("Element is not an Application Port Addressing Information Element (16 bit address).", "element"); + } + } + else + { + throw new ArgumentNullException("element"); + } + } + + /// + /// Returns the byte array equivalent of this instance. + /// + /// The byte array. + public override byte[] ToByteArray() + { + byte[] bytes = BitConverter.GetBytes(this.destinationPort); + byte[] numArray = BitConverter.GetBytes(this.originatorPort); + byte[] numArray1 = new byte[6]; + numArray1[0] = 5; + numArray1[1] = 4; + numArray1[2] = bytes[1]; + numArray1[3] = bytes[0]; + numArray1[4] = numArray[1]; + numArray1[5] = numArray[0]; + return numArray1; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageDecoder.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageDecoder.cs new file mode 100644 index 0000000..1d79869 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageDecoder.cs @@ -0,0 +1,470 @@ +using GsmComm.PduConverter; +using System; +using System.Collections.Generic; +using System.Text; + +/// +/// Decodes messages based on Nokia's Smart Messaging specification and related messages. +/// +/// +/// This methods in this class can be used to find and combine concatenated (long, multi-part) SMS messages. +/// To determine, whether a message is part of a concatenated message at all, use +/// When you have identified that a message is a part of a concatenated message, you need to find the other message parts +/// belonging to the same message, can be used for this. +/// +/// After all parts of the concatenated message have been identified, the parts need to be combined. This is done +/// using and . The difference between these two +/// methods is, that the first one returns the combined data in binary format, whereas the latter returns the result +/// as text. +/// To verify that all message parts are present before attempting to combine them, +/// can be used. Calling this method is optional, but prevents exceptions when combining without all parts present. +/// The above methods all accept instances to abstract some of the work that has to be done +/// to find and combine concatenated SMS messages. However, it is also possible to retrieve the underlying data that +/// is used for these operations (and also possible for other operations as well) using the methods +/// and . +/// +namespace GsmComm.PduConverter.SmartMessaging +{ + public class SmartMessageDecoder + { + public SmartMessageDecoder() + { + } + + /// + /// Determines whether all parts of a concatenated message are present. + /// + /// The parts that make up the concatenated message. + /// true if all parts are present, false otherwise. + /// parts is null. + /// + /// The reference numbers differ between the message parts. + /// -or- + /// The number of total messages differs between the message parts. + /// -or- + /// A non-concatenated message part is present at an invalid position. + /// + public static bool AreAllConcatPartsPresent(IList parts) + { + bool flag = false; + SmartMessageDecoder.GetConcatUserData(parts, false, true, true, out flag); + return flag; + } + + /// + /// Determines whether two messages are part of the same concatenated message. + /// + /// The first message to compare. + /// The second message to compare. + /// true if both messages appear to belong to the same concatenated message, false otherwise. + /// + /// This comparison is supported for and objects. + /// For all other objects, this comparison always returns false. + /// For objects, the , + /// and properties are compared. + /// For objects, the , + /// and properties are compared. + /// + /// pdu1 or pdu2 is null. + public static bool ArePartOfSameMessage(SmsPdu pdu1, SmsPdu pdu2) + { + if (pdu1 != null) + { + if (pdu2 != null) + { + bool flag = false; + if (pdu1 as SmsDeliverPdu == null || pdu2 as SmsDeliverPdu == null) + { + if (pdu1 is SmsSubmitPdu && pdu2 is SmsSubmitPdu) + { + SmsSubmitPdu smsSubmitPdu = (SmsSubmitPdu)pdu1; + SmsSubmitPdu smsSubmitPdu1 = (SmsSubmitPdu)pdu2; + if (smsSubmitPdu.DestinationAddress == smsSubmitPdu1.DestinationAddress && smsSubmitPdu.DestinationAddressType == smsSubmitPdu1.DestinationAddressType) + { + flag = SmartMessageDecoder.HaveSameReferenceNumber(pdu1, pdu2); + } + } + } + else + { + SmsDeliverPdu smsDeliverPdu = (SmsDeliverPdu)pdu1; + SmsDeliverPdu smsDeliverPdu1 = (SmsDeliverPdu)pdu2; + if (smsDeliverPdu.OriginatingAddress == smsDeliverPdu1.OriginatingAddress && smsDeliverPdu.OriginatingAddressType == smsDeliverPdu1.OriginatingAddressType) + { + flag = SmartMessageDecoder.HaveSameReferenceNumber(pdu1, pdu2); + } + } + return flag; + } + else + { + throw new ArgumentNullException("pdu2"); + } + } + else + { + throw new ArgumentNullException("pdu1"); + } + } + + /// + /// Combines the parts of a concatenated message into a single message. + /// + /// The parts that make up the concatenated message. + /// A byte array containing the combined user data of all parts without any headers. + /// + /// All parts must be available, but can be in any order. + /// The user data is returned in its binary format. If you want the user data to be + /// returned as text, use instead. + /// If the first part is a non-concatenated message, its user data is returned back, and no more parts are processed + /// afterwards. + /// + /// parts is null. + /// + /// Not all parts of the message are available. + /// -or- + /// The reference numbers differ between the message parts. + /// -or- + /// The number of total messages differs between the message parts. + /// -or- + /// A non-concatenated message part is present at an invalid position. + /// + /// + public static byte[] CombineConcatMessage(IList parts) + { + bool flag = false; + List concatUserData = SmartMessageDecoder.GetConcatUserData(parts, false, false, false, out flag); + List nums = new List(); + foreach (byte[] concatUserDatum in concatUserData) + { + nums.AddRange(concatUserDatum); + } + return nums.ToArray(); + } + + /// + /// Combines the parts of a concatenated message into a single message text. + /// + /// The parts that make up the concatenated message. + /// A string containing the combined message text of all parts. + /// + /// All parts must be available, but can be in any order. + /// The user data is converted into text according to the data coding scheme specified in the message. + /// If you want the user data to be returned in its binary format, use instead. + /// If the first part is a non-concatenated message, its user data is returned back as text, and no more parts are processed + /// afterwards. + /// + /// parts is null. + /// + /// Not all parts of the message are available. + /// -or- + /// The reference numbers differ between the message parts. + /// -or- + /// The number of total messages differs between the message parts. + /// -or- + /// A non-concatenated message part is present at an invalid position. + /// + /// + public static string CombineConcatMessageText(IList parts) + { + bool flag = false; + List concatUserData = SmartMessageDecoder.GetConcatUserData(parts, true, false, false, out flag); + StringBuilder stringBuilder = new StringBuilder(); + foreach (string concatUserDatum in concatUserData) + { + stringBuilder.Append(concatUserDatum); + } + return stringBuilder.ToString(); + } + + /// + /// Decodes a user data header into information elements. + /// + /// The user data header to be decoded. + /// The elements found as an array of objects. + /// + /// Known information elements are decoded into their respective objects, while unknown + /// information elements are stored in generic objects. + /// The list of known information elements consists of elements used within Smart Messaging, + /// and does not aim to be a complete set of all existing elements. + /// The currently recognized elements are: + /// + /// + /// + /// + /// + /// + public static InformationElement[] DecodeUserDataHeader(byte[] userDataHeader) + { + InformationElement unknownInformationElement; + List informationElements = new List(); + byte num = 0; + byte num1 = userDataHeader[0]; + if (num1 > 0) + { + num = (byte)(num + 1); + do + { + byte num2 = num; + num = (byte)(num2 + 1); + byte num3 = userDataHeader[num2]; + byte num4 = num; + num = (byte)(num4 + 1); + byte num5 = userDataHeader[num4]; + byte[] numArray = new byte[num5 + 2]; + Array.Copy(userDataHeader, num - 2, numArray, 0, num5 + 2); + num = (byte)(num + num5); + if (num3 != 0) + { + if (num3 != 8) + { + if (num3 != 5) + { + unknownInformationElement = new UnknownInformationElement(numArray); + } + else + { + unknownInformationElement = new PortAddressElement16(numArray); + } + } + else + { + unknownInformationElement = new ConcatMessageElement16(numArray); + } + } + else + { + unknownInformationElement = new ConcatMessageElement8(numArray); + } + informationElements.Add(unknownInformationElement); + } + while (num < num1); + } + return informationElements.ToArray(); + } + + /// + /// Gets the concatenation information of a message. + /// + /// The message to get the information of. + /// An object implementing , if + /// the message is a part of a concatenated message, null otherwise. + /// + /// The returned information can be used to discover the parts of a concatenated message + /// or recombine the parts back into one message. + /// + public static IConcatenationInfo GetConcatenationInfo(SmsPdu pdu) + { + if (pdu != null) + { + IConcatenationInfo concatenationInfo = null; + if (pdu.UserDataHeaderPresent) + { + byte[] userDataHeader = pdu.GetUserDataHeader(); + InformationElement[] informationElementArray = SmartMessageDecoder.DecodeUserDataHeader(userDataHeader); + InformationElement[] informationElementArray1 = informationElementArray; + int num = 0; + while (num < (int)informationElementArray1.Length) + { + InformationElement informationElement = informationElementArray1[num]; + if (informationElement as IConcatenationInfo == null) + { + num++; + } + else + { + concatenationInfo = (IConcatenationInfo)informationElement; + break; + } + } + } + return concatenationInfo; + } + else + { + throw new ArgumentNullException("pdu"); + } + } + + /// + /// Retrieves the user data of all parts of a concatenated message. + /// + /// The parts that make up the concatenated message. + /// If true, formats the returned user data as text. If false, returns the user data + /// in its binary form. + /// Specifies whether missing parts are allowed. If true, null is returned + /// in the resulting list in place of every missing part. If false, an exception is raised when a part is + /// missing. + /// If set to true, does not fill the returned list with data. If set to false, the data is returned + /// normally. Use this in conjunction with allowMissingParts set to true to verify whether all message parts are present. + /// Is set to true if all message parts are available, false otherwise. Use this in conjunction + /// with allowMissingParts set to true to verify whether all message parts are present. + /// A list of objects containing the user data of every part without any headers. + /// The outputAsText parameter determines the actual data type that is returned. If outputAsText is true, the return type is a + /// list of byte arrays, if false a list of strings is returned. + /// + /// + /// The parts can be in any order. + /// If the first part is a non-concatenated message, its user data is returned back, and no more parts are processed + /// afterwards. + /// + /// parts is null. + /// + /// Not all parts of the message are available and allowMissingParts is false. + /// -or- + /// The reference numbers differ between the message parts. + /// -or- + /// The number of total messages differs between the message parts. + /// -or- + /// A non-concatenated message part is present at an invalid position. + /// + private static List GetConcatUserData(IList parts, bool outputAsText, bool allowMissingParts, bool noOutput, out bool allPartsAvailable) + { + if (parts != null) + { + allPartsAvailable = true; + List objs = new List(); + if (parts.Count > 0) + { + SmsPdu item = parts[0]; + if (!SmartMessageDecoder.IsPartOfConcatMessage(item)) + { + if (!noOutput) + { + if (!outputAsText) + { + objs.Add(item.UserData); + } + else + { + objs.Add(item.UserDataText); + } + } + } + else + { + SortedList concatenationInfos = SmartMessageDecoder.SortConcatMessageParts(parts); + int totalMessages = concatenationInfos.Keys[0].TotalMessages; + int num = 0; + for (int i = 1; i <= totalMessages; i++) + { + bool flag = false; + if (num < concatenationInfos.Count) + { + IConcatenationInfo concatenationInfo = concatenationInfos.Keys[num]; + SmsPdu smsPdu = concatenationInfos.Values[num]; + if (i == concatenationInfo.CurrentNumber) + { + if (!noOutput) + { + if (!outputAsText) + { + objs.Add(smsPdu.GetUserDataWithoutHeader()); + } + else + { + objs.Add(smsPdu.GetUserDataTextWithoutHeader()); + } + } + num++; + } + else + { + flag = true; + } + } + else + { + flag = true; + } + if (flag) + { + allPartsAvailable = false; + if (!allowMissingParts) + { + throw new ArgumentException(string.Concat("Not all parts of the message are available. Part #", i, " is missing."), "parts"); + } + else + { + if (!noOutput) + { + objs.Add(null); + } + } + } + } + } + } + return objs; + } + else + { + throw new ArgumentNullException("parts"); + } + } + + private static bool HaveSameReferenceNumber(SmsPdu pdu1, SmsPdu pdu2) + { + bool flag = false; + if (SmartMessageDecoder.IsPartOfConcatMessage(pdu1) && SmartMessageDecoder.IsPartOfConcatMessage(pdu2)) + { + IConcatenationInfo concatenationInfo = SmartMessageDecoder.GetConcatenationInfo(pdu1); + IConcatenationInfo concatenationInfo1 = SmartMessageDecoder.GetConcatenationInfo(pdu2); + if (concatenationInfo.ReferenceNumber == concatenationInfo1.ReferenceNumber) + { + flag = true; + } + } + return flag; + } + + /// + /// Determines whether a message is part of a concatenated message. + /// + /// The message. + /// true if the message is part of a concatenated message, false otherwise. + public static bool IsPartOfConcatMessage(SmsPdu pdu) + { + return SmartMessageDecoder.GetConcatenationInfo(pdu) != null; + } + + private static SortedList SortConcatMessageParts(IList parts) + { + IConcatenationInfo concatenationInfo; + IConcatenationInfo concatenationInfo1 = null; + SortedList concatenationInfos = new SortedList(new ConcatInfoComparer()); + foreach (SmsPdu part in parts) + { + if (concatenationInfo1 != null) + { + if (!SmartMessageDecoder.IsPartOfConcatMessage(part)) + { + throw new ArgumentException("A non-concatenated message part is present at an invalid position.", "parts"); + } + else + { + concatenationInfo = SmartMessageDecoder.GetConcatenationInfo(part); + if (concatenationInfo1.ReferenceNumber == concatenationInfo.ReferenceNumber) + { + if (concatenationInfo1.TotalMessages != concatenationInfo.TotalMessages) + { + throw new ArgumentException("The number of total messages differs between the message parts.", "parts"); + } + } + else + { + throw new ArgumentException("The reference numbers differ between the message parts.", "parts"); + } + } + } + else + { + concatenationInfo = SmartMessageDecoder.GetConcatenationInfo(part); + } + concatenationInfos.Add(concatenationInfo, part); + concatenationInfo1 = concatenationInfo; + } + return concatenationInfos; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageFactory.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageFactory.cs new file mode 100644 index 0000000..7e97f4b --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/SmartMessageFactory.cs @@ -0,0 +1,455 @@ +using GsmComm.PduConverter; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; + +/// +/// Creates messages based on Nokia's Smart Messaging specification and related messages. +/// +namespace GsmComm.PduConverter.SmartMessaging +{ + public class SmartMessageFactory + { + private static ushort refNumber; + + /// + /// Represents an empty operator logo that can be used to remove an operator logo. + /// It depends on the phone model whether it works. + /// + public readonly static byte[] EmptyOperatorLogo; + + static SmartMessageFactory() + { + SmartMessageFactory.refNumber = 1; + SmartMessageFactory.EmptyOperatorLogo = SmartMessageFactory.CreateOperatorLogo(new OtaBitmap(null), "000", "00"); + } + + public SmartMessageFactory() + { + } + + /// + /// Calculates the next reference number. + /// + /// Returns the current number and then increments it by one. + /// If the new number would exceed 65535, it it reset to 1. + /// The next reference number. + protected static ushort CalcNextRefNumber() + { + ushort num; + lock (typeof(SmartMessageFactory)) + { + num = SmartMessageFactory.refNumber; + if (SmartMessageFactory.refNumber != 65535) + { + SmartMessageFactory.refNumber = (ushort)(SmartMessageFactory.refNumber + 1); + } + else + { + SmartMessageFactory.refNumber = 1; + } + } + return num; + } + + /// + /// Creates a concatenated text message. + /// + /// The message text. + /// The message's destination address. + /// A set of objects that represent the message. + /// + /// A concatenated message makes it possible to exceed the maximum length of a normal message, + /// created by splitting the message data into multiple parts. + /// Concatenated messages are also known as long or multi-part messages. + /// The userDataText is converted to the GSM 7-bit default alphabet automatically. + /// If no concatenation is necessary, a single, non-concatenated object is created. + /// + /// userDataText is so long that it would create more than 255 message parts. + public static SmsSubmitPdu[] CreateConcatTextMessage(string userDataText, string destinationAddress) + { + return SmartMessageFactory.CreateConcatTextMessage(userDataText, false, destinationAddress); + } + + /// + /// Creates a concatenated text message. + /// + /// The message text. + /// Specifies if the userDataText is to be encoded as Unicode. If not, the GSM 7-bit default alphabet is used. + /// The message's destination address. + /// A set of objects that represent the message. + /// + /// A concatenated message makes it possible to exceed the maximum length of a normal message, + /// created by splitting the message data into multiple parts. + /// Concatenated messages are also known as long or multi-part messages. + /// If no concatenation is necessary, a single, non-concatenated object is created. + /// + /// userDataText is so long that it would create more than 255 message parts. + public static SmsSubmitPdu[] CreateConcatTextMessage(string userDataText, bool unicode, string destinationAddress) + { + string str; + int length = 0; + int num; + byte[] bytes; + SmsSubmitPdu smsSubmitPdu; + int num1; + byte num2; + if (unicode) + { + num1 = 70; + } + else + { + num1 = 160; + } + int num3 = num1; + if (unicode) + { + str = userDataText; + } + else + { + str = TextDataConverter.StringTo7Bit(userDataText); + } + if (str.Length <= num3) + { + if (unicode) + { + smsSubmitPdu = new SmsSubmitPdu(userDataText, destinationAddress, 8); + } + else + { + smsSubmitPdu = new SmsSubmitPdu(userDataText, destinationAddress); + } + SmsSubmitPdu[] smsSubmitPduArray = new SmsSubmitPdu[1]; + smsSubmitPduArray[0] = smsSubmitPdu; + return smsSubmitPduArray; + } + else + { + ConcatMessageElement16 concatMessageElement16 = new ConcatMessageElement16(0, 0, 0); + byte length1 = (byte)((int)SmartMessageFactory.CreateUserDataHeader(concatMessageElement16).Length); + byte num4 = (byte)((double)length1 / 7 * 8); + if (unicode) + { + num2 = length1; + } + else + { + num2 = num4; + } + byte num5 = num2; + StringCollection stringCollections = new StringCollection(); + for (int i = 0; i < str.Length; i = i + length) + { + if (!unicode) + { + if (str.Length - i < num3 - num5) + { + length = str.Length - i; + } + else + { + length = num3 - num5; + } + } + else + { + if (str.Length - i < (num3 * 2 - num5) / 2) + { + length = str.Length - i; + } + else + { + length = (num3 * 2 - num5) / 2; + } + } + string str1 = str.Substring(i, length); + stringCollections.Add(str1); + } + if (stringCollections.Count <= 255) + { + SmsSubmitPdu[] smsSubmitPduArray1 = new SmsSubmitPdu[stringCollections.Count]; + ushort num6 = SmartMessageFactory.CalcNextRefNumber(); + byte num7 = 0; + for (int j = 0; j < stringCollections.Count; j++) + { + num7 = (byte)(num7 + 1); + ConcatMessageElement16 concatMessageElement161 = new ConcatMessageElement16(num6, (byte)stringCollections.Count, num7); + byte[] numArray = SmartMessageFactory.CreateUserDataHeader(concatMessageElement161); + if (unicode) + { + Encoding bigEndianUnicode = Encoding.BigEndianUnicode; + bytes = bigEndianUnicode.GetBytes(stringCollections[j]); + num = (int)bytes.Length; + } + else + { + bytes = TextDataConverter.SeptetsToOctetsInt(stringCollections[j]); + num = stringCollections[j].Length; + } + SmsSubmitPdu smsSubmitPdu1 = new SmsSubmitPdu(); + smsSubmitPdu1.DestinationAddress = destinationAddress; + if (unicode) + { + smsSubmitPdu1.DataCodingScheme = 8; + } + smsSubmitPdu1.SetUserData(bytes, (byte)num); + smsSubmitPdu1.AddUserDataHeader(numArray); + smsSubmitPduArray1[j] = smsSubmitPdu1; + } + return smsSubmitPduArray1; + } + else + { + throw new ArgumentException("A concatenated message must not have more than 255 parts.", "userDataText"); + } + } + } + + /// + /// Creates an operator logo. + /// + /// The OTA bitmap to use as the logo. Maximum size is 72x14 pixels. + /// MCC. The operator's country code. Must be 3 digits long. + /// MNC. The operator's network code. Must be 2 digits long. + /// A byte array containing the generated operator logo. + /// otaBitmap is null. + /// mobileCountryCode is not 3 digits long. -or- + /// mobileNetworkCode is not 2 digits long. + /// -or- The bitmap is larger than 72x14 pixels. + /// + public static byte[] CreateOperatorLogo(OtaBitmap otaBitmap, string mobileCountryCode, string mobileNetworkCode) + { + if (otaBitmap != null) + { + if (otaBitmap.Width > 72 || otaBitmap.Height > 14) + { + string[] str = new string[5]; + str[0] = "Bitmaps used as operator logos must not be larger than "; + int num = 72; + str[1] = num.ToString(); + str[2] = "x"; + int num1 = 14; + str[3] = num1.ToString(); + str[4] = " pixels."; + throw new ArgumentException(string.Concat(str)); + } + else + { + if (mobileCountryCode.Length == 3) + { + if (mobileNetworkCode.Length == 2) + { + byte num2 = 48; + byte[] numArray = Calc.HexToInt(BcdWorker.EncodeSemiOctets(mobileCountryCode.PadRight(4, 'F'))); + byte[] numArray1 = Calc.HexToInt(BcdWorker.EncodeSemiOctets(mobileNetworkCode.PadRight(2, 'F'))); + int length = 1 + (int)numArray.Length + (int)numArray1.Length + 1; + int length1 = 0; + byte[] numArray2 = new byte[length]; + int num3 = length1; + length1 = num3 + 1; + numArray2[num3] = num2; + numArray.CopyTo(numArray2, length1); + length1 = length1 + (int)numArray.Length; + numArray1.CopyTo(numArray2, length1); + length1 = length1 + (int)numArray1.Length; + int num4 = length1; + length1 = num4 + 1; + numArray2[num4] = 10; + byte[] byteArray = otaBitmap.ToByteArray(); + byte[] numArray3 = new byte[(int)numArray2.Length + (int)byteArray.Length]; + numArray2.CopyTo(numArray3, 0); + byteArray.CopyTo(numArray3, length1); + return numArray3; + } + else + { + throw new ArgumentException("mobileNetworkCode must be 2 digits long.", "mobileNetworkCode"); + } + } + else + { + throw new ArgumentException("mobileCountryCode must be 3 digits long.", "mobileCountryCode"); + } + } + } + else + { + throw new ArgumentNullException("otaBitmap"); + } + } + + /// + /// Creates an operator logo message. + /// + /// The operator logo. Use to create one. + /// The message's destination address. + /// A set of objects that represent the message. + /// operatorLogo is null. + /// operatorLogo is an empty array. + /// -or- destinationAddress is an empty string. + /// -or- operatorLogo is so big that it would create more than 255 message parts. + public static SmsSubmitPdu[] CreateOperatorLogoMessage(byte[] operatorLogo, string destinationAddress) + { + int length; + if (operatorLogo != null) + { + if ((int)operatorLogo.Length != 0) + { + PortAddressElement16 portAddressElement16 = new PortAddressElement16(5506, 0); + byte num = (byte)((int)portAddressElement16.ToByteArray().Length); + int num1 = num + 1; + bool flag = (int)operatorLogo.Length > 140 - num1; + if (flag) + { + ConcatMessageElement8 concatMessageElement8 = new ConcatMessageElement8(0, 0, 0); + num = (byte)((int)portAddressElement16.ToByteArray().Length + (int)concatMessageElement8.ToByteArray().Length); + num1 = num + 1; + } + ArrayList arrayLists = new ArrayList(); + int num2 = 0; + byte num3 = (byte)Math.Ceiling((double)((int)operatorLogo.Length) / (double)(140 - num1)); + if (arrayLists.Count <= 255) + { + ushort num4 = 1; + if (flag) + { + num4 = SmartMessageFactory.CalcNextRefNumber(); + } + for (byte i = 1; i <= num3; i = (byte)(i + 1)) + { + if ((int)operatorLogo.Length - num2 < 140 - num1) + { + length = (int)operatorLogo.Length - num2; + } + else + { + length = 140 - num1; + } + byte[] numArray = new byte[num1]; + int length1 = 0; + int num5 = length1; + length1 = num5 + 1; + numArray[num5] = num; + portAddressElement16.ToByteArray().CopyTo(numArray, length1); + length1 = length1 + (int)portAddressElement16.ToByteArray().Length; + if (flag) + { + ConcatMessageElement8 concatMessageElement81 = new ConcatMessageElement8((byte)(num4 % 256), num3, i); + concatMessageElement81.ToByteArray().CopyTo(numArray, length1); + length1 = length1 + (int)concatMessageElement81.ToByteArray().Length; + } + byte[] numArray1 = new byte[(int)numArray.Length + length]; + numArray.CopyTo(numArray1, 0); + Array.Copy(operatorLogo, num2, numArray1, length1, length); + SmsSubmitPdu smsSubmitPdu = new SmsSubmitPdu(); + smsSubmitPdu.MessageFlags.UserDataHeaderPresent = true; + smsSubmitPdu.DataCodingScheme = 21; + smsSubmitPdu.DestinationAddress = destinationAddress; + smsSubmitPdu.SetUserData(numArray1, (byte)((int)numArray1.Length)); + arrayLists.Add(smsSubmitPdu); + num2 = num2 + length; + } + SmsSubmitPdu[] smsSubmitPduArray = new SmsSubmitPdu[arrayLists.Count]; + arrayLists.CopyTo(smsSubmitPduArray, 0); + return smsSubmitPduArray; + } + else + { + throw new ArgumentException("A concatenated message must not have more than 255 parts.", "operatorLogo"); + } + } + else + { + throw new ArgumentException("operatorLogo must not be an empty array.", "operatorLogo"); + } + } + else + { + throw new ArgumentNullException("operatorLogo"); + } + } + + /// + /// Creates a user data header with port addressing information. + /// + /// The message's destination port. + /// A byte array containing the user data header. + public static byte[] CreatePortAddressHeader(ushort destinationPort) + { + PortAddressElement16 portAddressElement16 = new PortAddressElement16(destinationPort, 0); + return SmartMessageFactory.CreateUserDataHeader(portAddressElement16); + } + + /// + /// Creates a user data header out of information elements. + /// + /// The instance to be stored in the header. + /// A byte array containing the user data header. + /// element is null. + /// Element is too large, size exceeds 255 bytes. + public static byte[] CreateUserDataHeader(InformationElement element) + { + if (element != null) + { + byte[] byteArray = element.ToByteArray(); + if ((int)byteArray.Length <= 255) + { + byte[] length = new byte[(int)byteArray.Length + 1]; + length[0] = (byte)((int)byteArray.Length); + byteArray.CopyTo(length, 1); + return length; + } + else + { + throw new ArgumentException("Element is to large, size exceeds 255 bytes."); + } + } + else + { + throw new ArgumentNullException("element"); + } + } + + /// + /// Creates a user data header out of information elements. + /// + /// The instances to be stored in the header. + /// A byte array containing the user data header. + /// elements is null. + /// The sum of all elements is too large, size exceeds 255 bytes. + public static byte[] CreateUserDataHeader(InformationElement[] elements) + { + if (elements != null) + { + List nums = new List(); + nums.Add(0); + int length = 0; + InformationElement[] informationElementArray = elements; + for (int i = 0; i < (int)informationElementArray.Length; i++) + { + InformationElement informationElement = informationElementArray[i]; + byte[] byteArray = informationElement.ToByteArray(); + nums.AddRange(byteArray); + length = length + (int)byteArray.Length; + } + if (length <= 255) + { + nums[0] = (byte)length; + return nums.ToArray(); + } + else + { + throw new ArgumentException("The sum of all elements is too large, size exceeds 255 bytes."); + } + } + else + { + throw new ArgumentNullException("elements"); + } + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmartMessaging/UnknownInformationElement.cs b/PDUConverter/GsmComm.PduConverter/SmartMessaging/UnknownInformationElement.cs new file mode 100644 index 0000000..bbba81c --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmartMessaging/UnknownInformationElement.cs @@ -0,0 +1,114 @@ +using System; + +/// +/// Implements an unknown information element. +/// +namespace GsmComm.PduConverter.SmartMessaging +{ + public class UnknownInformationElement : InformationElement + { + private byte identifier; + + private byte[] data; + + /// + /// Gets the information element data. + /// + public byte[] Data + { + get + { + return this.data; + } + } + + /// + /// Gets the information element identifier. + /// + public byte Identifier + { + get + { + return this.identifier; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The information element identifier. + /// The information element data. + /// data is null. + /// data is larger than 255 bytes. + public UnknownInformationElement(byte identifier, byte[] data) + { + if (data != null) + { + if ((int)data.Length <= 255) + { + this.identifier = identifier; + this.data = data; + return; + } + else + { + throw new ArgumentException("Data must be between 0 and 255 bytes long.", "data"); + } + } + else + { + throw new ArgumentNullException("data"); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The information element as a byte array. + /// element is null. + /// Information element is shorter than 2 bytes. + /// Available number of data bytes is less than specified in data length. + public UnknownInformationElement(byte[] element) + { + if (element != null) + { + if ((int)element.Length >= 2) + { + this.identifier = element[0]; + byte num = element[1]; + if ((int)element.Length >= num + 2) + { + this.data = new byte[num]; + Array.Copy(element, 2, this.data, 0, num); + return; + } + else + { + throw new FormatException("Available number of data bytes is less then specified in data length."); + } + } + else + { + throw new ArgumentException("Information element must at least be 2 bytes long.", "element"); + } + } + else + { + throw new ArgumentNullException("element"); + } + } + + /// + /// Returns the byte array equivalent of this instance. + /// + /// The byte array. + public override byte[] ToByteArray() + { + byte[] length = new byte[2 + (int)this.data.Length]; + length[0] = this.identifier; + length[1] = (byte)((int)this.data.Length); + this.data.CopyTo(length, 2); + return length; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsDeliverMessageFlags.cs b/PDUConverter/GsmComm.PduConverter/SmsDeliverMessageFlags.cs new file mode 100644 index 0000000..d5d7f57 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsDeliverMessageFlags.cs @@ -0,0 +1,173 @@ +using System; + +/// +/// Represents the the first octet of an SMS-DELIVER PDU. +/// +namespace GsmComm.PduConverter +{ + public class SmsDeliverMessageFlags : IncomingMessageFlags + { + private const byte TP_MTI_SMS_Deliver = 0; + + private const byte TP_MMS = 4; + + private const byte TP_SRI = 32; + + private const byte TP_UDHI = 64; + + private const byte TP_RP = 128; + + private bool moreMessages; + + private bool statusReportRequested; + + private bool userDataHeaderPresent; + + private bool replyPathExists; + + /// + /// Gets the type of the message. + /// + /// Always returns . + public override IncomingMessageType MessageType + { + get + { + return IncomingMessageType.SmsDeliver; + } + } + + /// + /// Gets or sets if there are more messages to send. + /// + public bool MoreMessages + { + get + { + return this.moreMessages; + } + set + { + this.moreMessages = value; + } + } + + /// + /// Gets or sets if a reply path exists. + /// + public bool ReplyPathExists + { + get + { + return this.replyPathExists; + } + set + { + this.replyPathExists = value; + } + } + + /// + /// Gets or sets if a status report was be requested. + /// + public bool StatusReportRequested + { + get + { + return this.statusReportRequested; + } + set + { + this.statusReportRequested = value; + } + } + + /// + /// Gets or sets if a user data header is present. + /// + public bool UserDataHeaderPresent + { + get + { + return this.userDataHeaderPresent; + } + set + { + this.userDataHeaderPresent = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public SmsDeliverMessageFlags() + { + this.moreMessages = false; + this.statusReportRequested = false; + this.userDataHeaderPresent = false; + this.replyPathExists = false; + } + + /// + /// Initializes a new instance of the SmsDeliverMessageFlags class with a + /// predefined data byte. + /// + /// The message flags as a byte value. + public SmsDeliverMessageFlags(byte flags) + { + this.FromByte(flags); + } + + /// + /// Fills the object with values from the data byte. + /// + /// The byte value. + protected override void FromByte(byte b) + { + IncomingMessageType incomingMessageType = IncomingMessageType.SmsDeliver; + if (0 > 0) + { + incomingMessageType = IncomingMessageType.SmsDeliver; + } + if (incomingMessageType == IncomingMessageType.SmsDeliver) + { + this.moreMessages = (b & 4) == 0; + this.StatusReportRequested = (b & 32) > 0; + this.userDataHeaderPresent = (b & 64) > 0; + this.replyPathExists = (b & 128) > 0; + return; + } + else + { + throw new ArgumentException("Not an SMS-DELIVER message."); + } + } + + /// + /// Returns the byte equivalent of this instance. + /// + /// The byte value. + public override byte ToByte() + { + byte num = 0; + num = (byte)num; + if (!this.moreMessages) + { + num = (byte)(num | 4); + } + if (this.StatusReportRequested) + { + num = (byte)(num | 32); + } + if (this.userDataHeaderPresent) + { + num = (byte)(num | 64); + } + if (this.replyPathExists) + { + num = (byte)(num | 128); + } + return num; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsDeliverPdu.cs b/PDUConverter/GsmComm.PduConverter/SmsDeliverPdu.cs new file mode 100644 index 0000000..91e3936 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsDeliverPdu.cs @@ -0,0 +1,253 @@ +using System; + +/// +/// Represents an SMS-DELIVER PDU, a received short message. +/// +namespace GsmComm.PduConverter +{ + public class SmsDeliverPdu : IncomingSmsPdu + { + private byte originatingAddressType; + + private string originatingAddress; + + private SmsTimestamp scTimestamp; + + /// + /// Gets the message flags. + /// + /// The property is being set + /// and the value is null. + public SmsDeliverMessageFlags MessageFlags + { + get + { + return (SmsDeliverMessageFlags)this.messageFlags; + } + } + + /// + /// Gets or sets if there are more messages to send. + /// + public bool MoreMessages + { + get + { + return this.MessageFlags.MoreMessages; + } + set + { + this.MessageFlags.MoreMessages = value; + } + } + + /// + /// Gets or sets the originating address. + /// + /// + /// When setting the property, also the property + /// will be set, attempting to autodetect the address type. + /// When getting the property, the address may be extended with address-type + /// specific prefixes or other chraracters. + /// + public string OriginatingAddress + { + get + { + return base.CreateAddressOfType(this.originatingAddress, this.originatingAddressType); + } + set + { + byte num = 0; + string str = null; + base.FindTypeOfAddress(value, out num, out str); + this.originatingAddress = str; + this.originatingAddressType = num; + } + } + + /// + /// Gets or sets the type of the originating address. + /// + /// + /// Represents the Type-of-Address octets for the originating address of the PDU. + /// + public byte OriginatingAddressType + { + get + { + return this.originatingAddressType; + } + } + + /// + /// Gets or sets if a reply path exists. + /// + public bool ReplyPathExists + { + get + { + return this.MessageFlags.ReplyPathExists; + } + set + { + this.MessageFlags.ReplyPathExists = value; + } + } + + /// + /// Gets or sets the timestamp the message was received by the SC. + /// + public SmsTimestamp SCTimestamp + { + get + { + return this.scTimestamp; + } + set + { + this.scTimestamp = value; + } + } + + /// + /// Gets or sets if a status report was be requested. + /// + public bool StatusReportRequested + { + get + { + return this.MessageFlags.StatusReportRequested; + } + set + { + this.MessageFlags.StatusReportRequested = value; + } + } + + /// + /// Gets or sets if a user data header is present. + /// + public override bool UserDataHeaderPresent + { + get + { + return this.MessageFlags.UserDataHeaderPresent; + } + set + { + this.MessageFlags.UserDataHeaderPresent = value; + } + } + + /// + /// Initializes a new instance using default values. + /// + public SmsDeliverPdu() + { + this.messageFlags = new SmsDeliverMessageFlags(); + this.originatingAddress = string.Empty; + this.originatingAddressType = 0; + this.scTimestamp = SmsTimestamp.None; + } + + /// + /// Initializes a new instance of the class + /// using the specified PDU string. + /// + /// The PDU string to convert. + /// Specifies if the string contains + /// SMSC data octets at the beginning. + /// Specifies the actual PDU length, that is the length in bytes without + /// the SMSC header. Set to -1 if unknown. + /// + /// This constructor assumes that the string contains an SMS-DELIVER + /// PDU data stream as specified by GSM 07.05. + /// + public SmsDeliverPdu(string pdu, bool includesSmscData, int actualLength) + { + string str = null; + byte num = 0; + byte num1 = 0; + byte[] numArray = null; + if (pdu != string.Empty) + { + bool flag = actualLength >= 0; + int num2 = actualLength; + if (!flag || num2 > 0) + { + int num3 = 0; + if (includesSmscData) + { + PduParts.DecodeSmscAddress(pdu, ref num3, out str, out num); + base.SetSmscAddress(str, num); + } + int num4 = num3; + num3 = num4 + 1; + this.messageFlags = new SmsDeliverMessageFlags(BcdWorker.GetByte(pdu, num4)); + if (flag) + { + num2--; + if (num2 <= 0) + { + base.ConstructLength = num3 * 2; + return; + } + } + PduParts.DecodeGeneralAddress(pdu, ref num3, out this.originatingAddress, out this.originatingAddressType); + if (num3 * 2 < pdu.Length) + { + int num5 = num3; + num3 = num5 + 1; + base.ProtocolID = BcdWorker.GetByte(pdu, num5); + int num6 = num3; + num3 = num6 + 1; + base.DataCodingScheme = BcdWorker.GetByte(pdu, num6); + this.scTimestamp = new SmsTimestamp(pdu, ref num3); + PduParts.DecodeUserData(pdu, ref num3, base.DataCodingScheme, out num1, out numArray); + base.SetUserData(numArray, num1); + base.ConstructLength = num3 * 2; + return; + } + else + { + this.scTimestamp = SmsTimestamp.None; + base.ProtocolID = 145; + base.DataCodingScheme = 137; + base.ConstructLength = num3 * 2; + return; + } + } + else + { + return; + } + } + else + { + throw new ArgumentException("pdu must not be an empty string."); + } + } + + /// + /// Returns the relevant timestamp for the message. + /// + /// An containing the SMSC timestamp, + /// the time the message was received by the service center. + public override SmsTimestamp GetTimestamp() + { + return this.scTimestamp; + } + + /// + /// Converts the value of this instance into a string. + /// + /// If true, excludes the SMSC header. + /// The encoded string. + /// Not implemented, always throws an . + public override string ToString(bool excludeSmscData) + { + throw new NotImplementedException("SmsDeliverPdu.ToString() not implemented."); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsPdu.cs b/PDUConverter/GsmComm.PduConverter/SmsPdu.cs new file mode 100644 index 0000000..582bee2 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsPdu.cs @@ -0,0 +1,628 @@ +using System; +using System.Text; + +/// +/// Provides the base for an SMS PDU. +/// +namespace GsmComm.PduConverter +{ + public abstract class SmsPdu : ITimestamp + { + private const int maxSeptets = 160; + + private const byte maxOctets = 140; + + /// + /// Gets the maximum message text length in septets. + /// + public const int MaxTextLength = 160; + + /// + /// Gets the maximum Unicode message text length in characters. + /// + public const int MaxUnicodeTextLength = 70; + + private byte smscTOA; + + private string smscAddress; + + private byte PID; + + private byte DCS; + + private byte userDataLength; + + private byte[] userData; + + private int constructLength; + + /// + /// Gets the length of the actual PDU data part in bytes. That is, + /// without the SMSC header. + /// + public int ActualLength + { + get + { + return this.ToString(true).Length / 2; + } + } + + /// + /// Gets the number of characters that have been actually been used for decoding upon construction. + /// + public int ConstructLength + { + get + { + return this.constructLength; + } + protected set + { + this.constructLength = value; + } + } + + /// + /// Gets or sets the data coding scheme. + /// + /// Represents the TP-DCS octet of the PDU. + /// The data coding scheme specifies how the data is coded + /// and may also specify a message class. + public byte DataCodingScheme + { + get + { + return this.DCS; + } + set + { + this.DCS = value; + } + } + + /// + /// Gets or sets the protocol identifier. + /// + /// Represents the TP-PID octet of the PDU. + public byte ProtocolID + { + get + { + return this.PID; + } + set + { + this.PID = value; + } + } + + /// + /// Gets or sets the SMSC address. + /// + /// + /// When setting the property: Also the property will be set, + /// attempting to autodetect the address type. + /// When getting the property: The address may be extended with address-type + /// specific prefixes or other chraracters. + /// + public string SmscAddress + { + get + { + return this.CreateAddressOfType(this.smscAddress, this.smscTOA); + } + set + { + byte num = 0; + string str = null; + this.FindTypeOfAddress(value, out num, out str); + this.smscAddress = str; + this.smscTOA = num; + } + } + + /// + /// Gets the type of the SMSC address. + /// + /// + /// Represents the Type-of-Address octets for the SMSC address of the PDU. + /// + public byte SmscAddressType + { + get + { + return this.smscTOA; + } + } + + /// + /// Gets the total length of the PDU string in bytes. + /// + public int TotalLength + { + get + { + return this.ToString().Length / 2; + } + } + + /// + /// Gets the user data. + /// + /// + /// Represents the TP-User-Data octet of the PDU. + /// + public byte[] UserData + { + get + { + return this.userData; + } + } + + /// + /// Gets or sets if a user data header is present. + /// + public abstract bool UserDataHeaderPresent + { + get; + set; + } + + /// + /// Gets the user data length. + /// + /// + /// Represents the TP-User-Data-Length octet of the PDU. + /// The does not necessarily match the number + /// of bytes in the because it may be further encoded. + /// + public byte UserDataLength + { + get + { + return this.userDataLength; + } + } + + /// + /// Gets or sets the user data text (i.e. the message text). + /// + /// + /// This property supports automatic encoding and decoding of text from and to the GSM 7-bit default + /// alphabet and the UCS2 charset. For this to work properly, the + /// property must be set correctly before setting the UserDataText. + /// For all other data with other alphabets or special data codings, the property + /// must be used to get or set the data. + /// Setting this property also sets the property accordingly. + /// + public string UserDataText + { + get + { + return PduParts.DecodeText(this.userData, this.DCS); + } + set + { + DataCodingScheme dataCodingScheme = GsmComm.PduConverter.DataCodingScheme.Decode(this.DCS); + if (dataCodingScheme.Alphabet != 2) + { + this.Encode7BitText(value); + return; + } + else + { + this.EncodeUcs2Text(value); + return; + } + } + } + + /// + /// Initializes a new instance using default values. + /// + protected SmsPdu() + { + this.SmscAddress = string.Empty; + this.PID = 0; + this.DCS = 0; + this.userDataLength = 0; + this.userData = null; + } + + /// + /// Adds a user data header to an existing user data. + /// + /// The user data header to add. + /// There is already a user data header present in this message. + /// The resulting total of user data header and existing user data exceeds the allowed + /// maximum data length. + /// + /// The user data must already be set before adding a user data header. + /// Adding a user data header reduces the available space for the remaining user data. If the resulting total of + /// user data header and existing user data exceeds allowed maximum data length, an exception is raised. + /// + public void AddUserDataHeader(byte[] header) + { + int num; + if (this.UserDataHeaderPresent) + { + throw new InvalidOperationException("There is already a user data header present in this message."); + } + else + { + int length = (int)header.Length; + byte num1 = (byte)((double)length * 8 / 7); + DataCodingScheme dataCodingScheme = GsmComm.PduConverter.DataCodingScheme.Decode(this.DCS); + if (dataCodingScheme.Alphabet != 0) + { + if (dataCodingScheme.Alphabet == 1 || dataCodingScheme.Alphabet == 2) + { + num = length + this.userDataLength; + } + else + { + num = num1 + this.userDataLength; + } + } + else + { + num = num1 + this.userDataLength; + } + byte[] numArray = new byte[(int)header.Length + (int)this.userData.Length]; + header.CopyTo(numArray, 0); + this.userData.CopyTo(numArray, (int)header.Length); + this.SetUserData(numArray, (byte)num); + this.UserDataHeaderPresent = true; + return; + } + } + + /// + /// Modifies an address to make it look like the specified address type. + /// + /// The address (phone number). + /// The address type. + /// The modified address. + /// If the address can't be modified, the original string is returned. + protected string CreateAddressOfType(string address, byte type) + { + if (!(address != string.Empty) || type != 145 || address.StartsWith("+")) + { + return address; + } + else + { + return string.Concat("+", address); + } + } + + /// + /// Decodes the text from 7-Bit user data in this instance. + /// + /// The decoded . + /// This method assumes that the property contains an encoded + /// GSM 7-Bit default text packed into octets. If contains something different, + /// the results are not defined. + protected string Decode7BitText() + { + return PduParts.Decode7BitText(this.userData); + } + + /// + /// Decodes the text from UCS2 (16-Bit) user data in this instance. + /// + /// The decoded . + /// This method assumes that the property contains an encoded + /// UCS2 text. If contains something different, the results are not defined. + /// + protected string DecodeUcs2Text() + { + return PduParts.DecodeUcs2Text(this.userData); + } + + /// + /// Encodes the specified text as 7-Bit user data in this instance. + /// + /// The text to encode. + /// The text is converted to the GSM 7-Bit default alphabet first, then it is packed into octets. + /// The final result is saved in the properties and . + /// + /// Text is too long. + protected void Encode7BitText(string text) + { + string str = TextDataConverter.StringTo7Bit(text); + int length = str.Length; + if (length <= 160) + { + this.SetUserData(TextDataConverter.SeptetsToOctetsInt(str), (byte)length); + return; + } + else + { + string[] strArrays = new string[5]; + strArrays[0] = "Text is too long. A maximum of "; + int num = 160; + strArrays[1] = num.ToString(); + strArrays[2] = " resulting septets is allowed. The current input results in "; + strArrays[3] = length.ToString(); + strArrays[4] = " septets."; + throw new ArgumentException(string.Concat(strArrays)); + } + } + + /// + /// Encodes the specified text as UCS2 (16-Bit) user data in this instance. + /// + /// The text to encode. + /// The text is converted to the UCS2 character set. The result is saved in the properties + /// and . + protected void EncodeUcs2Text(string text) + { + Encoding bigEndianUnicode = Encoding.BigEndianUnicode; + byte[] bytes = bigEndianUnicode.GetBytes(text); + int length = (int)bytes.Length; + if (length <= 140) + { + this.SetUserData(bytes, (byte)length); + return; + } + else + { + string[] str = new string[5]; + str[0] = "Text is too long. A maximum of "; + byte num = 140; + str[1] = num.ToString(); + str[2] = " resulting octets is allowed. The current input results in "; + str[3] = length.ToString(); + str[4] = " octets."; + throw new ArgumentException(string.Concat(str)); + } + } + + /// + /// Determines the address type. + /// + /// The address (phone number). + /// The detected address type. + /// The modified address that can be directly used for communication. + /// If you use address "+4812345678" the resulting type will be 0x91 and useThisAddress + /// will be "4812345678". Call to recreate the original address. + protected void FindTypeOfAddress(string address, out byte type, out string useThisAddress) + { + byte num = default(byte); + if (address == string.Empty) + { + useThisAddress = address; + type = 0; + return; + } + else + { + while (address.StartsWith("+")) + { + num = 145; + address = address.Substring(1); + } + useThisAddress = address; + type = num; + return; + } + } + + /// + /// Modifies the message text so that it is safe to be sent via GSM 7-Bit default encoding. + /// + /// The message text. + /// The converted message text. + /// Replaces invalid characters in the text and truncates it to the maximum allowed length. + public static string GetSafeText(string data) + { + bool flag = false; + bool flag1 = false; + return SmsPdu.GetSafeText(data, out flag, out flag1); + } + + /// + /// Modifies the message text so that it is safe to be sent via GSM 7-Bit default encoding. + /// + /// The message text. + /// Will be set to true if the message length was corrected. + /// Will be set to true if one or more characters were replaced. + /// The converted message text. + /// Replaces invalid characters in the text and truncates it to the maximum allowed length. + public static string GetSafeText(string data, out bool lengthCorrected, out bool charsCorrected) + { + string str; + string str1; + bool flag = false; + lengthCorrected = false; + charsCorrected = false; + if (data.Length <= 160) + { + str = data; + } + else + { + str = data.Substring(0, 160); + lengthCorrected = true; + } + do + { + str1 = TextDataConverter.StringTo7Bit(str, false, out flag); + if (flag) + { + charsCorrected = true; + } + if (str1.Length <= 160) + { + continue; + } + str = data.Substring(0, str.Length - 1); + lengthCorrected = true; + } + while (str1.Length > 160); + string str2 = TextDataConverter.SevenBitToString(str1); + return str2; + } + + /// + /// Gets the SMSC address and the type as it is saved internally. + /// + /// The SMSC address. + /// The address type of address. + public void GetSmscAddress(out string address, out byte addressType) + { + address = this.smscAddress; + addressType = this.smscTOA; + } + + /// + /// Gets the length in septets of the specified text. + /// + /// The text the get the length for. + /// The text length. + public static int GetTextLength(string text) + { + return TextDataConverter.StringTo7Bit(text).Length; + } + + /// + /// In derived classes, returns the relevant timestamp for the message. + /// + /// The timestamp. + /// If the message type does not have a relevant timestamp, + /// it returns + public abstract SmsTimestamp GetTimestamp(); + + /// + /// Extracts the user data header out of the user data. + /// + /// A byte array containing the extracted header. + /// There is no user data header is present in this message. + /// Use to determine whether a user data header is present. + public byte[] GetUserDataHeader() + { + if (!this.UserDataHeaderPresent) + { + throw new InvalidOperationException("There is no user data header is present in this message."); + } + else + { + byte num = this.userData[0]; + num = (byte)(num + 1); + byte[] numArray = new byte[num]; + Array.Copy(this.userData, numArray, num); + return numArray; + } + } + + /// + /// Extracts the section of the user data that is not occupied by the user data header + /// and returns it as text. + /// + /// A string containing the extracted text. + /// There is no user data header is present in this message. + /// Use to determine whether a user data header is present. + public string GetUserDataTextWithoutHeader() + { + byte[] userDataWithoutHeader = this.GetUserDataWithoutHeader(); + return PduParts.DecodeText(userDataWithoutHeader, this.DCS); + } + + /// + /// Extracts the section of the user data that is not occupied by the user data header. + /// + /// A byte array containing the extracted data. + /// There is no user data header is present in this message. + /// Use to determine whether a user data header is present. + public byte[] GetUserDataWithoutHeader() + { + if (!this.UserDataHeaderPresent) + { + throw new InvalidOperationException("There is no user data header is present in this message."); + } + else + { + byte num = this.userData[0]; + num = (byte)(num + 1); + int length = (int)this.userData.Length - num; + byte[] numArray = new byte[length]; + Array.Copy(this.userData, num, numArray, 0, length); + return numArray; + } + } + + /// + /// Checks if the user data portion of the PDU is complete. + /// + /// true if all data is available, false otherwise. + /// This method can be used to verify that the user data has not been truncated or otherwise + /// invalidated. + public bool IsUserDataComplete() + { + int remainingUserDataBytes = PduParts.GetRemainingUserDataBytes(this.userDataLength, this.DCS); + return (int)this.userData.Length >= remainingUserDataBytes; + } + + /// + /// Sets the SMSC address and type directly without attempting to + /// autodetect the type. + /// + /// The SMSC address. + /// The address type of address. + public void SetSmscAddress(string address, byte addressType) + { + this.smscAddress = address; + this.smscTOA = addressType; + } + + /// + /// Sets the user data using raw octets. + /// + /// The user data directly as a byte array. + /// The length of the data. Note that this is not necessarily + /// the number of bytes in the array, the length depends on the data coding. + /// UserData is too long, more than 140 octets were passed + /// Assumes that raw octets are passed. Use the property + /// if you want to pass text. + public void SetUserData(byte[] data, byte dataLength) + { + if ((int)data.Length <= 140) + { + this.userData = data; + this.userDataLength = dataLength; + return; + } + else + { + string[] str = new string[5]; + str[0] = "User data is too long. A maximum of "; + byte num = 140; + str[1] = num.ToString(); + str[2] = " octets is allowed. "; + int length = (int)data.Length; + str[3] = length.ToString(); + str[4] = " octets were passed."; + throw new ArgumentException(string.Concat(str)); + } + } + + /// + /// In derived classes, converts the value of this instance into a string. + /// + /// If true, excludes the SMSC header, otherwise it is included. + /// The encoded string. + public abstract string ToString(bool excludeSmscData); + + /// + /// Converts the value of this instance into a string, including SMSC header. + /// + /// The encoded string. + public override string ToString() + { + return this.ToString(false); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsStatusReportMessageFlags.cs b/PDUConverter/GsmComm.PduConverter/SmsStatusReportMessageFlags.cs new file mode 100644 index 0000000..0293a31 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsStatusReportMessageFlags.cs @@ -0,0 +1,151 @@ +using System; + +/// +/// Represents the the first octet of an SMS-STATUS-REPORT PDU. +/// +namespace GsmComm.PduConverter +{ + public class SmsStatusReportMessageFlags : IncomingMessageFlags + { + private const byte TP_MTI_SMS_Status_Report = 2; + + private const byte TP_UDHI = 4; + + private const byte TP_MMS = 8; + + private const byte TP_SRQ = 16; + + private bool userDataHeaderPresent; + + private bool moreMessages; + + private bool qualifier; + + /// + /// Parameter describing the message type. + /// + public override IncomingMessageType MessageType + { + get + { + return IncomingMessageType.SmsStatusReport; + } + } + + /// + /// Parameter indicating whether or not there are more messages to send. + /// + public bool MoreMessages + { + get + { + return this.moreMessages; + } + set + { + this.moreMessages = value; + } + } + + /// + /// Parameter indicating whether the previously submitted TPDU was an + /// SMS-SUBMIT or an SMS-COMMAND. + /// + /// + /// false = SMS-STATUS-REPORT is the result of a SMS-SUBMIT + /// true = SMS-STATUS-REPORT is the result of a SMS-COMMAND + /// + public bool Qualifier + { + get + { + return this.qualifier; + } + set + { + this.qualifier = value; + } + } + + /// + /// Parameter indicating that the TP-UD field contains a Header. + /// + public bool UserDataHeaderPresent + { + get + { + return this.userDataHeaderPresent; + } + set + { + this.userDataHeaderPresent = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + public SmsStatusReportMessageFlags() + { + this.userDataHeaderPresent = false; + this.moreMessages = false; + this.qualifier = false; + } + + /// + /// Initializes a new instance of the IncomingMessageFlags class with a + /// predefined data byte. + /// + public SmsStatusReportMessageFlags(byte flags) + { + this.FromByte(flags); + } + + /// + /// Fills the object with values from the data byte. + /// + /// The byte value. + protected override void FromByte(byte b) + { + IncomingMessageType incomingMessageType = IncomingMessageType.SmsStatusReport; + if ((b & 2) > 0) + { + incomingMessageType = IncomingMessageType.SmsStatusReport; + } + if (incomingMessageType == IncomingMessageType.SmsStatusReport) + { + this.userDataHeaderPresent = (b & 4) > 0; + this.moreMessages = (b & 8) == 0; + this.qualifier = (b & 16) > 0; + return; + } + else + { + throw new ArgumentException("Not an SMS-STATUS-REPORT message."); + } + } + + /// + /// Returns the byte equivalent of this instance. + /// + /// The byte value. + public override byte ToByte() + { + byte num = 0; + num = (byte)(num | 2); + if (this.userDataHeaderPresent) + { + num = (byte)(num | 4); + } + if (!this.moreMessages) + { + num = (byte)(num | 8); + } + if (this.qualifier) + { + num = (byte)(num | 16); + } + return num; + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsStatusReportPdu.cs b/PDUConverter/GsmComm.PduConverter/SmsStatusReportPdu.cs new file mode 100644 index 0000000..f84f9d8 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsStatusReportPdu.cs @@ -0,0 +1,380 @@ +using System; + +/// +/// Represents an SMS-STATUS-REPORT PDU, a status report message. +/// +namespace GsmComm.PduConverter +{ + public class SmsStatusReportPdu : IncomingSmsPdu + { + private byte messageReference; + + private byte recipientAddressType; + + private string recipientAddress; + + private SmsTimestamp scTimestamp; + + private SmsTimestamp dischargeTime; + + private MessageStatus status; + + private ParameterIndicator parameterIndicator; + + /// + /// Gets or sets the parameter identifying the time associated with a + /// particular TP-ST outcome. + /// + public SmsTimestamp DischargeTime + { + get + { + return this.dischargeTime; + } + set + { + this.dischargeTime = value; + } + } + + /// + /// Gets the message flags. + /// + public SmsStatusReportMessageFlags MessageFlags + { + get + { + return (SmsStatusReportMessageFlags)this.messageFlags; + } + } + + /// + /// Gets or sets the parameter identifying the previously submitted + /// SMS-SUBMIT or an SMS-COMMAND. + /// + public byte MessageReference + { + get + { + return this.messageReference; + } + set + { + this.messageReference = value; + } + } + + /// + /// Parameter indicating whether or not there are more messages to send. + /// + public bool MoreMessages + { + get + { + return this.MessageFlags.MoreMessages; + } + set + { + this.MessageFlags.MoreMessages = value; + } + } + + /// + /// Gets or sets the parameter indicating the presence of any of the + /// optional parameters which follow. + /// + public ParameterIndicator ParameterIndicator + { + get + { + return this.parameterIndicator; + } + set + { + this.parameterIndicator = value; + } + } + + /// + /// Parameter indicating whether the previously submitted TPDU was an + /// SMS-SUBMIT or an SMS-COMMAND. + /// + /// + /// false = SMS-STATUS-REPORT is the result of a SMS-SUBMIT + /// true = SMS-STATUS-REPORT is the result of a SMS-COMMAND + /// + public bool Qualifier + { + get + { + return this.MessageFlags.Qualifier; + } + set + { + this.MessageFlags.Qualifier = value; + } + } + + /// + /// Gets or sets the address of the recipient of the previously + /// submitted mobile originated short message. + /// + /// + /// When setting the property, also the property + /// will be set, attempting to autodetect the address type. + /// When getting the property, the address may be extended with address-type + /// specific prefixes or other chraracters. + /// + public string RecipientAddress + { + get + { + return base.CreateAddressOfType(this.recipientAddress, this.recipientAddressType); + } + set + { + byte num = 0; + string str = null; + base.FindTypeOfAddress(value, out num, out str); + this.recipientAddress = str; + this.recipientAddressType = num; + } + } + + /// + /// Gets the type of the recipient address. + /// + /// + /// Represents the Type-of-Address octets for the recipient address of the PDU. + /// + public byte RecipientAddressType + { + get + { + return this.recipientAddressType; + } + } + + /// + /// Gets or sets the parameter identifying time when the SC received + /// the previously sent SMS-SUBMIT. + /// + public SmsTimestamp SCTimestamp + { + get + { + return this.scTimestamp; + } + set + { + this.scTimestamp = value; + } + } + + /// + /// Gets or sets the parameter identifying the status of the previously + /// sent mobile originated short message. + /// + public MessageStatus Status + { + get + { + return this.status; + } + set + { + this.status = value; + } + } + + /// + /// Parameter indicating that the TP-UD field contains a Header. + /// + public override bool UserDataHeaderPresent + { + get + { + return this.MessageFlags.UserDataHeaderPresent; + } + set + { + this.MessageFlags.UserDataHeaderPresent = value; + } + } + + /// + /// Initializes a new instance using default values. + /// + public SmsStatusReportPdu() + { + this.messageFlags = new SmsStatusReportMessageFlags(); + this.messageReference = 0; + this.RecipientAddress = string.Empty; + this.scTimestamp = SmsTimestamp.None; + this.dischargeTime = SmsTimestamp.None; + this.status = 0; + this.parameterIndicator = null; + } + + /// + /// Initializes a new instance of the class + /// using the specified PDU string. + /// + /// The PDU string to convert. + /// Specifies if the string contains + /// SMSC data octets at the beginning. + /// Specifies the actual PDU length, that is the length in bytes without + /// the SMSC header. Set to -1 if unknown. + /// + /// This constructor assumes that the string contains an SMS-STATUS-REPORT + /// PDU data stream as specified + /// by GSM 07.05. + /// + public SmsStatusReportPdu(string pdu, bool includesSmscData, int actualLength) + { + string str = null; + byte num = 0; + ParameterIndicator parameterIndicator; + byte num1 = 0; + byte[] numArray = null; + if (pdu != string.Empty) + { + bool flag = actualLength >= 0; + int num2 = actualLength; + if (!flag || num2 > 0) + { + int num3 = 0; + if (includesSmscData) + { + PduParts.DecodeSmscAddress(pdu, ref num3, out str, out num); + base.SetSmscAddress(str, num); + } + int num4 = num3; + num3 = num4 + 1; + this.messageFlags = new SmsStatusReportMessageFlags(BcdWorker.GetByte(pdu, num4)); + if (flag) + { + num2--; + if (num2 <= 0) + { + base.ConstructLength = num3 * 2; + return; + } + } + int num5 = num3; + num3 = num5 + 1; + this.messageReference = BcdWorker.GetByte(pdu, num5); + PduParts.DecodeGeneralAddress(pdu, ref num3, out this.recipientAddress, out this.recipientAddressType); + if (num3 * 2 < pdu.Length) + { + this.scTimestamp = new SmsTimestamp(pdu, ref num3); + this.dischargeTime = new SmsTimestamp(pdu, ref num3); + int num6 = num3; + num3 = num6 + 1; + this.status = BcdWorker.GetByte(pdu, num6); + int num7 = BcdWorker.CountBytes(pdu); + if (num3 < num7) + { + int num8 = num3; + num3 = num8 + 1; + this.parameterIndicator = new ParameterIndicator(BcdWorker.GetByte(pdu, num8)); + if (this.parameterIndicator.Extension) + { + do + { + int num9 = BcdWorker.CountBytes(pdu); + if (num3 < num9) + { + int num10 = num3; + num3 = num10 + 1; + parameterIndicator = new ParameterIndicator(BcdWorker.GetByte(pdu, num10)); + } + else + { + base.ConstructLength = num3 * 2; + return; + } + } + while (parameterIndicator.Extension); + } + if (this.parameterIndicator.TP_PID) + { + int num11 = num3; + num3 = num11 + 1; + base.ProtocolID = BcdWorker.GetByte(pdu, num11); + } + if (this.parameterIndicator.TP_DCS) + { + int num12 = num3; + num3 = num12 + 1; + base.DataCodingScheme = BcdWorker.GetByte(pdu, num12); + } + if (this.parameterIndicator.TP_UDL) + { + PduParts.DecodeUserData(pdu, ref num3, base.DataCodingScheme, out num1, out numArray); + base.SetUserData(numArray, num1); + } + base.ConstructLength = num3 * 2; + return; + } + else + { + base.ConstructLength = num3 * 2; + return; + } + } + else + { + base.ProtocolID = 145; + base.DataCodingScheme = 137; + this.SCTimestamp = SmsTimestamp.None; + this.DischargeTime = SmsTimestamp.None; + base.ConstructLength = num3 * 2; + return; + } + } + else + { + return; + } + } + else + { + throw new ArgumentException("pdu must not be an empty string."); + } + } + + /// + /// Returns the relevant timestamp for the message. + /// + /// An containing the discharge time, the timestamp where + /// the status in this report occurred. + public override SmsTimestamp GetTimestamp() + { + return this.dischargeTime; + } + + /// + /// Sets the recipient address and type directly without attempting to + /// autodetect the type. + /// + /// The recipient address + /// The address type + public void SetRecipientAddress(string address, byte addressType) + { + this.recipientAddress = address; + this.recipientAddressType = addressType; + } + + /// + /// Converts the value of this instance into a string. + /// + /// If true, excludes the SMSC header. + /// The encoded string. + /// Not implemented, always throws an . + public override string ToString(bool excludeSmscData) + { + throw new NotImplementedException("SmsStatusReportPdu.ToString() not implemented."); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsSubmitMessageFlags.cs b/PDUConverter/GsmComm.PduConverter/SmsSubmitMessageFlags.cs new file mode 100644 index 0000000..54d0d01 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsSubmitMessageFlags.cs @@ -0,0 +1,259 @@ +using System; + +/// +/// Represents the the first octet of an SMS-SUBMIT-PDU. +/// +namespace GsmComm.PduConverter +{ + public class SmsSubmitMessageFlags : OutgoingMessageFlags + { + private const byte TP_MTI_SMS_Submit = 1; + + private const byte TP_RD = 4; + + private const byte TP_VPF_Enhanced = 8; + + private const byte TP_VPF_Relative = 16; + + private const byte TP_VPF_Absolute = 24; + + private const byte TP_SRR = 32; + + private const byte TP_UDHI = 64; + + private const byte TP_RP = 128; + + private bool rejectDuplicates; + + private ValidityPeriodFormat validityPeriodFormat; + + private bool requestStatusReport; + + private bool userDataHeaderPresent; + + private bool replyPathExists; + + /// + /// Gets the message type, always returns . + /// + public override OutgoingMessageType MessageType + { + get + { + return OutgoingMessageType.SmsSubmit; + } + } + + /// + /// Gets or sets if the SC should reject duplicate messages. + /// + public bool RejectDuplicates + { + get + { + return this.rejectDuplicates; + } + set + { + this.rejectDuplicates = value; + } + } + + /// + /// Gets or sets if a reply path exists. + /// + public bool ReplyPathExists + { + get + { + return this.replyPathExists; + } + set + { + this.replyPathExists = value; + } + } + + /// + /// Gets or sets if s status report should be requested. + /// + public bool RequestStatusReport + { + get + { + return this.requestStatusReport; + } + set + { + this.requestStatusReport = value; + } + } + + /// + /// Gets or sets if a user data header is present. + /// + public bool UserDataHeaderPresent + { + get + { + return this.userDataHeaderPresent; + } + set + { + this.userDataHeaderPresent = value; + } + } + + /// + /// Gets or sets the validity period format. + /// + public ValidityPeriodFormat ValidityPeriodFormat + { + get + { + return this.validityPeriodFormat; + } + set + { + this.validityPeriodFormat = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Default settings are: + /// + /// + /// ReplyPathExists = false + /// + /// + /// UserDataHeaderPresent = false + /// + /// + /// RequestStatusReport = false + /// + /// + /// ValidityPeriodFormat = ValidityPeriodFormat.Unspecified + /// + /// + /// RejectDuplicates = false + /// + /// + /// MessageType = OutgoingMessageType.SmsSubmit + /// + /// + /// + public SmsSubmitMessageFlags() + { + this.rejectDuplicates = false; + this.validityPeriodFormat = ValidityPeriodFormat.Unspecified; + this.requestStatusReport = false; + this.userDataHeaderPresent = false; + this.replyPathExists = false; + } + + /// + /// Initializes a new instance of the MessageFlags class with a + /// predefined data byte. + /// + public SmsSubmitMessageFlags(byte flags) + { + this.FromByte(flags); + } + + /// + /// Fills the object with values from the data byte. + /// + /// The byte value. + protected override void FromByte(byte b) + { + OutgoingMessageType outgoingMessageType = OutgoingMessageType.SmsSubmit; + if ((b & 1) > 0) + { + outgoingMessageType = OutgoingMessageType.SmsSubmit; + } + if (outgoingMessageType == OutgoingMessageType.SmsSubmit) + { + this.rejectDuplicates = (b & 4) > 0; + this.validityPeriodFormat = ValidityPeriodFormat.Unspecified; + if ((b & 24) > 0) + { + this.validityPeriodFormat = ValidityPeriodFormat.Absolute; + } + if ((b & 16) > 0) + { + this.validityPeriodFormat = ValidityPeriodFormat.Relative; + } + if ((b & 8) > 0) + { + this.validityPeriodFormat = ValidityPeriodFormat.Enhanced; + } + this.requestStatusReport = (b & 32) > 0; + this.userDataHeaderPresent = (b & 64) > 0; + this.replyPathExists = (b & 128) > 0; + return; + } + else + { + throw new ArgumentException("Not an SMS-SUBMIT message."); + } + } + + /// + /// Returns the byte equivalent of this instance. + /// + /// The byte value. + public override byte ToByte() + { + byte num = 0; + num = (byte)(num | 1); + if (this.rejectDuplicates) + { + num = (byte)(num | 4); + } + ValidityPeriodFormat validityPeriodFormat = this.validityPeriodFormat; + switch (validityPeriodFormat) + { + case ValidityPeriodFormat.Relative: + { + num = (byte)(num | 16); + break; + } + case ValidityPeriodFormat.Absolute: + { + num = (byte)(num | 24); + break; + } + case ValidityPeriodFormat.Enhanced: + { + num = (byte)(num | 8); + break; + } + } + if (this.requestStatusReport) + { + num = (byte)(num | 32); + } + if (this.userDataHeaderPresent) + { + num = (byte)(num | 64); + } + if (this.replyPathExists) + { + num = (byte)(num | 128); + } + return num; + } + + /// + /// Returns the string equivalent of this instance. + /// + public override string ToString() + { + byte num = this.ToByte(); + return num.ToString(); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsSubmitPdu.cs b/PDUConverter/GsmComm.PduConverter/SmsSubmitPdu.cs new file mode 100644 index 0000000..6d23391 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsSubmitPdu.cs @@ -0,0 +1,476 @@ +using System; +using System.Text; + +/// +/// Represents an SMS-SUBMIT PDU, an outgoing short message. +/// +namespace GsmComm.PduConverter +{ + public class SmsSubmitPdu : OutgoingSmsPdu + { + private byte destTOA; + + private string destAddress; + + private ValidityPeriod validityPeriod; + + /// + /// Gets or sets the destination address. + /// + /// + /// When setting the property also the + /// property will be set, attempting to autodetect the address type. + /// When getting the property: The address may be extended with address-type + /// specific prefixes or other chraracters. + /// + public string DestinationAddress + { + get + { + return base.CreateAddressOfType(this.destAddress, this.destTOA); + } + set + { + byte num = 0; + string str = null; + base.FindTypeOfAddress(value, out num, out str); + this.destAddress = str; + this.destTOA = num; + } + } + + /// + /// Gets the type of the destination address. + /// + /// + /// Represents the Type-of-Address octets for the destination address of the PDU. + /// + public byte DestinationAddressType + { + get + { + return this.destTOA; + } + } + + /// + /// Gets the message flags. + /// + public SmsSubmitMessageFlags MessageFlags + { + get + { + return (SmsSubmitMessageFlags)this.messageFlags; + } + } + + /// + /// Gets or sets if the SC should reject duplicate messages. + /// + public bool RejectDuplicates + { + get + { + return this.MessageFlags.RejectDuplicates; + } + set + { + this.MessageFlags.RejectDuplicates = value; + } + } + + /// + /// Gets or sets if a reply path exists. + /// + public bool ReplyPathExists + { + get + { + return this.MessageFlags.ReplyPathExists; + } + set + { + this.MessageFlags.ReplyPathExists = value; + } + } + + /// + /// Gets or sets if s status report should be requested. + /// + public bool RequestStatusReport + { + get + { + return this.MessageFlags.RequestStatusReport; + } + set + { + this.MessageFlags.RequestStatusReport = value; + } + } + + /// + /// Gets or sets if a user data header is present. + /// + public override bool UserDataHeaderPresent + { + get + { + return this.MessageFlags.UserDataHeaderPresent; + } + set + { + this.MessageFlags.UserDataHeaderPresent = value; + } + } + + /// + /// Gets or sets the validity period. + /// + /// + /// Represents the TP-Validity-Period octet of the PDU. + /// The validity period specifies the time when SM expires. If SM is't delivered + /// before that moment, it is discarded by SC. Validity-Period can be in + /// three different formats: Relative, Enhanced and Absolute. + /// + public ValidityPeriod ValidityPeriod + { + get + { + return this.validityPeriod; + } + set + { + if (value as RelativeValidityPeriod == null) + { + if (value != null) + { + throw new ArgumentException(string.Concat("Unknown or unsupported validity period format: ", value.GetType().ToString())); + } + else + { + this.MessageFlags.ValidityPeriodFormat = ValidityPeriodFormat.Unspecified; + } + } + else + { + this.MessageFlags.ValidityPeriodFormat = ValidityPeriodFormat.Relative; + } + this.validityPeriod = value; + } + } + + /// + /// Initializes a new instance using default values. + /// + public SmsSubmitPdu() + { + this.messageFlags = new SmsSubmitMessageFlags(); + this.DestinationAddress = string.Empty; + this.ValidityPeriod = new RelativeValidityPeriod(167); + } + + /// + /// Initializes a new instance of the class + /// using the specified text and destination address. + /// + /// The message text, not exceeding 160 characters. + /// The message's destination address. + public SmsSubmitPdu(string userDataText, string destinationAddress) : this() + { + base.Encode7BitText(userDataText); + this.DestinationAddress = destinationAddress; + } + + /// + /// Initializes a new instance of the class using the specified text, + /// destination address and SMSC address. + /// + /// The message text, not exceeding 160 characters. + /// The message's destination address. + /// The service center (SMSC) address. Can be an empty string. + public SmsSubmitPdu(string userDataText, string destinationAddress, string smscAddress) : this() + { + base.Encode7BitText(userDataText); + this.DestinationAddress = destinationAddress; + base.SmscAddress = smscAddress; + } + + /// + /// Initializes a new instance of the class using the specified text, + /// destination address and data coding scheme. + /// + /// The message text. + /// The message's destination address. + /// Specifies how the userDataText should be encoded. + /// The maximum length of the userDataText parameter depends on the alphabet specified with + /// the dataCodingScheme parameter.Common values for the dataCodingScheme are + /// for GSM default alphabet and + /// for UCS2 alphabet (Unicode). + public SmsSubmitPdu(string userDataText, string destinationAddress, byte dataCodingScheme) : this() + { + base.DataCodingScheme = dataCodingScheme; + base.UserDataText = userDataText; + this.DestinationAddress = destinationAddress; + } + + /// + /// Initializes a new instance of the class using the specified text, + /// destination address, SMSC address and data coding scheme. + /// + /// The message text. + /// The message's destination address. + /// The service center (SMSC) address. Can be an empty string. + /// Specifies how the userDataText should be encoded. + /// The maximum length of the userDataText parameter depends on the alphabet specified with + /// the dataCodingScheme parameter.Common values for the dataCodingScheme are + /// for GSM default alphabet and + /// for UCS2 alphabet (Unicode). + public SmsSubmitPdu(string userDataText, string destinationAddress, string smscAddress, byte dataCodingScheme) : this() + { + base.DataCodingScheme = dataCodingScheme; + base.UserDataText = userDataText; + this.DestinationAddress = destinationAddress; + base.SmscAddress = smscAddress; + } + + /// + /// Initializes a new instance of the class + /// using the specified PDU string. + /// + /// The PDU string to convert. + /// Specifies if the string contains + /// SMSC data octets at the beginning. + /// Specifies the actual PDU length, that is the length in bytes without + /// the SMSC header. Set to -1 if unknown. + /// + /// This constructor assumes that the string contains an SMS-SUBMIT + /// PDU data stream as specified + /// by GSM 07.05. + /// AbsuluteValidityPeriod and EnhancedValidityPeriod are not + /// supported and will generate a + /// when encountered. + /// + /// + /// The string contains a + /// validity and the validity period format is not relative validity. + /// + public SmsSubmitPdu(string pdu, bool includesSmscData, int actualLength) + { + byte num = 0; + byte[] numArray = null; + int num1; + if (pdu != string.Empty) + { + bool flag = actualLength >= 0; + int num2 = actualLength; + if (!flag || num2 > 0) + { + int num3 = 0; + if (!includesSmscData) + { + base.SmscAddress = string.Empty; + } + else + { + int num4 = num3; + num3 = num4 + 1; + byte num5 = BcdWorker.GetByte(pdu, num4); + if (num5 <= 0) + { + base.SmscAddress = string.Empty; + } + else + { + int num6 = num3; + num3 = num6 + 1; + byte num7 = BcdWorker.GetByte(pdu, num6); + int num8 = num5 - 1; + string bytesString = BcdWorker.GetBytesString(pdu, num3, num8); + num3 = num3 + num8; + string str = BcdWorker.DecodeSemiOctets(bytesString); + if (str.EndsWith("F") || str.EndsWith("f")) + { + str = str.Substring(0, str.Length - 1); + } + base.SetSmscAddress(str, num7); + } + } + int num9 = num3; + num3 = num9 + 1; + this.messageFlags = new SmsSubmitMessageFlags(BcdWorker.GetByte(pdu, num9)); + if (flag) + { + num2--; + if (num2 <= 0) + { + base.ConstructLength = num3 * 2; + return; + } + } + int num10 = num3; + num3 = num10 + 1; + base.MessageReference = BcdWorker.GetByte(pdu, num10); + int num11 = num3; + num3 = num11 + 1; + byte num12 = BcdWorker.GetByte(pdu, num11); + int num13 = num3; + num3 = num13 + 1; + byte num14 = BcdWorker.GetByte(pdu, num13); + if (num12 <= 0) + { + this.DestinationAddress = string.Empty; + } + else + { + if (num12 % 2 != 0) + { + num1 = num12 + 1; + } + else + { + num1 = (int)num12; + } + int num15 = num1 / 2; + string bytesString1 = BcdWorker.GetBytesString(pdu, num3, num15); + num3 = num3 + num15; + string str1 = BcdWorker.DecodeSemiOctets(bytesString1).Substring(0, num12); + this.SetDestinationAddress(str1, num14); + } + int num16 = num3; + num3 = num16 + 1; + base.ProtocolID = BcdWorker.GetByte(pdu, num16); + int num17 = num3; + num3 = num17 + 1; + base.DataCodingScheme = BcdWorker.GetByte(pdu, num17); + ValidityPeriodFormat validityPeriodFormat = this.MessageFlags.ValidityPeriodFormat; + if (validityPeriodFormat == ValidityPeriodFormat.Unspecified) + { + this.ValidityPeriod = null; + } + else if (validityPeriodFormat == ValidityPeriodFormat.Relative) + { + int num18 = num3; + num3 = num18 + 1; + this.ValidityPeriod = new RelativeValidityPeriod(BcdWorker.GetByte(pdu, num18)); + } + else if (validityPeriodFormat == ValidityPeriodFormat.Absolute) + { + throw new NotSupportedException("Absolute validity period format not supported."); + } + else if (validityPeriodFormat == ValidityPeriodFormat.Enhanced) + { + throw new NotSupportedException("Enhanced validity period format not supported."); + } + else + { + throw new NotSupportedException(string.Concat("Validity period format \"", (object)this.MessageFlags.ValidityPeriodFormat.ToString(), "\" not supported.")); + } + PduParts.DecodeUserData(pdu, ref num3, base.DataCodingScheme, out num, out numArray); + base.SetUserData(numArray, num); + base.ConstructLength = num3 * 2; + return; + } + else + { + return; + } + } + else + { + throw new ArgumentException("pdu must not be an empty string."); + } + } + + private string EncodeDestinationAddress(string address, byte addressType) + { + if (address.Length != 0) + { + byte length = (byte)address.Length; + string str = BcdWorker.EncodeSemiOctets(address); + return string.Concat(Calc.IntToHex(length), Calc.IntToHex(addressType), str); + } + else + { + return string.Concat(Calc.IntToHex(0), Calc.IntToHex(addressType)); + } + } + + private string EncodeSmscAddress(string address, byte addressType) + { + if (address.Length != 0) + { + string str = BcdWorker.EncodeSemiOctets(address); + byte length = (byte)(str.Length / 2 + 1); + return string.Concat(Calc.IntToHex(length), Calc.IntToHex(addressType), str); + } + else + { + return Calc.IntToHex(0); + } + } + + /// + /// Returns the relevant timestamp for the message. + /// + /// Always . An does not have + /// a timestamp. + public override SmsTimestamp GetTimestamp() + { + return SmsTimestamp.None; + } + + /// + /// Sets the destination address and type directly without attempting to + /// autodetect the type. + /// + /// The destination address + /// The address type + public void SetDestinationAddress(string address, byte addressType) + { + this.destAddress = address; + this.destTOA = addressType; + } + + /// + /// Converts the value of this instance into a string. + /// + /// If true, excludes the SMSC header. + /// The encoded string. + public override string ToString(bool excludeSmscData) + { + string str = null; + byte num = 0; + string empty = string.Empty; + if (this.MessageFlags.ValidityPeriodFormat != ValidityPeriodFormat.Relative) + { + if (this.MessageFlags.ValidityPeriodFormat != ValidityPeriodFormat.Unspecified) + { + throw new NotSupportedException(string.Concat("The specified validity period format \"", this.MessageFlags.ValidityPeriodFormat.ToString(), "\" is not supported.")); + } + } + else + { + empty = Calc.IntToHex((RelativeValidityPeriod)this.ValidityPeriod); + } + StringBuilder stringBuilder = new StringBuilder(); + if (!excludeSmscData) + { + base.GetSmscAddress(out str, out num); + stringBuilder.Append(this.EncodeSmscAddress(str, num)); + } + stringBuilder.Append(Calc.IntToHex(this.messageFlags)); + stringBuilder.Append(Calc.IntToHex(this.messageReference)); + stringBuilder.Append(this.EncodeDestinationAddress(this.destAddress, this.destTOA)); + stringBuilder.Append(Calc.IntToHex(base.ProtocolID)); + stringBuilder.Append(Calc.IntToHex(base.DataCodingScheme)); + stringBuilder.Append(empty); + stringBuilder.Append(Calc.IntToHex(base.UserDataLength)); + if (base.UserData != null) + { + stringBuilder.Append(Calc.IntToHex(base.UserData)); + } + return stringBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/SmsTimestamp.cs b/PDUConverter/GsmComm.PduConverter/SmsTimestamp.cs new file mode 100644 index 0000000..1371b42 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/SmsTimestamp.cs @@ -0,0 +1,407 @@ +using System; + +/// +/// For TP-SCTS and all other timestamps that look the same. +/// +namespace GsmComm.PduConverter +{ + public struct SmsTimestamp : IComparable + { + private byte year; + + private byte month; + + private byte day; + + private byte hour; + + private byte minute; + + private byte second; + + private int timeZoneOffset; + + /// + /// The value for an invalid or not present timestamp. + /// + public static SmsTimestamp None; + + /// + /// Gets the day. + /// + public byte Day + { + get + { + return this.day; + } + } + + /// + /// Gets the hour. + /// + public byte Hour + { + get + { + return this.hour; + } + } + + /// + /// Gets the minute. + /// + public byte Minute + { + get + { + return this.minute; + } + } + + /// + /// Gets the month. + /// + public byte Month + { + get + { + return this.month; + } + } + + /// + /// Gets the second. + /// + public byte Second + { + get + { + return this.second; + } + } + + /// + /// Gets the time zone offset as an integer. + /// + /// One unit of this offset equals 15 minutes. If you don't need this actual value, + /// consider using instead. + public int TimeZoneOffset + { + get + { + return this.timeZoneOffset; + } + } + + /// + /// Gets the time zone offset as a string useful to append to the sortable date string. + /// + private string TimeZoneOffsetSortableString + { + get + { + string str; + TimeSpan timeZoneOffsetSpan = this.TimeZoneOffsetSpan; + if (timeZoneOffsetSpan.Ticks < (long)0) + { + str = "-"; + } + else + { + str = "+"; + } + string str1 = str; + int num = Math.Abs(timeZoneOffsetSpan.Hours); + string str2 = num.ToString().PadLeft(2, '0'); + int num1 = Math.Abs(timeZoneOffsetSpan.Minutes); + string str3 = num1.ToString().PadLeft(2, '0'); + return string.Concat(str1, str2, ":", str3); + } + } + + /// + /// Gets the time zone offset as a time span. + /// + public TimeSpan TimeZoneOffsetSpan + { + get + { + return TimeSpan.FromMinutes((double)(this.timeZoneOffset * 15)); + } + } + + /// + /// Gets the time zone offset as a string. + /// + public string TimeZoneOffsetString + { + get + { + string str; + TimeSpan timeZoneOffsetSpan = this.TimeZoneOffsetSpan; + if (timeZoneOffsetSpan.Ticks < (long)0) + { + str = "-"; + } + else + { + str = "+"; + } + string str1 = str; + int num = Math.Abs(timeZoneOffsetSpan.Hours); + string str2 = num.ToString().PadLeft(2, '0'); + int num1 = Math.Abs(timeZoneOffsetSpan.Minutes); + string str3 = num1.ToString().PadLeft(2, '0'); + return string.Concat(str1, str2, str3); + } + } + + /// + /// Gets the year. + /// + public byte Year + { + get + { + return this.year; + } + } + + static SmsTimestamp() + { + SmsTimestamp.None = new SmsTimestamp(new DateTime(1900, 1, 1), 0); + } + + /// + /// Decodes the timestamp out of a PDU stream. + /// + /// The string to get the timestamp from. + /// The current position in the string + public SmsTimestamp(string pdu, ref int index) + { + string bytesString = BcdWorker.GetBytesString(pdu, index, 7); + index = index + 7; + string str = BcdWorker.DecodeSemiOctets(bytesString); + this.year = byte.Parse(str.Substring(0, 2)); + this.month = byte.Parse(str.Substring(2, 2)); + this.day = byte.Parse(str.Substring(4, 2)); + this.hour = byte.Parse(str.Substring(6, 2)); + this.minute = byte.Parse(str.Substring(8, 2)); + this.second = byte.Parse(str.Substring(10, 2)); + string str1 = str.Substring(12, 2); + byte num = Calc.HexToInt(str1)[0]; + if ((num & 128) <= 0) + { + this.timeZoneOffset = int.Parse(str1); + return; + } + else + { + num = (byte)(num & 127); + this.timeZoneOffset = -num; + return; + } + } + + /// + /// Creates the timestamp using a DateTime object and an offset. + /// + /// The timestamp to initialize this object with. + /// The time zone offset for the specified timestamp. + public SmsTimestamp(DateTime timestamp, int timeZoneOffset) + { + int year = timestamp.Year; + this.year = (byte)int.Parse(year.ToString().Substring(2, 2)); + this.month = (byte)timestamp.Month; + this.day = (byte)timestamp.Day; + this.hour = (byte)timestamp.Hour; + this.minute = (byte)timestamp.Minute; + this.second = (byte)timestamp.Second; + this.timeZoneOffset = timeZoneOffset; + } + + /// + /// Compares this instance to a specified object and returns an indication of their relative values. + /// + /// An object to compare, or a null reference + /// + /// + /// less than zero = the instance is less than value. + /// zero = the instance is equal to value. + /// greater than zero = The instance is grater than value -or- value is a null reference. + /// + /// + /// value is not an . + public int CompareTo(object value) + { + if (value as SmsTimestamp == null) + { + if (value != null) + { + throw new ArgumentException("value is not an SmsTimestamp."); + } + else + { + return 1; + } + } + else + { + SmsTimestamp smsTimestamp = (SmsTimestamp)value; + int num = this.year.CompareTo(smsTimestamp.Year); + if (num == 0) + { + num = this.month.CompareTo(smsTimestamp.Month); + if (num == 0) + { + num = this.day.CompareTo(smsTimestamp.Day); + if (num == 0) + { + num = this.hour.CompareTo(smsTimestamp.Hour); + if (num == 0) + { + num = this.minute.CompareTo(smsTimestamp.Minute); + if (num == 0) + { + num = this.second.CompareTo(smsTimestamp.Second); + if (num == 0) + { + num = this.timeZoneOffset.CompareTo(smsTimestamp.TimeZoneOffset); + return num; + } + else + { + return num; + } + } + else + { + return num; + } + } + else + { + return num; + } + } + else + { + return num; + } + } + else + { + return num; + } + } + else + { + return num; + } + } + } + + /// + /// Returns the timestamp as a object. + /// + /// The converted date object. + /// + /// The object does not hold the time zone offset, + /// this information will be lost when using this method. Use the + /// TimeZoneOffset-releated properties and methods for working with the + /// offset. If you just need the string representation, consider using the , + /// or methods instead. + /// + /// + /// An saves its years only with two digits by design, + /// uses 4 digits. The following conversion is performed when converting from an + /// to a : If the year is equal or greater than 90, + /// then 1900 is added, otherwise 2000. + /// + /// + /// If there is an error during conversion, the constant + /// is returned, no exception is thrown. + /// + /// + public DateTime ToDateTime() + { + DateTime dateTime; + int num; + try + { + if (this.year >= 90) + { + num = 1900 + this.year; + } + else + { + num = 2000 + this.year; + } + dateTime = new DateTime(num, this.month, this.day, this.hour, this.minute, this.second); + } + catch (ArgumentOutOfRangeException argumentOutOfRangeException) + { + dateTime = DateTime.MinValue; + } + return dateTime; + } + + /// + /// Returns a formatted date using the sortable date/time pattern and the time zone. + /// + /// The formatted date string. + /// The format is independent of the currently active culture and + /// is always "yyyy-MM-ddTHH:mm:sszzz". It is useful for persisting the timestamp value as text. + /// If you just want to display the value, consider using the or + /// methods instead. + public string ToSortableString() + { + DateTime dateTime = this.ToDateTime(); + return string.Concat(dateTime.ToString("s"), this.TimeZoneOffsetSortableString); + } + + /// + /// Returns the formatted date using DateTime of the current culture + the time zone offset. + /// + /// The formatted date string. + /// + /// The returned string is useful for display but not for persistence because is uses + /// the currently active culture to format the string, which can cause problems when trying to parse + /// the persisted string. If you want to persist the timestamp, consider using + /// instead. + /// + public override string ToString() + { + return this.ToString(true); + } + + /// + /// Returns a formatted date string using DateTime of the current culture. + /// + /// Specify true to include the + /// time zone offset in the string, false if it should not be included. + /// The formatted date string. + /// + /// The returned string is useful for display but not for persistence because is uses + /// the currently active culture to format the string, which can cause problems when trying to parse + /// the persisted string. If you want to persist the timestamp, consider using + /// instead. + /// + public string ToString(bool includeTimeZoneOffset) + { + string str; + DateTime dateTime = this.ToDateTime(); + string str1 = dateTime.ToString(); + if (includeTimeZoneOffset) + { + str = string.Concat(" ", this.TimeZoneOffsetString); + } + else + { + str = ""; + } + return string.Concat(str1, str); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/StatusCategory.cs b/PDUConverter/GsmComm.PduConverter/StatusCategory.cs new file mode 100644 index 0000000..f3ff79d --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/StatusCategory.cs @@ -0,0 +1,19 @@ +/// +/// Contains the possible categories of a message status. +/// +namespace GsmComm.PduConverter +{ + public enum StatusCategory + { + /// Short message transaction completed. + Success, + /// Temporary error, SC still trying to transfer SM. + TemporaryErrorWithRetry, + /// Permanent error, SC is not making any more transfer attempts. + PermanentError, + /// Temporary error, SC is not making any more transfer attempts. + TemporaryErrorNoRetry, + /// Status code is out of the defined range. + Reserved + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/TextDataConverter.cs b/PDUConverter/GsmComm.PduConverter/TextDataConverter.cs new file mode 100644 index 0000000..4845c60 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/TextDataConverter.cs @@ -0,0 +1,464 @@ +using System; +using System.Collections; +using System.Text; + +/// +/// Converts between text strings and PDU-compatible formats. +/// +namespace GsmComm.PduConverter +{ + public static class TextDataConverter + { + /// + /// The GSM 7-Bit default alphabet. + /// + private static char[] sevenBitDefault; + + static TextDataConverter() + { + char[] chrArray = new char[] { '@', '£', '$', '¥', 'è', 'é', 'ù', 'ì', 'ò', 'Ç', '\n', 'Ø', 'ø', '\r', 'Å', 'å', 'Δ', '\u005F', 'Φ', 'Γ', 'Λ', 'Ω', 'Π', 'Ψ', 'Σ', 'Θ', 'Ξ', '\u001B', 'Æ', 'æ', 'ß', 'É', ' ', '!', '\"', '#', '¤', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '¡', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ä', 'Ö', 'Ñ', 'Ü', '\u00A7', '¿', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'ä', 'ö', 'ñ', 'ü', 'à' }; + TextDataConverter.sevenBitDefault = chrArray; + } + + private static byte CharToSevenBitExtension(char c) + { + char chr = c; + if (chr > '\u005E') + { + switch (chr) + { + case '{': + { + return 40; + } + case '|': + { + return 64; + } + case '}': + { + return 41; + } + case '~': + { + return 61; + } + default: + { + if (chr == '€') + { + return 101; + } + else + { + break; + } + } + } + } + else + { + if (chr == '\f') + { + return 10; + } + else + { + switch (chr) + { + case '[': + { + return 60; + } + case '\\': + { + return 47; + } + case ']': + { + return 62; + } + case '\u005E': + { + return 20; + } + } + } + } + throw new ArgumentException(string.Concat("The character '", c.ToString(), "' does not exist in the GSM 7-bit default alphabet extension table.")); + } + + /// + /// Expands an array of octets into string of septets. + /// + /// An array of 8-bit encoded data (octets), represented as a string. + /// The converted data as a string. + public static string OctetsToSeptetsStr(byte[] data) + { + string str; + if (data != null) + { + string empty = string.Empty; + string empty1 = string.Empty; + int i = 0; + for (; i < (int)data.Length; i++) + { + string bin = Calc.IntToBin(data[i], 8); + string str1 = bin.Substring(i % 7 + 1, 7 - i % 7); + if (i == 0 || i % 7 == 0) + { + if (i != 0) + { + empty = string.Concat(empty, (char)Calc.BinToInt(empty1)); + } + str = str1; + } + else + { + str = string.Concat(str1, empty1); + } + empty1 = bin.Substring(0, i % 7 + 1); + empty = string.Concat(empty, (char)Calc.BinToInt(str)); + } + if (i != 0 && i % 7 == 0 && empty1 != "0000000") + { + empty = string.Concat(empty, (char)Calc.BinToInt(empty1)); + } + return empty; + } + else + { + return string.Empty; + } + } + + /// + /// Compacts a string of septets into octets. + /// + /// 7-bit encoded data (septets), represented as a string. + /// + /// When only 7 of 8 available bits of a character are used, 1 bit is + /// wasted per character. This method compacts a string of characters + /// which consist solely of such 7-bit characters. + /// Effectively, every 8 bytes of the original string are packed into + /// 7 bytes in the resulting string. + /// + public static string SeptetsToOctetsHex(string data) + { + string empty = string.Empty; + string str = string.Empty; + for (int i = 0; i < data.Length; i++) + { + string bin = Calc.IntToBin((byte)data[i], 7); + if (i != 0 && i % 8 != 0) + { + string str1 = bin.Substring(7 - i % 8); + string str2 = string.Concat(str1, str); + empty = string.Concat(empty, Calc.IntToHex(Calc.BinToInt(str2))); + } + str = bin.Substring(0, 7 - i % 8); + if (i == data.Length - 1 && str != string.Empty) + { + empty = string.Concat(empty, Calc.IntToHex(Calc.BinToInt(str))); + } + } + return empty; + } + + /// + /// Compacts a string of septets into octets. + /// + /// 7-bit encoded data (septets), represented as a string. + /// + /// When only 7 of 8 available bits of a character are used, 1 bit is + /// wasted per character. This method compacts a string of characters + /// which consist solely of such 7-bit characters. + /// Effectively, every 8 bytes of the original string are packed into + /// 7 bytes in the resulting string. + /// + public static byte[] SeptetsToOctetsInt(string data) + { + ArrayList arrayLists = new ArrayList(); + string empty = string.Empty; + for (int i = 0; i < data.Length; i++) + { + string bin = Calc.IntToBin((byte)data[i], 7); + if (i != 0 && i % 8 != 0) + { + string str = bin.Substring(7 - i % 8); + string str1 = string.Concat(str, empty); + arrayLists.Add(Calc.BinToInt(str1)); + } + empty = bin.Substring(0, 7 - i % 8); + if (i == data.Length - 1 && empty != string.Empty) + { + arrayLists.Add(Calc.BinToInt(empty)); + } + } + byte[] numArray = new byte[arrayLists.Count]; + arrayLists.CopyTo(numArray); + return numArray; + } + + private static char SevenBitExtensionToChar(byte b) + { + byte num = b; + if (num > 41) + { + if (num == 47) + { + return '\\'; + } + else + { + if (num == 60) + { + return '['; + } + else if (num == 61) + { + return '~'; + } + else if (num == 62) + { + return ']'; + } + else if (num == 63) + { + throw new ArgumentException(string.Concat("The value ", b.ToString(), " is not part of the 7-bit default alphabet extension table.")); + } + else if (num == 64) + { + return '|'; + } + if (num == 101) + { + return '€'; + } + } + } + else + { + if (num == 10) + { + return '\f'; + } + else + { + if (num == 20) + { + return '\u005E'; + } + else + { + switch (num) + { + case 40: + { + return '{'; + } + case 41: + { + return '}'; + } + } + } + } + } + throw new ArgumentException(string.Concat("The value ", b.ToString(), " is not part of the 7-bit default alphabet extension table.")); + } + + /// + /// Converts a string consisting of characters from the GSM + /// "7-bit default alphabet" into a string of corresponding characters + /// of the ISO-8859-1 character set. + /// + /// The string to convert. + /// The converted string. + /// + /// Note that the converted string does not necessarily have the same + /// length as the original one because some characters may be escaped. + /// This method throws an exception if an invalid character + /// is encountered. + /// + public static string SevenBitToString(string s) + { + return TextDataConverter.SevenBitToString(s, true); + } + + /// + /// Converts a string consisting of characters from the GSM + /// "7-bit default alphabet" into a string of corresponding characters + /// of the ISO-8859-1 character set. + /// + /// The string to convert. + /// If true, throws an exception if + /// an invalid character is encountered. If false, replaces every + /// unknown character with a question mark (?). + /// The converted string. + /// + /// Note that the converted string does not necessarily have the same + /// length as the original one because some characters may be escaped. + /// This method throws an exception if an invalid character + /// is encountered. + /// + public static string SevenBitToString(string s, bool throwExceptions) + { + string empty = string.Empty; + bool flag = false; + for (int i = 0; i < s.Length; i++) + { + byte num = (byte)s[i]; + if (!flag) + { + if (num == 27) + { + flag = true; + } + else + { + if (num > TextDataConverter.sevenBitDefault.GetUpperBound(0)) + { + if (!throwExceptions) + { + empty = string.Concat(empty, (char)63); + } + else + { + object[] str = new object[5]; + str[0] = "Character '"; + str[1] = (char)num; + str[2] = "' at position "; + int num1 = i + 1; + str[3] = num1.ToString(); + str[4] = " is not part of the GSM 7-bit default alphabet."; + throw new ArgumentException(string.Concat(str)); + } + } + else + { + empty = string.Concat(empty, TextDataConverter.sevenBitDefault[num]); + } + } + } + else + { + try + { + empty = string.Concat(empty, TextDataConverter.SevenBitExtensionToChar(num)); + } + catch (Exception exception) + { + if (!throwExceptions) + { + empty = string.Concat(empty, (char)63); + } + else + { + throw; + } + } + flag = false; + } + } + return empty; + } + + /// + /// Converts a string consisting of characters from the ISO-8859-1 + /// character set into a string of corresponding characters of the + /// "GSM 7-bit default alphabet" character set. + /// + /// The string to convert. + /// If true, throws an exception if + /// an invalid character is encountered. If false, replaces every + /// unknown character with a question mark (?). + /// Will be set to true if invalid characters + /// were replaced. throwExceptions must be false for this to work. + /// The converted string. + /// + /// Note that the converted string does not need to have the same + /// length as the original one because some characters may be escaped. + /// + /// throwExceptions is true and invalid character is encountered in the string. + public static string StringTo7Bit(string s, bool throwExceptions, out bool charsReplaced) + { + StringBuilder stringBuilder = new StringBuilder(); + charsReplaced = false; + bool flag = false; + int i = 0; + int num = 0; + for (i = 0; i < s.Length; i++) + { + flag = false; + char chr = s.Substring(i, 1)[0]; + num = 0; + while (num <= TextDataConverter.sevenBitDefault.GetUpperBound(0)) + { + if (TextDataConverter.sevenBitDefault[num] != chr) + { + num++; + } + else + { + stringBuilder.Append((ushort)num); + flag = true; + break; + } + } + if (!flag) + { + try + { + byte sevenBitExtension = TextDataConverter.CharToSevenBitExtension(chr); + stringBuilder.Append('\u001B'); + stringBuilder.Append(sevenBitExtension); + flag = true; + } + catch (Exception exception) + { + } + } + if (!flag) + { + if (!throwExceptions) + { + stringBuilder.Append('?'); + charsReplaced = true; + } + else + { + object[] str = new object[5]; + str[0] = "The character '"; + str[1] = chr; + str[2] = "' at position "; + int num1 = i + 1; + str[3] = num1.ToString(); + str[4] = " does not exist in the GSM 7-bit default alphabet."; + throw new ArgumentException(string.Concat(str)); + } + } + } + return stringBuilder.ToString(); + } + + /// + /// Converts a string consisting of characters from the ISO-8859-1 + /// character set into a string of corresponding characters of the + /// "GSM 7-bit default alphabet" character set. + /// + /// The string to convert. + /// The converted string. + /// + /// Throws an exception when an invalid character is encountered in the string. + /// Note that the converted string does not need to have the same + /// length as the original one because some characters may be escaped. + /// + /// An invalid character is encountered in the string. + public static string StringTo7Bit(string s) + { + bool flag = false; + return TextDataConverter.StringTo7Bit(s, true, out flag); + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/ValidityPeriod.cs b/PDUConverter/GsmComm.PduConverter/ValidityPeriod.cs new file mode 100644 index 0000000..0dc23a8 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/ValidityPeriod.cs @@ -0,0 +1,17 @@ +/// +/// Implements the base for a validity Period. +/// +/// +/// Note to inheritors: Override the ToString method in derived classes to be able to +/// display the validity immediately as a string. +/// +/// +namespace GsmComm.PduConverter +{ + public abstract class ValidityPeriod + { + protected ValidityPeriod() + { + } + } +} \ No newline at end of file diff --git a/PDUConverter/GsmComm.PduConverter/ValidityPeriodFormat.cs b/PDUConverter/GsmComm.PduConverter/ValidityPeriodFormat.cs new file mode 100644 index 0000000..24df977 --- /dev/null +++ b/PDUConverter/GsmComm.PduConverter/ValidityPeriodFormat.cs @@ -0,0 +1,17 @@ +/// +/// Specifies how the validity period is formatted. +/// +namespace GsmComm.PduConverter +{ + public enum ValidityPeriodFormat + { + /// No validity is specified, the TP-VP block will not be specified. + Unspecified, + /// A relative validity is specified in the TP-VP block. + Relative, + /// An absolute validity is specified in the TP-VP block. + Absolute, + /// An enhanced validity is specified in the TP-VP block. + Enhanced + } +} \ No newline at end of file diff --git a/PDUConverter/PDUConverter.csproj b/PDUConverter/PDUConverter.csproj new file mode 100644 index 0000000..41656b0 --- /dev/null +++ b/PDUConverter/PDUConverter.csproj @@ -0,0 +1,227 @@ + + + + {5E657EFE-0A30-466D-B025-FF177E23A727} + 2 + Debug + AnyCPU + PDUConverter + Library + v4.0 + + + bin\Debug\ + true + DEBUG;TRACE + false + 4 + full + prompt + AnyCPU + + + bin\Release\ + false + TRACE + true + 4 + pdbonly + prompt + AnyCPU + + + + + + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + false + false + + + + \ No newline at end of file diff --git a/PDUConverter/Properties/AssemblyInfo.cs b/PDUConverter/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8ff53f3 --- /dev/null +++ b/PDUConverter/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("Stefan Mayr")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCopyright("Copyright © 2004-2011 Stefan Mayr")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyFileVersion("1.21.0.0")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyTitle("SMS PDU Converter")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("1.21.0.0")] +[assembly: CompilationRelaxations(8)] +[assembly: ComVisible(false)] +[assembly: Guid("36c38b1b-8e9a-4828-865e-eda5d402248d")] +[assembly: RuntimeCompatibility(WrapNonExceptionThrows=true)]