/**
* @file pdulib.h
* @author David Henry (mgadriver@gmail.com)
* @brief Encode/Decode PDU data
* @version 0.5.5
* @date 2022-07-11
*
* @copyright Copyright (c) 2021
*
* Release History
* 0.1.1 Original release
* 0.5.1 Fixed bug where Greek characters in a GSM7 message were ignored
* Fixed bad behaviour where static lookup tables were duplicated (moved to pdulib.cpp)
* Default GSM7 now fully supported
* 0.5.4 Add getConcatInfo method to support concatenated messages
* 0.5.5 Place PROGMEM option here (if desired)
* Add buffer size option to PDU constructor
* 0.5.7 Bug fixes Issues 27,28,29,30,33
* Enhancement Issue 32
*/
//#define PM // uncomment to implement Arduino PROGMEM feature
#ifdef PDU_LIB_INCLUDE
#else
#define PDU_LIB_INCLUDE
#define BITMASK_7BITS 0x7F
// DCS bit masks
#define DCS_COMPRESSED (5<<1)
#define DCS_CLASS_MEANING (4<<1)
#define DCS_ALPHABET_MASK (3<<2)
#define DCS_ALPHABET_OFFSET 2
#define DCS_7BIT_ALPHABET_MASK 0B0000 //(0<<2)
#define DCS_8BIT_ALPHABET_MASK 0B0100 //1<<2)
#define DCS_16BIT_ALPHABET_MASK 0B1000 //(2<<2)
#define DCS_CLASS_MASK 3
#define DCS_IMMEDIATE_DISPLAY 3
#define DCS_ME_SPECIFIC_MASK 1
#define DCS_SIM_SPECIFIC_MASK 2
#define DCS_TE_SPECIFIC_MASK 3
// PDU bits
#define PDU_VALIDITY_MASK_OFFSET 3
#define PDU_VALIDITY_NOT_PRESENT 0
#define PDU_VALIDITY_PRESENT_RELATIVE 2
#define PDU_VALIDITY_PRESENT_ENHANCED 1
#define PDU_VALIDITY_PRESENT_ABSOLUTE 3
#define PDU_UDHI 6
// PDU MTI values
#define PDU_SMS_DELIVER 0
#define PDU_SMS_SUBMIT 1
// type of address
#define INTERNATIONAL_NUMBER 0x91
#define NATIONAL_NUMBER 0xA1 // outgoing only , incoming has 0x81
#define EXT_MASK 0x80 // bit 7
#define TON_MASK 0x70 // bits 4-6
#define TON_OFFSET 4
#define NPI_MASK 0x0f // bits 0-3
#define MAX_SMS_LENGTH_7BIT 160 // GSM 3.4
#define MAX_NUMBER_OCTETS 140
#define MAX_NUMBER_LENGTH 20 // gets packed into BCD or packed 7 bit
#define UTF8_BUFFSIZE 100 // tailor to what you need
//SCA (12) + type + mref + address(12) + pid + dcs + length + data(140) -- no valtime
#define PDU_BINARY_MAX_LENGTH 170
/* Define Non-Printable Characters as a question mark */
#define NPC7 63
#define NPC8 '?'
#define EURO_UCS 0x20AC
//#define SURROGATE_PAIR 0xD800
enum eDCS { ALPHABET_7BIT, ALPHABET_8BIT, ALPHABET_16BIT };
enum eAddressType {INTERNATIONAL_NUMERIC,NATIONAL_NUMERIC,ALPHABETIC};
enum eLengthType {OCTETS,NIBBLES}; // SCA is in octets, sender/recipient nibbles
/**
* @brief PDU class, provides methods to decode a PDU message or encode a new one
* @param Optional Size of encode / decode work area
*
*/
class PDU
{
public:
PDU(int = UTF8_BUFFSIZE);
~PDU();
/**
* @brief Encode a PDU block for sending to an GSM modem
*
* @param recipient Phone number, must be numeric, no whitespace. International numbers prefixed by '+'
* @param message The message in UTF-8 format
* @return int The length of the message, need for the GSM command AT+CSMG=nn
*/
int encodePDU(const char *recipient,const char *message,unsigned short csms=0, unsigned char numparts=0, unsigned char partnumber=0);
/**
* @brief Get the address of the PDU message created by encodePDU
*
* @return const char* The pointer to the message. It already contained the CTRL/Z delimiter byte.
*/
const char *getSMS();
/**
* @brief Before encoding a PDU, you must supply the SCA phone number.
* Typically this can be retrieved from a GSM modem with the AT+CSCA? command.
*
* @param number The number as retrieved from the AT+CSCA? command
*
*/
void setSCAnumber(const char *number);
/**
* @brief Decode a PDU, typically received from a GSM modem when in PDU mode.
* After a successful decoding you can retrieve the components parts, described below.
*
* @param pdu A pointer to the PDU
* @return true If the decoding succeeded.
* @return false If the decoding did not succeed.
*/
bool decodePDU(const char *pdu);
/**
* @brief Get the SCA number from a decoded PDU
*
* @return char* Pointer to the number
*/
const char *getSCAnumber();
/**
* @brief Get the senders phone number from a decoded PDU
*
* @return const char* Pointer to the number
*/
const char *getSender();
/**
* @brief Get the Timestamp from a decoded PDU
*
* @return const char* The tomestamp formatted as YYYMMDDHHMMSS
*/
const char *getTimeStamp();
/**
* @brief Get the text from a decoded PDU.
*
* @return const unsigned char* The message in UTF-8 format.
*/
const char *getText();
/**
* @brief Create a UTF16 string from a codepoint > 0xffff
*
* @param codepoint Examples https://unicode.org/emoji/charts/full-emoji-list.html
* @param target Where to place the string
*/
void buildUtf16(unsigned long codepoint, char *target); // build a string from a codepoint
/**
* @brief Create a UTF string from a codepoint. Handles practically anything
*
* @param codepoint Examples https://en.wikipedia.org/wiki/List_of_Unicode_characters
* @param target Where to place the string
*/
int buildUtf(unsigned long codepoint, char *target); // build a string from a codepoint
/**
* @brief Convert a UTF8 string into an array of UCS2 octets
*
* @param utf8 The UTF8 string
* @param ucs2 Pointer to UCS2 array
* @return int Return the number of octets, -1 if the message is greater than the maximum allowed
*/
int utf8_to_ucs2(const char *utf8, char *ucs2); // translate an utf8 zero terminated string
/**
* @brief Examine an array of UCS2 to determine if this is a default GSM7 character
*
* @param pucs A UCS2 array e.g. as created by the utf8_to_ucs2_single method
* @return true or false
*/
bool isGSM7(unsigned short *pucs); // UCS may be a surrogate pair
/**
* @brief Examine a UTF8 encoded Unicode character
*
* @return The number of bytes (octets) occupied by the character
*/
int utf8Length(const char *);
/**
* @brief Encode a single UTF8 encoded Unicode character into UCS2 format.
*
* @param utf8 Pointer to an UTF8 stream
* @param pucs2 An array of unsigned short to receive the UCS2 data. Surrogate pair characters need an array size of 2.
* @return int The length (in bytes) of the converted character. This will be 4 for a Surrogate Pair (e.g an Emoji) else 2.
*/
int utf8_to_ucs2_single(const char *utf8, unsigned short *pucs2); // translate to a single ucs2
/**
* @brief Return info on concatenated message
* @return Pointer to an array of 3 int. 1st int, CMCS number, 2nd byte part number, 3rd number of parts, zero means this is not a concatenated message
*
*/
int * getConcatInfo();
/**
* @brief Return an indicator if the incoming SMS was fully decoded or not
* @return True if the incoming message overflowed the work buffer, False if not
*
*/
bool getOverflow();
/**
* @brief Error codes from Encode
*
*/
enum eEncodeError {OBSOLETE_ERROR = -1,UCS2_TOO_LONG = -2, GSM7_TOO_LONG = -3, MULTIPART_NUMBERS = -4,ADDRESS_FORMAT=-5,WORK_BUFFER_TOO_SMALL=-6,ALPHABET_8BIT_NOT_SUPPORTED = -7};
private:
bool overFlow;
int scalength;
char scabuffin[MAX_NUMBER_LENGTH]; // for incominging SCA
char scabuffout[MAX_NUMBER_LENGTH]; // for outgoing SCA
int addressLength; // in octets
char addressBuff[MAX_NUMBER_LENGTH]; // ample for any phone number
// int utf8length;
int generalWorkBuffLength; // static size of encode/decode work area
char *generalWorkBuff; // allocate dynamically
int tslength;
char tsbuff[20]; // big enough for timestamp
// char scanumber[MAX_NUMBER_LENGTH]; // for outgoing SMS
// following for buiulding an SMS-SUBMIT message - Binary not ASCII
// int addressType; // GSM 3.04 for building address part of SMS SUBMIT
int smsOffset;
// char smsSubmit[PDU_BINARY_MAX_LENGTH*2]; // big enough for largest message
int concatInfo[3];
unsigned char udhbuffer[8];
bool phoneNumberLegal(const char *); // check SCA/recipient number legal format
// helper methods
void stringToBCD(const char *number, char *pdu);
void BCDtoString(char *number, const char *pdu,int length);
void digitSwap(const char *number, char *pdu);
int utf8_to_packed7bit(const char *utf8, char *pdu, int *septets, int UDHsize, int availableSpace);
int pduGsm7_to_unicode(const char *pdu, int pdulength, char *ascii,char firstchar);
int convert_utf8_to_gsm7bit(const char *ascii, char *a7bit, int udhsize, int availableSpace);
int convert_7bit_to_unicode(unsigned char *a7bit, int length, char *ascii);
unsigned char gethex(const char *pc);
void putHex(unsigned char b, char *target);
// return number of ucs2 octets in output array
int pdu_to_ucs2(const char *pdu, int length, unsigned short *ucs2);
// callers responsibilty that utf8 array is big enough
int ucs2_to_utf8(unsigned short ucs2, char *utf8);
// callers responsibilty that ucs2 array is big enough
// int utf8_to_ucs2_single(const char *utf8, unsigned short *ucs2); // translate to a single ucs2
// int utf8_to_ucs2(const char *utf8, char *ucs2); // translate an utf8 zero terminated string
// get length of next utf8
// int utf8Length(const char *);
int decodeAddress(const char *,char *, eLengthType); // pdu to readable starts with length octet
bool setAddress(const char *,/*eAddressType,*/eLengthType, char *);
// bool isGSM7(unsigned short *pucs); // UCS may be a surrogate pair
// // Get SCA number for outgoing SMS
// const char *getMySCAnumber();
int buildUDH(unsigned short,unsigned,unsigned);
};
extern const
#ifdef PM
PROGMEM
#endif
short lookup_ascii8to7[];
extern const
#ifdef PM
PROGMEM
#endif
unsigned char lookup_gsm7toUnicode[];
extern const
#ifdef PM
PROGMEM
#endif
unsigned short lookup_Greek7ToUnicode[];
#endif
#define GREEK_UCS_MINIMUM 0x393
extern const
#ifdef PM
PROGMEM
#endif
unsigned short lookup_UnicodeToGreek7[];
struct sRange {
unsigned short min,max;
};
extern const
#ifdef PM
PROGMEM
#endif
struct sRange gsm7_legal[];