Merge remote-tracking branch 'cros/upstream' into 'cros/master'
diff --git a/.gitignore b/.gitignore
index d6580be..0c379af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,7 +52,8 @@
 src/tests/test-charsets
 src/tests/test-qcdm-serial-port
 src/tests/test-at-serial-port
-src/tests/test-sms-part
+src/tests/test-sms-part-3gpp
+src/tests/test-sms-part-cdma
 
 cli/mmcli
 
@@ -86,6 +87,7 @@
 po/insert-header.sin
 po/quot.sed
 po/remove-potcdate.sin
+po/*.gmo
 
 docs/reference/api/version.xml
 docs/reference/api/ModemManager.args
diff --git a/cli/mmcli-sms.c b/cli/mmcli-sms.c
index 4124f66..c49c8e8 100644
--- a/cli/mmcli-sms.c
+++ b/cli/mmcli-sms.c
@@ -180,42 +180,56 @@
 
     g_print ("  -----------------------------------\n"
              "  Properties |            PDU type: '%s'\n"
-             "             |               state: '%s'\n"
-             "             |                smsc: '%s'\n",
+             "             |               state: '%s'\n",
              mm_sms_pdu_type_get_string (pdu_type),
-             mm_sms_state_get_string (mm_sms_get_state (sms)),
-             VALIDATE (mm_sms_get_smsc (sms)));
+             mm_sms_state_get_string (mm_sms_get_state (sms)));
 
     if (mm_sms_get_validity_type (sms) == MM_SMS_VALIDITY_TYPE_RELATIVE)
         g_print ("             | validity (relative): '%u'\n",
                  mm_sms_get_validity_relative (sms));
 
-    g_print ("             |               class: '%d'\n"
-             "             |             storage: '%s'\n",
-             mm_sms_get_class (sms),
+    g_print ("             |             storage: '%s'\n",
              mm_sms_storage_get_string (mm_sms_get_storage (sms)));
 
+    /* Print properties which are set, regardless of the pdu type */
+
+    if (mm_sms_get_smsc (sms))
+        g_print ("             |                smsc: '%s'\n",
+                 mm_sms_get_smsc (sms));
+
+    if (mm_sms_get_class (sms) >= 0)
+        g_print ("             |               class: '%d'\n",
+                 mm_sms_get_class (sms));
+
+    if (mm_sms_get_teleservice_id (sms) != MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN)
+        g_print ("             |      teleservice id: '%s'\n",
+                 mm_sms_cdma_teleservice_id_get_string (mm_sms_get_teleservice_id (sms)));
+
+    if (mm_sms_get_service_category (sms) != MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN)
+        g_print ("             |    service category: '%s'\n",
+                 mm_sms_cdma_service_category_get_string (mm_sms_get_service_category (sms)));
+
+    /* Delivery report request just in 3GPP submit PDUs */
     if (pdu_type == MM_SMS_PDU_TYPE_SUBMIT)
         g_print ("             |     delivery report: '%s'\n",
                  mm_sms_get_delivery_report_request (sms) ? "requested" : "not requested");
 
-    if (pdu_type == MM_SMS_PDU_TYPE_STATUS_REPORT ||
-        pdu_type == MM_SMS_PDU_TYPE_SUBMIT)
+    if (mm_sms_get_message_reference (sms) != 0)
         g_print ("             |   message reference: '%u'\n",
                  mm_sms_get_message_reference (sms));
 
-    if (pdu_type == MM_SMS_PDU_TYPE_STATUS_REPORT ||
-        pdu_type == MM_SMS_PDU_TYPE_DELIVER)
+    if (mm_sms_get_timestamp (sms))
         g_print ("             |           timestamp: '%s'\n",
-                 VALIDATE (mm_sms_get_timestamp (sms)));
+                 mm_sms_get_timestamp (sms));
 
-    if (pdu_type == MM_SMS_PDU_TYPE_STATUS_REPORT) {
+    if (mm_sms_get_delivery_state (sms) != MM_SMS_DELIVERY_STATE_UNKNOWN)
         g_print ("             |      delivery state: '%s' (0x%X)\n",
                  VALIDATE (mm_sms_delivery_state_get_string_extended (mm_sms_get_delivery_state (sms))),
                  mm_sms_get_delivery_state (sms));
+
+    if (mm_sms_get_discharge_timestamp (sms))
         g_print ("             | discharge timestamp: '%s'\n",
-                 VALIDATE (mm_sms_get_discharge_timestamp (sms)));
-    }
+                 mm_sms_get_discharge_timestamp (sms));
 }
 
 static void
diff --git a/docs/reference/api/ModemManager-sections.txt b/docs/reference/api/ModemManager-sections.txt
index a9fa9cd..fd551a2 100644
--- a/docs/reference/api/ModemManager-sections.txt
+++ b/docs/reference/api/ModemManager-sections.txt
@@ -43,6 +43,8 @@
 MMSmsDeliveryState
 MMSmsStorage
 MMSmsValidityType
+MMSmsCdmaTeleserviceId
+MMSmsCdmaServiceCategory
 </SECTION>
 
 <SECTION>
diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt
index 302f42c..825f98d 100644
--- a/docs/reference/libmm-glib/libmm-glib-sections.txt
+++ b/docs/reference/libmm-glib/libmm-glib-sections.txt
@@ -1050,6 +1050,8 @@
 mm_sms_dup_smsc
 mm_sms_get_message_reference
 mm_sms_get_class
+mm_sms_get_teleservice_id
+mm_sms_get_service_category
 mm_sms_get_validity_type
 mm_sms_get_validity_relative
 mm_sms_get_timestamp
@@ -1101,6 +1103,10 @@
 mm_sms_properties_set_class
 mm_sms_properties_get_delivery_report_request
 mm_sms_properties_set_delivery_report_request
+mm_sms_properties_get_teleservice_id
+mm_sms_properties_set_teleservice_id
+mm_sms_properties_get_service_category
+mm_sms_properties_set_service_category
 <SUBSECTION Private>
 mm_sms_properties_get_dictionary
 mm_sms_properties_dup
@@ -1149,6 +1155,8 @@
 mm_sms_delivery_state_get_string
 mm_sms_storage_get_string
 mm_sms_validity_type_get_string
+mm_sms_cdma_teleservice_id_get_string
+mm_sms_cdma_service_category_get_string
 mm_firmware_image_type_get_string
 mm_oma_feature_build_string_from_mask
 mm_oma_session_type_get_string
@@ -1169,6 +1177,8 @@
 mm_sms_delivery_state_build_string_from_mask
 mm_sms_storage_build_string_from_mask
 mm_sms_validity_type_build_string_from_mask
+mm_sms_cdma_teleservice_id_build_string_from_mask
+mm_sms_cdma_service_category_build_string_from_mask
 mm_modem_location_source_get_string
 mm_modem_contacts_storage_build_string_from_mask
 mm_bearer_ip_family_build_string_from_mask
@@ -1218,6 +1228,8 @@
 MM_TYPE_SMS_STATE
 MM_TYPE_SMS_STORAGE
 MM_TYPE_SMS_VALIDITY_TYPE
+MM_TYPE_SMS_CDMA_TELESERVICE_ID
+MM_TYPE_SMS_CDMA_SERVICE_CATEGORY
 MM_TYPE_OMA_FEATURE
 MM_TYPE_OMA_SESSION_STATE
 MM_TYPE_OMA_SESSION_STATE_FAILED_REASON
@@ -1251,6 +1263,8 @@
 mm_sms_state_get_type
 mm_sms_storage_get_type
 mm_sms_validity_type_get_type
+mm_sms_cdma_teleservice_id_get_type
+mm_sms_cdma_service_category_get_type
 mm_oma_feature_get_type
 mm_oma_session_state_failed_reason_get_type
 mm_oma_session_state_get_type
@@ -2608,6 +2622,8 @@
 mm_gdbus_sms_get_validity
 mm_gdbus_sms_dup_validity
 mm_gdbus_sms_get_class
+mm_gdbus_sms_get_teleservice_id
+mm_gdbus_sms_get_service_category
 mm_gdbus_sms_get_timestamp
 mm_gdbus_sms_dup_timestamp
 mm_gdbus_sms_get_discharge_timestamp
@@ -2623,6 +2639,8 @@
 mm_gdbus_sms_call_store_sync
 <SUBSECTION Private>
 mm_gdbus_sms_set_class
+mm_gdbus_sms_set_teleservice_id
+mm_gdbus_sms_set_service_category
 mm_gdbus_sms_set_data
 mm_gdbus_sms_set_delivery_report_request
 mm_gdbus_sms_set_delivery_state
diff --git a/include/ModemManager-enums.h b/include/ModemManager-enums.h
index 95d359f..fd3e982 100644
--- a/include/ModemManager-enums.h
+++ b/include/ModemManager-enums.h
@@ -419,17 +419,29 @@
 /**
  * MMSmsPduType:
  * @MM_SMS_PDU_TYPE_UNKNOWN: Unknown type.
- * @MM_SMS_PDU_TYPE_DELIVER: SMS has been received from the SMSC.
- * @MM_SMS_PDU_TYPE_SUBMIT: SMS is sent, or to be sent to the SMSC.
- * @MM_SMS_PDU_TYPE_STATUS_REPORT: SMS is a status report received from the SMSC.
+ * @MM_SMS_PDU_TYPE_DELIVER: 3GPP Mobile-Terminated (MT) message.
+ * @MM_SMS_PDU_TYPE_SUBMIT: 3GPP Mobile-Originated (MO) message.
+ * @MM_SMS_PDU_TYPE_STATUS_REPORT: 3GPP status report (MT).
+ * @MM_SMS_PDU_TYPE_CDMA_DELIVER: 3GPP2 Mobile-Terminated (MT) message.
+ * @MM_SMS_PDU_TYPE_CDMA_SUBMIT: 3GPP2 Mobile-Originated (MO) message.
+ * @MM_SMS_PDU_TYPE_CDMA_CANCELLATION: 3GPP2 Cancellation (MO) message.
+ * @MM_SMS_PDU_TYPE_CDMA_DELIVERY_ACKNOWLEDGEMENT: 3GPP2 Delivery Acknowledgement (MT) message.
+ * @MM_SMS_PDU_TYPE_CDMA_USER_ACKNOWLEDGEMENT: 3GPP2 User Acknowledgement (MT or MO) message.
+ * @MM_SMS_PDU_TYPE_CDMA_READ_ACKNOWLEDGEMENT: 3GPP2 Read Acknowledgement (MT or MO) message.
  *
  * Type of PDUs used in the SMS.
  */
 typedef enum { /*< underscore_name=mm_sms_pdu_type >*/
-    MM_SMS_PDU_TYPE_UNKNOWN       = 0,
+    MM_SMS_PDU_TYPE_UNKNOWN = 0,
     MM_SMS_PDU_TYPE_DELIVER       = 1,
     MM_SMS_PDU_TYPE_SUBMIT        = 2,
-    MM_SMS_PDU_TYPE_STATUS_REPORT = 3
+    MM_SMS_PDU_TYPE_STATUS_REPORT = 3,
+    MM_SMS_PDU_TYPE_CDMA_DELIVER                  = 32,
+    MM_SMS_PDU_TYPE_CDMA_SUBMIT                   = 33,
+    MM_SMS_PDU_TYPE_CDMA_CANCELLATION             = 34,
+    MM_SMS_PDU_TYPE_CDMA_DELIVERY_ACKNOWLEDGEMENT = 35,
+    MM_SMS_PDU_TYPE_CDMA_USER_ACKNOWLEDGEMENT     = 36,
+    MM_SMS_PDU_TYPE_CDMA_READ_ACKNOWLEDGEMENT     = 37,
 } MMSmsPduType;
 
 /**
@@ -480,12 +492,71 @@
  * @MM_SMS_DELIVERY_STATE_TEMPORARY_FATAL_ERROR_QOS_NOT_AVAILABLE: Permanent error, QoS not available.
  * @MM_SMS_DELIVERY_STATE_TEMPORARY_FATAL_ERROR_IN_SME: Permanent error in SME.
  * @MM_SMS_DELIVERY_STATE_UNKNOWN: Unknown state.
+ * @MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_ADDRESS_VACANT: Permanent error in network, address vacant.
+ * @MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_ADDRESS_TRANSLATION_FAILURE: Permanent error in network, address translation failure.
+ * @MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_NETWORK_RESOURCE_OUTAGE: Permanent error in network, network resource outage.
+ * @MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_NETWORK_FAILURE: Permanent error in network, network failure.
+ * @MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_INVALID_TELESERVICE_ID: Permanent error in network, invalid teleservice id.
+ * @MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_OTHER: Permanent error, other network problem.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_NO_PAGE_RESPONSE: Permanent error in terminal, no page response.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_BUSY: Permanent error in terminal, destination busy.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_NO_ACKNOWLEDGMENT: Permanent error in terminal, no acknowledgement.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_RESOURCE_SHORTAGE: Permanent error in terminal, destination resource shortage.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED: Permanent error in terminal, SMS delivery postponed.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_OUT_OF_SERVICE: Permanent error in terminal, destination out of service.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_NO_LONGER_AT_THIS_ADDRESS: Permanent error in terminal, destination no longer at this address.
+ * @MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_OTHER: Permanent error, other terminal problem.
+ * @MM_SMS_DELIVERY_STATE_RADIO_INTERFACE_PROBLEM_RESOURCE_SHORTAGE: Permanent error in radio interface, resource shortage.
+ * @MM_SMS_DELIVERY_STATE_RADIO_INTERFACE_PROBLEM_INCOMPATIBILITY: Permanent error in radio interface, problem incompatibility.
+ * @MM_SMS_DELIVERY_STATE_RADIO_INTERFACE_PROBLEM_OTHER: Permanent error, other radio interface problem.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_ENCODING: Permanent error, encoding.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SMS_ORIGINATION_DENIED: Permanent error, SMS origination denied.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SMS_TERMINATION_DENIED: Permanent error, SMS termination denied.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SUPPLEMENTARY_SERVICE_NOT_SUPPORTED: Permanent error, supplementary service not supported.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SMS_NOT_SUPPORTED: Permanent error, SMS not supported.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_MISSING_EXPECTED_PARAMETER: Permanent error, missing expected parameter.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_MISSING_MANDATORY_PARAMETER: Permanent error, missing mandatory parameter.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_UNRECOGNIZED_PARAMETER_VALUE: Permanent error, unrecognized parameter value.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_UNEXPECTED_PARAMETER_VALUE: Permanent error, unexpected parameter value.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_USER_DATA_SIZE_ERROR: Permanent error, user data size error.
+ * @MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_OTHER: Permanent error, other general problem.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_ADDRESS_VACANT: Temporary error in network, address vacant.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_ADDRESS_TRANSLATION_FAILURE: Temporary error in network, address translation failure.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_NETWORK_RESOURCE_OUTAGE: Temporary error in network, network resource outage.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_NETWORK_FAILURE: Temporary error in network, network failure.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_INVALID_TELESERVICE_ID: Temporary error in network, invalid teleservice id.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_OTHER: Temporary error, other network problem.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_NO_PAGE_RESPONSE: Temporary error in terminal, no page response.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_BUSY: Temporary error in terminal, destination busy.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_NO_ACKNOWLEDGMENT: Temporary error in terminal, no acknowledgement.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_RESOURCE_SHORTAGE: Temporary error in terminal, destination resource shortage.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED: Temporary error in terminal, SMS delivery postponed.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_OUT_OF_SERVICE: Temporary error in terminal, destination out of service.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_NO_LONGER_AT_THIS_ADDRESS: Temporary error in terminal, destination no longer at this address.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_OTHER: Temporary error, other terminal problem.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_RADIO_INTERFACE_PROBLEM_RESOURCE_SHORTAGE: Temporary error in radio interface, resource shortage.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_RADIO_INTERFACE_PROBLEM_INCOMPATIBILITY: Temporary error in radio interface, problem incompatibility.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_RADIO_INTERFACE_PROBLEM_OTHER: Temporary error, other radio interface problem.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_ENCODING: Temporary error, encoding.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SMS_ORIGINATION_DENIED: Temporary error, SMS origination denied.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SMS_TERMINATION_DENIED: Temporary error, SMS termination denied.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SUPPLEMENTARY_SERVICE_NOT_SUPPORTED: Temporary error, supplementary service not supported.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SMS_NOT_SUPPORTED: Temporary error, SMS not supported.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_MISSING_EXPECTED_PARAMETER: Temporary error, missing expected parameter.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_MISSING_MANDATORY_PARAMETER: Temporary error, missing mandatory parameter.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_UNRECOGNIZED_PARAMETER_VALUE: Temporary error, unrecognized parameter value.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_UNEXPECTED_PARAMETER_VALUE: Temporary error, unexpected parameter value.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_USER_DATA_SIZE_ERROR: Temporary error, user data size error.
+ * @MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_OTHER: Temporary error, other general problem.
  *
- * Enumeration of known SMS delivery states as defined in 3GPP TS 03.40.
+ * Enumeration of known SMS delivery states as defined in 3GPP TS 03.40 and
+ * 3GPP2 N.S0005-O, section 6.5.2.125.
  *
  * States out of the known ranges may also be valid (either reserved or SC-specific).
  */
 typedef enum { /*< underscore_name=mm_sms_delivery_state >*/
+    /* --------------- 3GPP specific errors ---------------------- */
+
     /* Completed deliveries */
     MM_SMS_DELIVERY_STATE_COMPLETED_RECEIVED              = 0x00,
     MM_SMS_DELIVERY_STATE_COMPLETED_FORWARDED_UNCONFIRMED = 0x01,
@@ -520,7 +591,75 @@
     MM_SMS_DELIVERY_STATE_TEMPORARY_FATAL_ERROR_IN_SME               = 0x65,
 
     /* Unknown, out of any possible valid value [0x00-0xFF] */
-    MM_SMS_DELIVERY_STATE_UNKNOWN = 0x100
+    MM_SMS_DELIVERY_STATE_UNKNOWN = 0x100,
+
+    /* --------------- 3GPP2 specific errors ---------------------- */
+
+    /* Network problems */
+    MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_ADDRESS_VACANT              = 0x200,
+    MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_ADDRESS_TRANSLATION_FAILURE = 0x201,
+    MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_NETWORK_RESOURCE_OUTAGE     = 0x202,
+    MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_NETWORK_FAILURE             = 0x203,
+    MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_INVALID_TELESERVICE_ID      = 0x204,
+    MM_SMS_DELIVERY_STATE_NETWORK_PROBLEM_OTHER                       = 0x205,
+    /* Terminal problems */
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_NO_PAGE_RESPONSE                      = 0x220,
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_BUSY                      = 0x221,
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_NO_ACKNOWLEDGMENT                     = 0x222,
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_RESOURCE_SHORTAGE         = 0x223,
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED                = 0x224,
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_OUT_OF_SERVICE            = 0x225,
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_DESTINATION_NO_LONGER_AT_THIS_ADDRESS = 0x226,
+    MM_SMS_DELIVERY_STATE_TERMINAL_PROBLEM_OTHER                                 = 0x227,
+    /* Radio problems */
+    MM_SMS_DELIVERY_STATE_RADIO_INTERFACE_PROBLEM_RESOURCE_SHORTAGE = 0x240,
+    MM_SMS_DELIVERY_STATE_RADIO_INTERFACE_PROBLEM_INCOMPATIBILITY   = 0x241,
+    MM_SMS_DELIVERY_STATE_RADIO_INTERFACE_PROBLEM_OTHER             = 0x242,
+    /* General problems */
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_ENCODING                            = 0x260,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SMS_ORIGINATION_DENIED              = 0x261,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SMS_TERMINATION_DENIED              = 0x262,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SUPPLEMENTARY_SERVICE_NOT_SUPPORTED = 0x263,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_SMS_NOT_SUPPORTED                   = 0x264,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_MISSING_EXPECTED_PARAMETER          = 0x266,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_MISSING_MANDATORY_PARAMETER         = 0x267,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_UNRECOGNIZED_PARAMETER_VALUE        = 0x268,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_UNEXPECTED_PARAMETER_VALUE          = 0x269,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_USER_DATA_SIZE_ERROR                = 0x26A,
+    MM_SMS_DELIVERY_STATE_GENERAL_PROBLEM_OTHER                               = 0x26B,
+
+    /* Temporary network problems */
+    MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_ADDRESS_VACANT              = 0x300,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_ADDRESS_TRANSLATION_FAILURE = 0x301,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_NETWORK_RESOURCE_OUTAGE     = 0x302,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_NETWORK_FAILURE             = 0x303,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_INVALID_TELESERVICE_ID      = 0x304,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_NETWORK_PROBLEM_OTHER                       = 0x305,
+    /* Temporary terminal problems */
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_NO_PAGE_RESPONSE                      = 0x320,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_BUSY                      = 0x321,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_NO_ACKNOWLEDGMENT                     = 0x322,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_RESOURCE_SHORTAGE         = 0x323,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED                = 0x324,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_OUT_OF_SERVICE            = 0x325,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_DESTINATION_NO_LONGER_AT_THIS_ADDRESS = 0x326,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_TERMINAL_PROBLEM_OTHER                                 = 0x327,
+    /* Temporary radio problems */
+    MM_SMS_DELIVERY_STATE_TEMPORARY_RADIO_INTERFACE_PROBLEM_RESOURCE_SHORTAGE = 0x340,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_RADIO_INTERFACE_PROBLEM_INCOMPATIBILITY   = 0x341,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_RADIO_INTERFACE_PROBLEM_OTHER             = 0x342,
+    /* Temporary general problems */
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_ENCODING                            = 0x360,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SMS_ORIGINATION_DENIED              = 0x361,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SMS_TERMINATION_DENIED              = 0x362,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SUPPLEMENTARY_SERVICE_NOT_SUPPORTED = 0x363,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_SMS_NOT_SUPPORTED                   = 0x364,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_MISSING_EXPECTED_PARAMETER          = 0x366,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_MISSING_MANDATORY_PARAMETER         = 0x367,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_UNRECOGNIZED_PARAMETER_VALUE        = 0x368,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_UNEXPECTED_PARAMETER_VALUE          = 0x369,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_USER_DATA_SIZE_ERROR                = 0x36A,
+    MM_SMS_DELIVERY_STATE_TEMPORARY_GENERAL_PROBLEM_OTHER                               = 0x36B,
 } MMSmsDeliveryState;
 
 /**
@@ -562,6 +701,115 @@
 } MMSmsValidityType;
 
 /**
+ * MMSmsCdmaTeleserviceId:
+ * @MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN: Unknown.
+ * @MM_SMS_CDMA_TELESERVICE_ID_CMT91: IS-91 Extended Protocol Enhanced Services.
+ * @MM_SMS_CDMA_TELESERVICE_ID_WPT: Wireless Paging Teleservice.
+ * @MM_SMS_CDMA_TELESERVICE_ID_WMT: Wireless Messaging Teleservice.
+ * @MM_SMS_CDMA_TELESERVICE_ID_VMN: Voice Mail Notification.
+ * @MM_SMS_CDMA_TELESERVICE_ID_WAP: Wireless Application Protocol.
+ * @MM_SMS_CDMA_TELESERVICE_ID_WEMT: Wireless Enhanced Messaging Teleservice.
+ * @MM_SMS_CDMA_TELESERVICE_ID_SCPT: Service Category Programming Teleservice.
+ * @MM_SMS_CDMA_TELESERVICE_ID_CATPT: Card Application Toolkit Protocol Teleservice.
+ *
+ * Teleservice IDs supported for CDMA SMS, as defined in 3GPP2 X.S0004-550-E
+ * (section 2.256) and 3GPP2 C.S0015-B (section 3.4.3.1).
+ */
+typedef enum { /*< underscore_name=mm_sms_cdma_teleservice_id >*/
+    MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN = 0x0000,
+    MM_SMS_CDMA_TELESERVICE_ID_CMT91   = 0x1000,
+    MM_SMS_CDMA_TELESERVICE_ID_WPT     = 0x1001,
+    MM_SMS_CDMA_TELESERVICE_ID_WMT     = 0x1002,
+    MM_SMS_CDMA_TELESERVICE_ID_VMN     = 0x1003,
+    MM_SMS_CDMA_TELESERVICE_ID_WAP     = 0x1004,
+    MM_SMS_CDMA_TELESERVICE_ID_WEMT    = 0x1005,
+    MM_SMS_CDMA_TELESERVICE_ID_SCPT    = 0x1006,
+    MM_SMS_CDMA_TELESERVICE_ID_CATPT   = 0x1007,
+} MMSmsCdmaTeleserviceId;
+
+/**
+ * MMSmsCdmaServiceCategory:
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN: Unknown.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_EMERGENCY_BROADCAST: Emergency broadcast.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_ADMINISTRATIVE: Administrative.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_MAINTENANCE: Maintenance.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_LOCAL: General news (local).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_REGIONAL: General news (regional).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_NATIONAL: General news (national).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_INTERNATIONAL: General news (international).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_LOCAL: Business/Financial news (local).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_REGIONAL: Business/Financial news (regional).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_NATIONAL: Business/Financial news (national).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_INTERNATIONAL: Business/Financial news (international).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_LOCAL: Sports news (local).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_REGIONAL: Sports news (regional).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_NATIONAL: Sports news (national).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_INTERNATIONAL: Sports news (international).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_LOCAL: Entertainment news (local).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_REGIONAL: Entertainment news (regional).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_NATIONAL: Entertainment news (national).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_INTERNATIONAL: Entertainment news (international).
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_LOCAL_WEATHER: Local weather.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_TRAFFIC_REPORT: Area traffic report.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_FLIGHT_SCHEDULES: Local airport flight schedules.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_RESTAURANTS: Restaurants.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_LODGINGS: Lodgings.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_RETAIL_DIRECTORY: Retail directory.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_ADVERTISEMENTS: Advertisements.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_STOCK_QUOTES: Stock quotes.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_EMPLOYMENT: Employment.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_HOSPITALS: Medical / Health / Hospitals.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_TECHNOLOGY_NEWS: Technology news.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_MULTICATEGORY: Multi-category.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_PRESIDENTIAL_ALERT: Presidential alert.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_EXTREME_THREAT: Extreme threat.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_SEVERE_THREAT: Severe threat.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: Child abduction emergency.
+ * @MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_TEST: CMAS test.
+ *
+ * Service category for CDMA SMS, as defined in 3GPP2 C.R1001-D (section 9.3).
+ */
+typedef enum { /*< underscore_name=mm_sms_cdma_service_category >*/
+    MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN                          = 0x0000,
+    MM_SMS_CDMA_SERVICE_CATEGORY_EMERGENCY_BROADCAST              = 0x0001,
+    MM_SMS_CDMA_SERVICE_CATEGORY_ADMINISTRATIVE                   = 0x0002,
+    MM_SMS_CDMA_SERVICE_CATEGORY_MAINTENANCE                      = 0x0003,
+    MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_LOCAL               = 0x0004,
+    MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_REGIONAL            = 0x0005,
+    MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_NATIONAL            = 0x0006,
+    MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_INTERNATIONAL       = 0x0007,
+    MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_LOCAL              = 0x0008,
+    MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_REGIONAL           = 0x0009,
+    MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_NATIONAL           = 0x000A,
+    MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_INTERNATIONAL      = 0x000B,
+    MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_LOCAL                = 0x000C,
+    MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_REGIONAL             = 0x000D,
+    MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_NATIONAL             = 0x000E,
+    MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_INTERNATIONAL        = 0x000F,
+    MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_LOCAL         = 0x0010,
+    MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_REGIONAL      = 0x0011,
+    MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_NATIONAL      = 0x0012,
+    MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_INTERNATIONAL = 0x0013,
+    MM_SMS_CDMA_SERVICE_CATEGORY_LOCAL_WEATHER                    = 0x0014,
+    MM_SMS_CDMA_SERVICE_CATEGORY_TRAFFIC_REPORT                   = 0x0015,
+    MM_SMS_CDMA_SERVICE_CATEGORY_FLIGHT_SCHEDULES                 = 0x0016,
+    MM_SMS_CDMA_SERVICE_CATEGORY_RESTAURANTS                      = 0x0017,
+    MM_SMS_CDMA_SERVICE_CATEGORY_LODGINGS                         = 0x0018,
+    MM_SMS_CDMA_SERVICE_CATEGORY_RETAIL_DIRECTORY                 = 0x0019,
+    MM_SMS_CDMA_SERVICE_CATEGORY_ADVERTISEMENTS                   = 0x001A,
+    MM_SMS_CDMA_SERVICE_CATEGORY_STOCK_QUOTES                     = 0x001B,
+    MM_SMS_CDMA_SERVICE_CATEGORY_EMPLOYMENT                       = 0x001C,
+    MM_SMS_CDMA_SERVICE_CATEGORY_HOSPITALS                        = 0x001D,
+    MM_SMS_CDMA_SERVICE_CATEGORY_TECHNOLOGY_NEWS                  = 0x001E,
+    MM_SMS_CDMA_SERVICE_CATEGORY_MULTICATEGORY                    = 0x001F,
+    MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_PRESIDENTIAL_ALERT          = 0x1000,
+    MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_EXTREME_THREAT              = 0x1001,
+    MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_SEVERE_THREAT               = 0x1002,
+    MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY   = 0x1003,
+    MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_TEST                        = 0x1004,
+} MMSmsCdmaServiceCategory;
+
+/**
  * MMModemLocationSource:
  * @MM_MODEM_LOCATION_SOURCE_NONE: None.
  * @MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI: Location Area Code and Cell ID.
diff --git a/introspection/org.freedesktop.ModemManager1.Sms.xml b/introspection/org.freedesktop.ModemManager1.Sms.xml
index 000caad..343e611 100644
--- a/introspection/org.freedesktop.ModemManager1.Sms.xml
+++ b/introspection/org.freedesktop.ModemManager1.Sms.xml
@@ -101,19 +101,19 @@
         Indicates when the SMS expires in the SMSC.
 
         This value is composed of a
-	<link linkend="MMSmsValidityType">MMSmsValidityType</link>
-	key, with an associated data which contains type-specific validity
-	information:
+	    <link linkend="MMSmsValidityType">MMSmsValidityType</link>
+	    key, with an associated data which contains type-specific validity
+	    information:
 
-	<variablelist>
-	  <varlistentry><term><link linkend="MM-SMS-VALIDITY-TYPE-RELATIVE:CAPS">MM_SMS_VALIDITY_TYPE_RELATIVE</link></term>
+	    <variablelist>
+	     <varlistentry><term><link linkend="MM-SMS-VALIDITY-TYPE-RELATIVE:CAPS">MM_SMS_VALIDITY_TYPE_RELATIVE</link></term>
           <listitem>
-            <para>
-	      The value is the length of the validity period in minutes, given
-	      as an unsigned integer (D-Bus signature <literal>'u'</literal>).
+           <para>
+	        The value is the length of the validity period in minutes, given
+	        as an unsigned integer (D-Bus signature <literal>'u'</literal>).
             </para>
           </listitem>
-          </varlistentry>
+         </varlistentry>
         </variablelist>
     -->
     <property name="Validity" type="(uv)" access="read" />
@@ -129,6 +129,24 @@
     <property name="Class" type="i" access="read" />
 
     <!--
+        TeleserviceId:
+
+        A <link linkend="MMSmsCdmaTeleserviceId">MMSmsCdmaTeleserviceId</link> value.
+
+        Always <link linkend="MM-SMS-CDMA-TELESERVICE-ID-UNKNOWN:CAPS">MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN</link> for 3GPP.
+    -->
+    <property name="TeleserviceId" type="u" access="read" />
+
+    <!--
+        ServiceCategory:
+
+        A <link linkend="MMSmsCdmaServiceCategory">MMSmsCdmaServiceCategory</link> value.
+
+        Always <link linkend="MM-SMS-CDMA-SERVICE-CATEGORY-UNKNOWN:CAPS">MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN</link> for 3GPP.
+    -->
+    <property name="ServiceCategory" type="u" access="read" />
+
+    <!--
         DeliveryReportRequest:
 
         #TRUE if delivery report request is required, #FALSE otherwise.
diff --git a/libmm-glib/mm-common-helpers.c b/libmm-glib/mm-common-helpers.c
index 8d1f68e..088891f 100644
--- a/libmm-glib/mm-common-helpers.c
+++ b/libmm-glib/mm-common-helpers.c
@@ -957,6 +957,50 @@
     return MM_SMS_STORAGE_UNKNOWN;
 }
 
+MMSmsCdmaTeleserviceId
+mm_common_get_sms_cdma_teleservice_id_from_string (const gchar *str,
+                                                   GError **error)
+{
+	GEnumClass *enum_class;
+    guint i;
+
+    enum_class = G_ENUM_CLASS (g_type_class_ref (MM_TYPE_SMS_CDMA_TELESERVICE_ID));
+
+    for (i = 0; enum_class->values[i].value_nick; i++) {
+        if (!g_ascii_strcasecmp (str, enum_class->values[i].value_nick))
+            return enum_class->values[i].value;
+    }
+
+    g_set_error (error,
+                 MM_CORE_ERROR,
+                 MM_CORE_ERROR_INVALID_ARGS,
+                 "Couldn't match '%s' with a valid MMSmsCdmaTeleserviceId value",
+                 str);
+    return MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN;
+}
+
+MMSmsCdmaServiceCategory
+mm_common_get_sms_cdma_service_category_from_string (const gchar *str,
+                                                     GError **error)
+{
+	GEnumClass *enum_class;
+    guint i;
+
+    enum_class = G_ENUM_CLASS (g_type_class_ref (MM_TYPE_SMS_CDMA_SERVICE_CATEGORY));
+
+    for (i = 0; enum_class->values[i].value_nick; i++) {
+        if (!g_ascii_strcasecmp (str, enum_class->values[i].value_nick))
+            return enum_class->values[i].value;
+    }
+
+    g_set_error (error,
+                 MM_CORE_ERROR,
+                 MM_CORE_ERROR_INVALID_ARGS,
+                 "Couldn't match '%s' with a valid MMSmsCdmaServiceCategory value",
+                 str);
+    return MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN;
+}
+
 MMOmaFeature
 mm_common_get_oma_features_from_string (const gchar *str,
                                         GError **error)
diff --git a/libmm-glib/mm-common-helpers.h b/libmm-glib/mm-common-helpers.h
index e9ef27a..b05a4a9 100644
--- a/libmm-glib/mm-common-helpers.h
+++ b/libmm-glib/mm-common-helpers.h
@@ -59,6 +59,10 @@
                                                               GError **error);
 MMSmsStorage          mm_common_get_sms_storage_from_string  (const gchar *str,
                                                               GError **error);
+MMSmsCdmaTeleserviceId   mm_common_get_sms_cdma_teleservice_id_from_string   (const gchar *str,
+                                                                              GError **error);
+MMSmsCdmaServiceCategory mm_common_get_sms_cdma_service_category_from_string (const gchar *str,
+                                                                              GError **error);
 MMOmaFeature          mm_common_get_oma_features_from_string (const gchar *str,
                                                               GError **error);
 MMOmaSessionType      mm_common_get_oma_session_type_from_string (const gchar *str,
diff --git a/libmm-glib/mm-sms-properties.c b/libmm-glib/mm-sms-properties.c
index 5294112..93e8b9b 100644
--- a/libmm-glib/mm-sms-properties.c
+++ b/libmm-glib/mm-sms-properties.c
@@ -34,7 +34,7 @@
  * mm_modem_messaging_create() or mm_modem_messaging_create_sync().
  */
 
-G_DEFINE_TYPE (MMSmsProperties, mm_sms_properties, G_TYPE_OBJECT);
+G_DEFINE_TYPE (MMSmsProperties, mm_sms_properties, G_TYPE_OBJECT)
 
 #define PROPERTY_TEXT                    "text"
 #define PROPERTY_DATA                    "data"
@@ -43,6 +43,8 @@
 #define PROPERTY_VALIDITY                "validity"
 #define PROPERTY_CLASS                   "class"
 #define PROPERTY_DELIVERY_REPORT_REQUEST "delivery-report-request"
+#define PROPERTY_TELESERVICE_ID          "teleservice-id"
+#define PROPERTY_SERVICE_CATEGORY        "service-category"
 
 struct _MMSmsPropertiesPrivate {
     gchar *text;
@@ -54,6 +56,8 @@
     gint class;
     gboolean delivery_report_request_set;
     gboolean delivery_report_request;
+    MMSmsCdmaTeleserviceId teleservice_id;
+    MMSmsCdmaServiceCategory service_category;
 };
 
 /*****************************************************************************/
@@ -385,6 +389,74 @@
 
 /*****************************************************************************/
 
+/**
+ * mm_sms_properties_set_teleservice_id:
+ * @self: A #MMSmsProperties.
+ * @teleservice_id: The CDMA teleservice ID.
+ *
+ * Sets the CDMA teleservice ID of the SMS.
+ */
+void
+mm_sms_properties_set_teleservice_id (MMSmsProperties *self,
+                                      MMSmsCdmaTeleserviceId teleservice_id)
+{
+    g_return_if_fail (MM_IS_SMS_PROPERTIES (self));
+
+    self->priv->teleservice_id = teleservice_id;
+}
+
+/**
+ * mm_sms_properties_get_teleservice_id:
+ * @self: A #MMSmsProperties.
+ *
+ * Gets the CDMA teleservice ID of the SMS.
+ *
+ * Returns: the CDMA teleservice ID.
+ */
+MMSmsCdmaTeleserviceId
+mm_sms_properties_get_teleservice_id (MMSmsProperties *self)
+{
+    g_return_val_if_fail (MM_IS_SMS_PROPERTIES (self), MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN);
+
+    return self->priv->teleservice_id;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_sms_properties_set_service_category:
+ * @self: A #MMSmsProperties.
+ * @service_category: The CDMA service category.
+ *
+ * Sets the CDMA service category of the SMS.
+ */
+void
+mm_sms_properties_set_service_category (MMSmsProperties *self,
+                                        MMSmsCdmaServiceCategory service_category)
+{
+    g_return_if_fail (MM_IS_SMS_PROPERTIES (self));
+
+    self->priv->service_category = service_category;
+}
+
+/**
+ * mm_sms_properties_get_service_category:
+ * @self: A #MMSmsProperties.
+ *
+ * Gets the CDMA message service category of the SMS.
+ *
+ * Returns: the CDMA service category.
+ */
+MMSmsCdmaServiceCategory
+mm_sms_properties_get_service_category (MMSmsProperties *self)
+{
+    g_return_val_if_fail (MM_IS_SMS_PROPERTIES (self), MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN);
+
+    return self->priv->service_category;
+}
+
+/*****************************************************************************/
+
 GVariant *
 mm_sms_properties_get_dictionary (MMSmsProperties *self)
 {
@@ -446,6 +518,18 @@
                                PROPERTY_DELIVERY_REPORT_REQUEST,
                                g_variant_new_boolean (self->priv->delivery_report_request));
 
+    if (self->priv->teleservice_id != MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_TELESERVICE_ID,
+                               g_variant_new_uint32 (self->priv->teleservice_id));
+
+    if (self->priv->service_category != MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_SERVICE_CATEGORY,
+                               g_variant_new_uint32 (self->priv->service_category));
+
     return g_variant_ref_sink (g_variant_builder_end (&builder));
 }
 
@@ -539,7 +623,29 @@
         }
 
         mm_sms_properties_set_delivery_report_request (self, request);
-    }  else if (g_str_equal (key, PROPERTY_DATA)) {
+    } else if (g_str_equal (key, PROPERTY_TELESERVICE_ID)) {
+        MMSmsCdmaTeleserviceId teleservice_id;
+        GError *inner_error = NULL;
+
+        teleservice_id = mm_common_get_sms_cdma_teleservice_id_from_string (value, &inner_error);
+        if (inner_error) {
+            g_propagate_error (error, inner_error);
+            return FALSE;
+        }
+
+        mm_sms_properties_set_teleservice_id (self, teleservice_id);
+    } else if (g_str_equal (key, PROPERTY_SERVICE_CATEGORY)) {
+        MMSmsCdmaServiceCategory service_category;
+        GError *inner_error = NULL;
+
+        service_category = mm_common_get_sms_cdma_service_category_from_string (value, &inner_error);
+        if (inner_error) {
+            g_propagate_error (error, inner_error);
+            return FALSE;
+        }
+
+        mm_sms_properties_set_service_category (self, service_category);
+    } else if (g_str_equal (key, PROPERTY_DATA)) {
         g_set_error (error,
                      MM_CORE_ERROR,
                      MM_CORE_ERROR_INVALID_ARGS,
@@ -648,6 +754,14 @@
         mm_sms_properties_set_delivery_report_request (
             properties,
             g_variant_get_boolean (value));
+    else if (g_str_equal (key, PROPERTY_TELESERVICE_ID))
+        mm_sms_properties_set_teleservice_id (
+            properties,
+            g_variant_get_uint32 (value));
+    else if (g_str_equal (key, PROPERTY_SERVICE_CATEGORY))
+        mm_sms_properties_set_service_category (
+            properties,
+            g_variant_get_uint32 (value));
     else {
         /* Set error */
         g_set_error (error,
@@ -756,6 +870,8 @@
                                               MMSmsPropertiesPrivate);
     self->priv->validity_type = MM_SMS_VALIDITY_TYPE_UNKNOWN;
     self->priv->class = -1;
+    self->priv->teleservice_id = MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN;
+    self->priv->service_category = MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN;
 }
 
 static void
diff --git a/libmm-glib/mm-sms-properties.h b/libmm-glib/mm-sms-properties.h
index b0c290a..fb9dbf9 100644
--- a/libmm-glib/mm-sms-properties.h
+++ b/libmm-glib/mm-sms-properties.h
@@ -74,6 +74,10 @@
                                                     gint class);
 void mm_sms_properties_set_delivery_report_request (MMSmsProperties *self,
                                                     gboolean request);
+void mm_sms_properties_set_teleservice_id          (MMSmsProperties *self,
+                                                    MMSmsCdmaTeleserviceId teleservice_id);
+void mm_sms_properties_set_service_category        (MMSmsProperties *self,
+                                                    MMSmsCdmaServiceCategory service_category);
 
 const gchar  *mm_sms_properties_get_text                    (MMSmsProperties *self);
 const guint8 *mm_sms_properties_get_data                    (MMSmsProperties *self,
@@ -86,6 +90,8 @@
 guint         mm_sms_properties_get_validity_relative       (MMSmsProperties *self);
 gint          mm_sms_properties_get_class                   (MMSmsProperties *self);
 gboolean      mm_sms_properties_get_delivery_report_request (MMSmsProperties *self);
+MMSmsCdmaTeleserviceId   mm_sms_properties_get_teleservice_id   (MMSmsProperties *self);
+MMSmsCdmaServiceCategory mm_sms_properties_get_service_category (MMSmsProperties *self);
 
 /*****************************************************************************/
 /* ModemManager/libmm-glib/mmcli specific methods */
diff --git a/libmm-glib/mm-sms.c b/libmm-glib/mm-sms.c
index e7425f3..a49deb8 100644
--- a/libmm-glib/mm-sms.c
+++ b/libmm-glib/mm-sms.c
@@ -571,6 +571,42 @@
 /*****************************************************************************/
 
 /**
+ * mm_sms_get_teleservice_id:
+ * @self: A #MMSms.
+ *
+ * Gets the 3GPP2 Teleservice ID.
+ *
+ * Returns: a #MMSmsCdmaTeleserviceId.
+ */
+MMSmsCdmaTeleserviceId
+mm_sms_get_teleservice_id (MMSms *self)
+{
+    g_return_val_if_fail (MM_IS_SMS (self), MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN);
+
+    return (MMSmsCdmaTeleserviceId) mm_gdbus_sms_get_teleservice_id (MM_GDBUS_SMS (self));
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_sms_get_service_category:
+ * @self: A #MMSms.
+ *
+ * Gets the 3GPP2 Service Category.
+ *
+ * Returns: a #MMSmsCdmaServiceCategory.
+ */
+MMSmsCdmaServiceCategory
+mm_sms_get_service_category (MMSms *self)
+{
+    g_return_val_if_fail (MM_IS_SMS (self), MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN);
+
+    return (MMSmsCdmaServiceCategory) mm_gdbus_sms_get_service_category (MM_GDBUS_SMS (self));
+}
+
+/*****************************************************************************/
+
+/**
  * mm_sms_send_finish:
  * @self: A #MMSms.
  * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to mm_sms_send().
diff --git a/libmm-glib/mm-sms.h b/libmm-glib/mm-sms.h
index def70c1..4e4e935 100644
--- a/libmm-glib/mm-sms.h
+++ b/libmm-glib/mm-sms.h
@@ -103,6 +103,10 @@
 
 MMSmsPduType  mm_sms_get_pdu_type                (MMSms *self);
 
+MMSmsCdmaTeleserviceId mm_sms_get_teleservice_id (MMSms *self);
+
+MMSmsCdmaServiceCategory mm_sms_get_service_category (MMSms *self);
+
 void     mm_sms_send        (MMSms *self,
                              GCancellable *cancellable,
                              GAsyncReadyCallback callback,
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index d775d5a..478ea78 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -367,7 +367,8 @@
 test_modem_helpers_altair_lte_CPPFLAGS = \
 	-I$(top_srcdir)/plugins/altair \
 	$(PLUGIN_COMMON_COMPILER_FLAGS)
-test_modem_helpers_altair_lte_LDFLAGS = $(top_builddir)/libmm-glib/libmm-glib.la
+test_modem_helpers_altair_lte_LDADD = $(top_builddir)/libmm-glib/libmm-glib.la
+test_modem_helpers_altair_lte_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
 
 
 # VIA modem
diff --git a/plugins/huawei/mm-plugin-huawei.c b/plugins/huawei/mm-plugin-huawei.c
index fb13fe7..524edb6 100644
--- a/plugins/huawei/mm-plugin-huawei.c
+++ b/plugins/huawei/mm-plugin-huawei.c
@@ -390,8 +390,10 @@
     }
 
     /* We can run custom init in the first interface! clear the timeout as it is no longer needed */
-    g_source_remove (fi_ctx->timeout_id);
-    fi_ctx->timeout_id = 0;
+    if (fi_ctx->timeout_id) {
+        g_source_remove (fi_ctx->timeout_id);
+        fi_ctx->timeout_id = 0;
+    }
 
     huawei_custom_init_step (ctx);
 }
diff --git a/plugins/mtk/77-mm-mtk-port-types.rules b/plugins/mtk/77-mm-mtk-port-types.rules
index 54483c4..a2508dd 100644
--- a/plugins/mtk/77-mm-mtk-port-types.rules
+++ b/plugins/mtk/77-mm-mtk-port-types.rules
@@ -4,7 +4,7 @@
 
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="0e8d", GOTO="mm_mtk_port_types_vendorcheck"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="2001", GOTO="mm_dlink_port_types_vendorcheck"
-GOTO="mm_x22x_port_types_end"
+GOTO="mm_mtk_port_types_end"
 
 # MediaTek devices ---------------------------
 
diff --git a/plugins/x22x/mm-plugin-x22x.c b/plugins/x22x/mm-plugin-x22x.c
index 9b36048..72b8f2e 100644
--- a/plugins/x22x/mm-plugin-x22x.c
+++ b/plugins/x22x/mm-plugin-x22x.c
@@ -26,6 +26,10 @@
 #include "mm-plugin-x22x.h"
 #include "mm-broadband-modem-x22x.h"
 
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
 G_DEFINE_TYPE (MMPluginX22x, mm_plugin_x22x, MM_TYPE_PLUGIN)
 
 int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION;
@@ -185,6 +189,17 @@
               GList *probes,
               GError **error)
 {
+#if defined WITH_QMI
+    if (mm_port_probe_list_has_qmi_port (probes)) {
+        mm_dbg ("QMI-powered X22X modem found...");
+        return MM_BASE_MODEM (mm_broadband_modem_qmi_new (sysfs_path,
+                                                          drivers,
+                                                          mm_plugin_get_name (self),
+                                                          vendor,
+                                                          product));
+    }
+#endif
+
     return MM_BASE_MODEM (mm_broadband_modem_x22x_new (sysfs_path,
                                                        drivers,
                                                        mm_plugin_get_name (self),
@@ -205,26 +220,28 @@
     port = mm_port_probe_peek_port (probe);
     ptype = mm_port_probe_get_port_type (probe);
 
-    /* Look for port type hints; just probing can't distinguish which port should
-     * be the data/primary port on these devices.  We have to tag them based on
-     * what the Windows .INF files say the port layout should be.
-     */
-    if (g_udev_device_get_property_as_boolean (port, "ID_MM_X22X_PORT_TYPE_MODEM")) {
-        mm_dbg ("x22x: AT port '%s/%s' flagged as primary",
-                mm_port_probe_get_port_subsys (probe),
-                mm_port_probe_get_port_name (probe));
-        pflags = MM_AT_PORT_FLAG_PRIMARY;
-    } else if (g_udev_device_get_property_as_boolean (port, "ID_MM_X22X_PORT_TYPE_AUX")) {
-        mm_dbg ("x22x: AT port '%s/%s' flagged as secondary",
-                mm_port_probe_get_port_subsys (probe),
-                mm_port_probe_get_port_name (probe));
-        pflags = MM_AT_PORT_FLAG_SECONDARY;
-    } else {
-        /* If the port was tagged by the udev rules but isn't a primary or secondary,
-         * then ignore it to guard against race conditions if a device just happens
-         * to show up with more than two AT-capable ports.
+    if (ptype == MM_PORT_TYPE_AT) {
+        /* Look for port type hints; just probing can't distinguish which port should
+         * be the data/primary port on these devices.  We have to tag them based on
+         * what the Windows .INF files say the port layout should be.
          */
-        ptype = MM_PORT_TYPE_IGNORED;
+        if (g_udev_device_get_property_as_boolean (port, "ID_MM_X22X_PORT_TYPE_MODEM")) {
+            mm_dbg ("x22x: AT port '%s/%s' flagged as primary",
+                    mm_port_probe_get_port_subsys (probe),
+                    mm_port_probe_get_port_name (probe));
+            pflags = MM_AT_PORT_FLAG_PRIMARY;
+        } else if (g_udev_device_get_property_as_boolean (port, "ID_MM_X22X_PORT_TYPE_AUX")) {
+            mm_dbg ("x22x: AT port '%s/%s' flagged as secondary",
+                    mm_port_probe_get_port_subsys (probe),
+                    mm_port_probe_get_port_name (probe));
+            pflags = MM_AT_PORT_FLAG_SECONDARY;
+        } else {
+            /* If the port was tagged by the udev rules but isn't a primary or secondary,
+             * then ignore it to guard against race conditions if a device just happens
+             * to show up with more than two AT-capable ports.
+             */
+            ptype = MM_PORT_TYPE_IGNORED;
+        }
     }
 
     return mm_base_modem_grab_port (modem,
@@ -240,7 +257,7 @@
 G_MODULE_EXPORT MMPlugin *
 mm_plugin_create (void)
 {
-    static const gchar *subsystems[] = { "tty", NULL };
+    static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
     /* Vendors: TAMobile and Olivetti */
     static const guint16 vendor_ids[] = { 0x1bbb, 0x0b3c, 0 };
     /* Only handle X22X tagged devices here. */
@@ -259,6 +276,7 @@
                       MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
                       MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
                       MM_PLUGIN_ALLOWED_AT,         TRUE,
+                      MM_PLUGIN_ALLOWED_QMI,        TRUE,
                       MM_PLUGIN_ALLOWED_UDEV_TAGS,  udev_tags,
                       MM_PLUGIN_CUSTOM_INIT,        &custom_init,
                       NULL));
diff --git a/po/LINGUAS b/po/LINGUAS
index e69de29..fbc658f 100644
--- a/po/LINGUAS
+++ b/po/LINGUAS
@@ -0,0 +1 @@
+uk
\ No newline at end of file
diff --git a/po/uk.po b/po/uk.po
new file mode 100644
index 0000000..b4c8c5d
--- /dev/null
+++ b/po/uk.po
@@ -0,0 +1,98 @@
+# Ukrainian translation of Modem Manager
+# Copyright (C) 2013 Free Software Foundation, Inc.
+# This file is distributed under the same license as the Modem Manager package.
+#
+# Yuri Chornoivan <yurchor@ukr.net>, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: Modem Manager\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-10-19 18:13+0300\n"
+"PO-Revision-Date: 2013-10-19 18:17+0300\n"
+"Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n"
+"Language-Team: Ukrainian <kde-i18n-uk@kde.org>\n"
+"Language: uk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Lokalize 1.5\n"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:1
+msgid "Control the Modem Manager daemon"
+msgstr "Керування фоновою службою Modem Manager"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:2
+msgid "System policy prevents controlling the Modem Manager."
+msgstr "Правила системи перешкоджають керування Modem Manager."
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:3
+msgid "Unlock and control a mobile broadband device"
+msgstr "Розблокувати пристрій мобільної широкосмугової мережі і керувати ним"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:4
+msgid ""
+"System policy prevents unlocking or controlling the mobile broadband device."
+msgstr ""
+"Правила системи забороняють розблокування і керування пристроями "
+"широкосмугових мобільних мереж."
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:5
+msgid "Add, modify, and delete mobile broadband contacts"
+msgstr ""
+"Додати, внести зміни і вилучити контакти пристрою мобільних широкосмугових "
+"мереж"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:6
+msgid ""
+"System policy prevents adding, modifying, or deleting this device's contacts."
+msgstr ""
+"Правила системи перешкоджають додаванню, внесенню змін та вилученню записів "
+"контактів на цьому пристрої."
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:7
+msgid "Send, save, modify, and delete text messages"
+msgstr "Надіслати, зберегти, внести зміни або вилучити текстові повідомлення"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:8
+msgid ""
+"System policy prevents sending or maniuplating this device's text messages."
+msgstr ""
+"Правила системи забороняють надсилання або керування текстовими "
+"повідомленнями цього пристрою."
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:9
+msgid "Enable and view geographic location and positioning information"
+msgstr ""
+"Увімкнути або переглянути дані щодо географічного розташування і позиціювання"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:10
+msgid ""
+"System policy prevents enabling or viewing geographic location information."
+msgstr ""
+"Правила системи забороняють вмикання або перегляд даних щодо розташування."
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:11
+msgid "Query and utilize network information and services"
+msgstr "Надіслати запит і використати дані щодо мережі і служби"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:12
+msgid ""
+"System policy prevents querying or utilizing network information and "
+"services."
+msgstr ""
+"Правила системи забороняють надсилання запитів і використання даних щодо "
+"мережі і служб."
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:13
+msgid "Query and manage firmware on a mobile broadband device"
+msgstr ""
+"Опитування та керування мікропрограмою на пристрої мобільної широкосмугової "
+"мережі"
+
+#: ../data/org.freedesktop.ModemManager1.policy.in.in.h:14
+msgid "System policy prevents querying or managing this device's firmware."
+msgstr ""
+"Правила системи перешкоджають опитуванню або керування мікропрограмою цього "
+"пристрою."
diff --git a/src/77-mm-usb-device-blacklist.rules b/src/77-mm-usb-device-blacklist.rules
index a3058f7..4a92737 100644
--- a/src/77-mm-usb-device-blacklist.rules
+++ b/src/77-mm-usb-device-blacklist.rules
@@ -94,4 +94,23 @@
 # Bluegiga BLE112B
 ATTRS{idVendor}=="2458", ATTRS{idProduct}=="0001", ENV{ID_MM_DEVICE_IGNORE}="1"
 
+# MediaTek GPS chip (HOLUX M-1200E, GlobalTop Gms-d1, etc)
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", ENV{ID_MM_DEVICE_IGNORE}="1"
+
+# PS-360 OEM (GPS sold with MS Street and Trips 2005)
+ATTRS{idVendor}=="067b", ATTRS{idProduct}=="aaa0", ENV{ID_MM_DEVICE_IGNORE}="1"
+
+# u-blox AG, u-blox 5 GPS chips
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", ENV{ID_MM_DEVICE_IGNORE}="1"
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", ENV{ID_MM_DEVICE_IGNORE}="1"
+
+# Garmin GPS devices
+DRIVERS=="garmin_gps", ENV{ID_MM_DEVICE_IGNORE}="1"
+
+# Cypress M8-based GPS devices, UPSes, and serial converters
+DRIVERS=="cypress_m8", ENV{ID_MM_DEVICE_IGNORE}="1"
+
+# All devices in the Openmoko vendor ID
+ATTRS{idVendor}=="1d50", ENV{ID_MM_DEVICE_IGNORE}="1"
+
 LABEL="mm_usb_device_blacklist_end"
diff --git a/src/77-mm-usb-serial-adapters-greylist.rules b/src/77-mm-usb-serial-adapters-greylist.rules
index 1f30833..814ff0b 100644
--- a/src/77-mm-usb-serial-adapters-greylist.rules
+++ b/src/77-mm-usb-serial-adapters-greylist.rules
@@ -24,6 +24,7 @@
 
 # Cygnal Integrated Products, Inc. CP210x
 ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea71", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
 
 # QinHeng Electronics HL-340
 ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
diff --git a/src/Makefile.am b/src/Makefile.am
index 7ed067d..d3afed2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -30,7 +30,11 @@
 	mm-charsets.c \
 	mm-charsets.h \
 	mm-sms-part.h \
-	mm-sms-part.c
+	mm-sms-part.c \
+	mm-sms-part-3gpp.h \
+	mm-sms-part-3gpp.c \
+	mm-sms-part-cdma.h \
+	mm-sms-part-cdma.c
 
 # Additional QMI support in libmodem-helpers
 if WITH_QMI
@@ -191,8 +195,6 @@
 	mm-base-modem-at.c \
 	mm-base-modem.h \
 	mm-base-modem.c \
-	mm-sms-part.h \
-	mm-sms-part.c \
 	mm-sms.h \
 	mm-sms.c \
 	mm-sms-list.h \
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index f7dbd9b..4c5d6ea 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -36,6 +36,7 @@
 #include "mm-iface-modem.h"
 #include "mm-iface-modem-3gpp.h"
 #include "mm-iface-modem-messaging.h"
+#include "mm-sms-part-3gpp.h"
 
 static void iface_modem_init (MMIfaceModem *iface);
 static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
@@ -2407,10 +2408,10 @@
     MMSmsPart *part;
     GError *error = NULL;
 
-    part = mm_sms_part_new_from_binary_pdu (pdu->message_index,
-                                            pdu->pdu_data,
-                                            pdu->pdu_data_size,
-                                            &error);
+    part = mm_sms_part_3gpp_new_from_binary_pdu (pdu->message_index,
+                                                 pdu->pdu_data,
+                                                 pdu->pdu_data_size,
+                                                 &error);
     if (part) {
         mm_dbg ("Correctly parsed PDU (%d)", pdu->message_index);
         mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index b7ad422..9caec63 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -40,6 +40,8 @@
 #include "mm-sim-qmi.h"
 #include "mm-bearer-qmi.h"
 #include "mm-sms-qmi.h"
+#include "mm-sms-part-3gpp.h"
+#include "mm-sms-part-cdma.h"
 
 static void iface_modem_init (MMIfaceModem *iface);
 static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
@@ -6573,17 +6575,8 @@
         return;
     }
 
-    /* We only handle 3GPP messaging (PDU based) currently, so just ignore
-     * CDMA-only QMI modems */
-    if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
-        mm_dbg ("Messaging capabilities supported by this modem, "
-                "but 3GPP2 messaging not supported yet by ModemManager");
-        g_simple_async_result_set_op_res_gboolean (result, FALSE);
-    } else {
-        mm_dbg ("Messaging capabilities supported");
-        g_simple_async_result_set_op_res_gboolean (result, TRUE);
-    }
-
+    mm_dbg ("Messaging capabilities supported");
+    g_simple_async_result_set_op_res_gboolean (result, TRUE);
     g_simple_async_result_complete_in_idle (result);
     g_object_unref (result);
 }
@@ -6608,8 +6601,11 @@
     }
 
     *mem1 = g_array_sized_new (FALSE, FALSE, sizeof (MMSmsStorage), 2);
-    supported = MM_SMS_STORAGE_SM;
-    g_array_append_val (*mem1, supported);
+    /* Add SM storage only if not CDMA-only */
+    if (!mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
+        supported = MM_SMS_STORAGE_SM;
+        g_array_append_val (*mem1, supported);
+    }
     supported = MM_SMS_STORAGE_ME;
     g_array_append_val (*mem1, supported);
     *mem2 = g_array_ref (*mem1);
@@ -6784,11 +6780,20 @@
 
 typedef enum {
     LOAD_INITIAL_SMS_PARTS_STEP_FIRST,
-    LOAD_INITIAL_SMS_PARTS_STEP_LIST_ALL,
-    LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_READ,
-    LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_NOT_READ,
-    LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_SENT,
-    LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_NOT_SENT,
+    LOAD_INITIAL_SMS_PARTS_STEP_3GPP_FIRST,
+    LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_ALL,
+    LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MT_READ,
+    LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MT_NOT_READ,
+    LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MO_SENT,
+    LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MO_NOT_SENT,
+    LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LAST,
+    LOAD_INITIAL_SMS_PARTS_STEP_CDMA_FIRST,
+    LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_ALL,
+    LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MT_READ,
+    LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MT_NOT_READ,
+    LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MO_SENT,
+    LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MO_NOT_SENT,
+    LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LAST,
     LOAD_INITIAL_SMS_PARTS_STEP_LAST
 } LoadInitialSmsPartsStep;
 
@@ -6843,43 +6848,43 @@
                        QmiWmsMessageFormat format,
                        GArray *data)
 {
+    MMSmsPart *part = NULL;
+    GError *error = NULL;
+
     switch (format) {
     case QMI_WMS_MESSAGE_FORMAT_CDMA:
-        mm_dbg ("Skipping CDMA messages for now...");
+        part = mm_sms_part_cdma_new_from_binary_pdu (index,
+                                                     (guint8 *)data->data,
+                                                     data->len,
+                                                     &error);
+
+        break;
+    case QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT:
+    case QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_BROADCAST:
+        part = mm_sms_part_3gpp_new_from_binary_pdu (index,
+                                                     (guint8 *)data->data,
+                                                     data->len,
+                                                     &error);
         break;
     case QMI_WMS_MESSAGE_FORMAT_MWI:
         mm_dbg ("Don't know how to process 'message waiting indicator' messages");
         break;
-    case QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT:
-    case QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_BROADCAST: {
-        MMSmsPart *part;
-        GError *error = NULL;
-
-        part = mm_sms_part_new_from_binary_pdu (index,
-                                                (guint8 *)data->data,
-                                                data->len,
-                                                &error);
-        if (part) {
-            mm_dbg ("Correctly parsed PDU (%d)",
-                    index);
-            mm_iface_modem_messaging_take_part (self,
-                                                part,
-                                                mm_sms_state_from_qmi_message_tag (tag),
-                                                mm_sms_storage_from_qmi_storage_type (storage));
-        } else {
-            /* Don't treat the error as critical */
-            mm_dbg ("Error parsing PDU (%d): %s",
-                    index,
-                    error->message);
-            g_error_free (error);
-        }
-
-        break;
-    }
     default:
         mm_dbg ("Unhandled message format '%u'", format);
         break;
     }
+
+    if (part) {
+        mm_dbg ("Correctly parsed PDU (%d)", index);
+        mm_iface_modem_messaging_take_part (self,
+                                            part,
+                                            mm_sms_state_from_qmi_message_tag (tag),
+                                            mm_sms_storage_from_qmi_storage_type (storage));
+    } else if (error) {
+        /* Don't treat the error as critical */
+        mm_dbg ("Error parsing PDU (%d): %s", index, error->message);
+        g_error_free (error);
+    }
 }
 
 static void
@@ -6942,8 +6947,10 @@
     if (ctx->i >= ctx->message_array->len ||
         !ctx->message_array) {
         /* If we just listed all SMS, we're done. Otherwise go to next tag. */
-        if (ctx->step == LOAD_INITIAL_SMS_PARTS_STEP_LIST_ALL)
-            ctx->step = LOAD_INITIAL_SMS_PARTS_STEP_LAST;
+        if (ctx->step == LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_ALL)
+            ctx->step = LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LAST;
+        else if (ctx->step == LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_ALL)
+            ctx->step = LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LAST;
         else
             ctx->step++;
         load_initial_sms_parts_step (ctx);
@@ -6960,11 +6967,21 @@
         mm_sms_storage_to_qmi_storage_type (ctx->storage),
         message->memory_index,
         NULL);
-    /* Only reading 3GPP SMS for now */
-    qmi_message_wms_raw_read_input_set_message_mode (
-        input,
-        QMI_WMS_MESSAGE_MODE_GSM_WCDMA,
-        NULL);
+
+    /* set message mode */
+    if (ctx->step < LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LAST)
+        qmi_message_wms_raw_read_input_set_message_mode (
+            input,
+            QMI_WMS_MESSAGE_MODE_GSM_WCDMA,
+            NULL);
+    else if (ctx->step < LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LAST)
+        qmi_message_wms_raw_read_input_set_message_mode (
+            input,
+            QMI_WMS_MESSAGE_MODE_CDMA,
+            NULL);
+    else
+        g_assert_not_reached ();
+
     qmi_client_wms_raw_read (QMI_CLIENT_WMS (ctx->client),
                              input,
                              3,
@@ -7019,36 +7036,95 @@
 load_initial_sms_parts_step (LoadInitialSmsPartsContext *ctx)
 {
     QmiMessageWmsListMessagesInput *input;
+    gint mode = -1;
     gint tag_type = -1;
 
     switch (ctx->step) {
     case LOAD_INITIAL_SMS_PARTS_STEP_FIRST:
         ctx->step++;
         /* Fall down */
-    case LOAD_INITIAL_SMS_PARTS_STEP_LIST_ALL:
-        mm_dbg ("loading all messages from storage '%s'...",
+    case LOAD_INITIAL_SMS_PARTS_STEP_3GPP_FIRST:
+        /* If modem doesn't have 3GPP caps, skip 3GPP SMS */
+        if (!mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self))) {
+            ctx->step = LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LAST;
+            load_initial_sms_parts_step (ctx);
+            return;
+        }
+        ctx->step++;
+        /* Fall down */
+    case LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_ALL:
+        mm_dbg ("loading all 3GPP messages from storage '%s'...",
                 mm_sms_storage_get_string (ctx->storage));
+        mode = QMI_WMS_MESSAGE_MODE_GSM_WCDMA;
         break;
-    case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_READ:
-        mm_dbg ("loading MT-read messages from storage '%s'...",
+    case LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MT_READ:
+        mm_dbg ("loading 3GPP MT-read messages from storage '%s'...",
                 mm_sms_storage_get_string (ctx->storage));
         tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MT_READ;
+        mode = QMI_WMS_MESSAGE_MODE_GSM_WCDMA;
         break;
-    case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_NOT_READ:
-        mm_dbg ("loading MT-not-read messages from storage '%s'...",
+    case LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MT_NOT_READ:
+        mm_dbg ("loading 3GPP MT-not-read messages from storage '%s'...",
                 mm_sms_storage_get_string (ctx->storage));
         tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MT_NOT_READ;
+        mode = QMI_WMS_MESSAGE_MODE_GSM_WCDMA;
         break;
-    case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_SENT:
-        mm_dbg ("loading MO-sent messages from storage '%s'...",
+    case LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MO_SENT:
+        mm_dbg ("loading 3GPP MO-sent messages from storage '%s'...",
                 mm_sms_storage_get_string (ctx->storage));
         tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MO_SENT;
+        mode = QMI_WMS_MESSAGE_MODE_GSM_WCDMA;
         break;
-    case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_NOT_SENT:
-        mm_dbg ("loading MO-not-sent messages from storage '%s'...",
+    case LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LIST_MO_NOT_SENT:
+        mm_dbg ("loading 3GPP MO-not-sent messages from storage '%s'...",
                 mm_sms_storage_get_string (ctx->storage));
         tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MO_NOT_SENT;
+        mode = QMI_WMS_MESSAGE_MODE_GSM_WCDMA;
         break;
+    case LOAD_INITIAL_SMS_PARTS_STEP_3GPP_LAST:
+        ctx->step++;
+        /* Fall down */
+    case LOAD_INITIAL_SMS_PARTS_STEP_CDMA_FIRST:
+        /* If modem doesn't have CDMA caps, skip CDMA SMS */
+        if (!mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->self))) {
+            ctx->step = LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LAST;
+            load_initial_sms_parts_step (ctx);
+            return;
+        }
+        ctx->step++;
+        /* Fall down */
+    case LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_ALL:
+        mm_dbg ("loading all CDMA messages from storage '%s'...",
+                mm_sms_storage_get_string (ctx->storage));
+        mode = QMI_WMS_MESSAGE_MODE_CDMA;
+        break;
+    case LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MT_READ:
+        mm_dbg ("loading CDMA MT-read messages from storage '%s'...",
+                mm_sms_storage_get_string (ctx->storage));
+        tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MT_READ;
+        mode = QMI_WMS_MESSAGE_MODE_CDMA;
+        break;
+    case LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MT_NOT_READ:
+        mm_dbg ("loading CDMA MT-not-read messages from storage '%s'...",
+                mm_sms_storage_get_string (ctx->storage));
+        tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MT_NOT_READ;
+        mode = QMI_WMS_MESSAGE_MODE_CDMA;
+        break;
+    case LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MO_SENT:
+        mm_dbg ("loading CDMA MO-sent messages from storage '%s'...",
+                mm_sms_storage_get_string (ctx->storage));
+        tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MO_SENT;
+        mode = QMI_WMS_MESSAGE_MODE_CDMA;
+        break;
+    case LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LIST_MO_NOT_SENT:
+        mm_dbg ("loading CDMA MO-not-sent messages from storage '%s'...",
+                mm_sms_storage_get_string (ctx->storage));
+        tag_type = QMI_WMS_MESSAGE_TAG_TYPE_MO_NOT_SENT;
+        mode = QMI_WMS_MESSAGE_MODE_CDMA;
+        break;
+    case LOAD_INITIAL_SMS_PARTS_STEP_CDMA_LAST:
+        ctx->step++;
+        /* Fall down */
     case LOAD_INITIAL_SMS_PARTS_STEP_LAST:
         /* All steps done */
         g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
@@ -7056,7 +7132,7 @@
         return;
     }
 
-    /* Request to list messages in a given storage */
+    g_assert (mode != -1);
     input = qmi_message_wms_list_messages_input_new ();
     qmi_message_wms_list_messages_input_set_storage_type (
         input,
@@ -7064,7 +7140,7 @@
         NULL);
     qmi_message_wms_list_messages_input_set_message_mode (
         input,
-        QMI_WMS_MESSAGE_MODE_GSM_WCDMA,
+        (QmiWmsMessageMode)mode,
         NULL);
     if (tag_type != -1)
         qmi_message_wms_list_messages_input_set_message_tag (
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 893dcde..b2184ac 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -42,6 +42,7 @@
 #include "mm-broadband-bearer.h"
 #include "mm-bearer-list.h"
 #include "mm-sms-list.h"
+#include "mm-sms-part-3gpp.h"
 #include "mm-sim.h"
 #include "mm-log.h"
 #include "mm-modem-helpers.h"
@@ -5575,7 +5576,7 @@
 {
     MMSmsPart *part;
     gint rv, status, tpdu_len;
-    gchar pdu[SMS_MAX_PDU_LEN + 1];
+    gchar pdu[MM_SMS_PART_3GPP_MAX_PDU_LEN + 1];
     const gchar *response;
     GError *error = NULL;
 
@@ -5593,7 +5594,7 @@
         return;
     }
 
-    rv = sscanf (response, "+CMGR: %d,,%d %" G_STRINGIFY (SMS_MAX_PDU_LEN) "s",
+    rv = sscanf (response, "+CMGR: %d,,%d %" G_STRINGIFY (MM_SMS_PART_3GPP_MAX_PDU_LEN) "s",
                  &status, &tpdu_len, pdu);
     if (rv != 3) {
         error = g_error_new (MM_CORE_ERROR,
@@ -5605,7 +5606,7 @@
         return;
     }
 
-    part = mm_sms_part_new_from_pdu (ctx->idx, pdu, &error);
+    part = mm_sms_part_3gpp_new_from_pdu (ctx->idx, pdu, &error);
     if (part) {
         mm_dbg ("Correctly parsed PDU (%d)", ctx->idx);
         mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
@@ -5715,7 +5716,7 @@
     if (!pdu)
         return;
 
-    part = mm_sms_part_new_from_pdu (SMS_PART_INVALID_INDEX, pdu, &error);
+    part = mm_sms_part_3gpp_new_from_pdu (SMS_PART_INVALID_INDEX, pdu, &error);
     if (part) {
         mm_dbg ("Correctly parsed non-stored PDU");
         mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
@@ -6082,7 +6083,7 @@
         MM3gppPduInfo *info = l->data;
         MMSmsPart *part;
 
-        part = mm_sms_part_new_from_pdu (info->index, info->pdu, &error);
+        part = mm_sms_part_3gpp_new_from_pdu (info->index, info->pdu, &error);
         if (part) {
             mm_dbg ("Correctly parsed PDU (%d)", info->index);
             mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index 9c86ebc..a295dc3 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -764,7 +764,7 @@
         return NULL;
     }
 
-    r = g_regex_new ("\\+CGDCONT:\\s*\\((\\d+)-(\\d+)\\),\\(?\"(\\S+)\"",
+    r = g_regex_new ("\\+CGDCONT:\\s*\\((\\d+)-?(\\d+)?\\),\\(?\"(\\S+)\"",
                      G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
                      0, &inner_error);
     g_assert (r != NULL);
@@ -786,19 +786,18 @@
             if (!mm_get_uint_from_match_info (match_info, 1, &min_cid))
                 mm_warn ("Invalid min CID in CGDCONT=? reply for PDP type '%s'", pdp_type_str);
             else {
-                /* Read max CID */
+                MM3gppPdpContextFormat *format;
+
+                /* Read max CID: Optional! If no value given, we default to min CID */
                 if (!mm_get_uint_from_match_info (match_info, 2, &max_cid))
-                    mm_warn ("Invalid max CID in CGDCONT=? reply for PDP type '%s'", pdp_type_str);
-                else {
-                    MM3gppPdpContextFormat *format;
+                    max_cid = min_cid;
 
-                    format = g_slice_new (MM3gppPdpContextFormat);
-                    format->pdp_type = pdp_type;
-                    format->min_cid = min_cid;
-                    format->max_cid = max_cid;
+                format = g_slice_new (MM3gppPdpContextFormat);
+                format->pdp_type = pdp_type;
+                format->min_cid = min_cid;
+                format->max_cid = max_cid;
 
-                    list = g_list_prepend (list, format);
-                }
+                list = g_list_prepend (list, format);
             }
         }
 
diff --git a/src/mm-plugin.c b/src/mm-plugin.c
index 9c28005..7318985 100644
--- a/src/mm-plugin.c
+++ b/src/mm-plugin.c
@@ -311,12 +311,17 @@
         }
     }
 
-    /* If we got filtered by vendor or product IDs  and we do not have vendor
-     * or product strings to compare with: unsupported */
+    /* If we got filtered by vendor or product IDs; mark it as unsupported only if:
+     *   a) we do not have vendor or product strings to compare with (i.e. plugin
+     *      doesn't have explicit vendor/product strings
+     *   b) the port is NOT an AT port which we can use for AT probing
+     */
     if ((vendor_filtered || product_filtered) &&
-        !self->priv->vendor_strings &&
-        !self->priv->product_strings &&
-        !self->priv->forbidden_product_strings) {
+        ((!self->priv->vendor_strings &&
+          !self->priv->product_strings &&
+          !self->priv->forbidden_product_strings) ||
+         g_str_equal (g_udev_device_get_subsystem (port), "net") ||
+         g_str_has_prefix (g_udev_device_get_name (port), "cdc-wdm"))) {
         mm_dbg ("(%s) [%s] filtered by vendor/product IDs",
                 self->priv->name,
                 g_udev_device_get_name (port));
diff --git a/src/mm-sms-mbim.c b/src/mm-sms-mbim.c
index c1e51f5..436f5b5 100644
--- a/src/mm-sms-mbim.c
+++ b/src/mm-sms-mbim.c
@@ -29,6 +29,7 @@
 #include "mm-sms-mbim.h"
 #include "mm-base-modem.h"
 #include "mm-log.h"
+#include "mm-sms-part-3gpp.h"
 
 G_DEFINE_TYPE (MMSmsMbim, mm_sms_mbim, MM_TYPE_SMS)
 
@@ -154,7 +155,7 @@
     }
 
     /* Get PDU */
-    pdu = mm_sms_part_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &msgstart, &error);
+    pdu = mm_sms_part_3gpp_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &msgstart, &error);
     if (!pdu) {
         g_simple_async_result_take_error (ctx->result, error);
         sms_send_context_complete_and_free (ctx);
diff --git a/src/mm-sms-part-3gpp.c b/src/mm-sms-part-3gpp.c
new file mode 100644
index 0000000..b305be5
--- /dev/null
+++ b/src/mm-sms-part-3gpp.c
@@ -0,0 +1,1169 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2011 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Google, Inc.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-sms-part-3gpp.h"
+#include "mm-charsets.h"
+#include "mm-log.h"
+
+#define PDU_SIZE 200
+
+#define SMS_TP_MTI_MASK               0x03
+#define  SMS_TP_MTI_SMS_DELIVER       0x00
+#define  SMS_TP_MTI_SMS_SUBMIT        0x01
+#define  SMS_TP_MTI_SMS_STATUS_REPORT 0x02
+
+#define SMS_NUMBER_TYPE_MASK          0x70
+#define SMS_NUMBER_TYPE_UNKNOWN       0x00
+#define SMS_NUMBER_TYPE_INTL          0x10
+#define SMS_NUMBER_TYPE_ALPHA         0x50
+
+#define SMS_NUMBER_PLAN_MASK          0x0f
+#define SMS_NUMBER_PLAN_TELEPHONE     0x01
+
+#define SMS_TP_MMS                    0x04
+#define SMS_TP_SRI                    0x20
+#define SMS_TP_UDHI                   0x40
+#define SMS_TP_RP                     0x80
+
+#define SMS_DCS_CODING_MASK           0xec
+#define  SMS_DCS_CODING_DEFAULT       0x00
+#define  SMS_DCS_CODING_8BIT          0x04
+#define  SMS_DCS_CODING_UCS2          0x08
+
+#define SMS_DCS_CLASS_VALID           0x10
+#define SMS_DCS_CLASS_MASK            0x03
+
+#define SMS_TIMESTAMP_LEN 7
+#define SMS_MIN_PDU_LEN (7 + SMS_TIMESTAMP_LEN)
+
+static char sms_bcd_chars[] = "0123456789*#abc\0\0";
+
+static void
+sms_semi_octets_to_bcd_string (char *dest, const guint8 *octets, int num_octets)
+{
+    int i;
+
+    for (i = 0 ; i < num_octets; i++) {
+        *dest++ = sms_bcd_chars[octets[i] & 0xf];
+        *dest++ = sms_bcd_chars[(octets[i] >> 4) & 0xf];
+    }
+    *dest++ = '\0';
+}
+
+static gboolean
+char_to_bcd (char in, guint8 *out)
+{
+    guint32 z;
+
+    if (isdigit (in)) {
+        *out = in - 0x30;
+        return TRUE;
+    }
+
+    for (z = 10; z < 16; z++) {
+        if (in == sms_bcd_chars[z]) {
+            *out = z;
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+static gsize
+sms_string_to_bcd_semi_octets (guint8 *buf, gsize buflen, const char *string)
+{
+    guint i;
+    guint8 bcd;
+    gsize addrlen, slen;
+
+    addrlen = slen = strlen (string);
+    if (addrlen % 2)
+        addrlen++;
+    g_return_val_if_fail (buflen >= addrlen, 0);
+
+    for (i = 0; i < addrlen; i += 2) {
+        if (!char_to_bcd (string[i], &bcd))
+            return 0;
+        buf[i / 2] = bcd & 0xF;
+
+        if (i >= slen - 1) {
+            /* PDU address gets padded with 0xF if string is odd length */
+            bcd = 0xF;
+        } else if (!char_to_bcd (string[i + 1], &bcd))
+            return 0;
+        buf[i / 2] |= bcd << 4;
+    }
+    return addrlen / 2;
+}
+
+/* len is in semi-octets */
+static char *
+sms_decode_address (const guint8 *address, int len)
+{
+    guint8 addrtype, addrplan;
+    char *utf8;
+
+    addrtype = address[0] & SMS_NUMBER_TYPE_MASK;
+    addrplan = address[0] & SMS_NUMBER_PLAN_MASK;
+    address++;
+
+    if (addrtype == SMS_NUMBER_TYPE_ALPHA) {
+        guint8 *unpacked;
+        guint32 unpacked_len;
+        unpacked = gsm_unpack (address, (len * 4) / 7, 0, &unpacked_len);
+        utf8 = (char *)mm_charset_gsm_unpacked_to_utf8 (unpacked,
+                                                        unpacked_len);
+        g_free(unpacked);
+    } else if (addrtype == SMS_NUMBER_TYPE_INTL &&
+               addrplan == SMS_NUMBER_PLAN_TELEPHONE) {
+        /* International telphone number, format as "+1234567890" */
+        utf8 = g_malloc (len + 3); /* '+' + digits + possible trailing 0xf + NUL */
+        utf8[0] = '+';
+        sms_semi_octets_to_bcd_string (utf8 + 1, address, (len + 1) / 2);
+    } else {
+        /*
+         * All non-alphanumeric types and plans are just digits, but
+         * don't apply any special formatting if we don't know the
+         * format.
+         */
+        utf8 = g_malloc (len + 2); /* digits + possible trailing 0xf + NUL */
+        sms_semi_octets_to_bcd_string (utf8, address, (len + 1) / 2);
+    }
+
+    return utf8;
+}
+
+static char *
+sms_decode_timestamp (const guint8 *timestamp)
+{
+    /* YYMMDDHHMMSS+ZZ */
+    char *timestr;
+    int quarters, hours;
+
+    timestr = g_malloc0 (16);
+    sms_semi_octets_to_bcd_string (timestr, timestamp, 6);
+    quarters = ((timestamp[6] & 0x7) * 10) + ((timestamp[6] >> 4) & 0xf);
+    hours = quarters / 4;
+    if (timestamp[6] & 0x08)
+        timestr[12] = '-';
+    else
+        timestr[12] = '+';
+    timestr[13] = (hours / 10) + '0';
+    timestr[14] = (hours % 10) + '0';
+    /* TODO(njw): Change timestamp rep to something that includes quarter-hours */
+    return timestr;
+}
+
+static MMSmsEncoding
+sms_encoding_type (int dcs)
+{
+    MMSmsEncoding scheme = MM_SMS_ENCODING_UNKNOWN;
+
+    switch ((dcs >> 4) & 0xf) {
+        /* General data coding group */
+        case 0: case 1:
+        case 2: case 3:
+            switch (dcs & 0x0c) {
+                case 0x08:
+                    scheme = MM_SMS_ENCODING_UCS2;
+                    break;
+                case 0x00:
+                    /* fallthrough */
+                    /* reserved - spec says to treat it as default alphabet */
+                case 0x0c:
+                    scheme = MM_SMS_ENCODING_GSM7;
+                    break;
+                case 0x04:
+                    scheme = MM_SMS_ENCODING_8BIT;
+                    break;
+            }
+            break;
+
+            /* Message waiting group (default alphabet) */
+        case 0xc:
+        case 0xd:
+            scheme = MM_SMS_ENCODING_GSM7;
+            break;
+
+            /* Message waiting group (UCS2 alphabet) */
+        case 0xe:
+            scheme = MM_SMS_ENCODING_UCS2;
+            break;
+
+            /* Data coding/message class group */
+        case 0xf:
+            switch (dcs & 0x04) {
+                case 0x00:
+                    scheme = MM_SMS_ENCODING_GSM7;
+                    break;
+                case 0x04:
+                    scheme = MM_SMS_ENCODING_8BIT;
+                    break;
+            }
+            break;
+
+            /* Reserved coding group values - spec says to treat it as default alphabet */
+        default:
+            scheme = MM_SMS_ENCODING_GSM7;
+            break;
+    }
+
+    return scheme;
+}
+
+static char *
+sms_decode_text (const guint8 *text, int len, MMSmsEncoding encoding, int bit_offset)
+{
+    char *utf8;
+    guint8 *unpacked;
+    guint32 unpacked_len;
+
+    if (encoding == MM_SMS_ENCODING_GSM7) {
+        mm_dbg ("Converting SMS part text from GSM7 to UTF8...");
+        unpacked = gsm_unpack ((const guint8 *) text, len, bit_offset, &unpacked_len);
+        utf8 = (char *) mm_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len);
+        mm_dbg ("   Got UTF-8 text: '%s'", utf8);
+        g_free (unpacked);
+    } else if (encoding == MM_SMS_ENCODING_UCS2) {
+        mm_dbg ("Converting SMS part text from UCS-2BE to UTF8...");
+        utf8 = g_convert ((char *) text, len, "UTF8", "UCS-2BE", NULL, NULL, NULL);
+        mm_dbg ("   Got UTF-8 text: '%s'", utf8);
+    } else {
+        g_warn_if_reached ();
+        utf8 = g_strdup ("");
+    }
+
+    return utf8;
+}
+
+static guint
+relative_to_validity (guint8 relative)
+{
+    if (relative <= 143)
+        return (relative + 1) * 5;
+
+    if (relative <= 167)
+        return 720 + (relative - 143) * 30;
+
+    return (relative - 166) * 1440;
+}
+
+static guint8
+validity_to_relative (guint validity)
+{
+    if (validity == 0)
+        return 167; /* 24 hours */
+
+    if (validity <= 720) {
+        /* 5 minute units up to 12 hours */
+        if (validity % 5)
+            validity += 5;
+        return (validity / 5) - 1;
+    }
+
+    if (validity > 720 && validity <= 1440) {
+        /* 12 hours + 30 minute units up to 1 day */
+        if (validity % 30)
+            validity += 30;  /* round up to next 30 minutes */
+        validity = MIN (validity, 1440);
+        return 143 + ((validity - 720) / 30);
+    }
+
+    if (validity > 1440 && validity <= 43200) {
+        /* 2 days up to 1 month */
+        if (validity % 1440)
+            validity += 1440;  /* round up to next day */
+        validity = MIN (validity, 43200);
+        return 167 + ((validity - 1440) / 1440);
+    }
+
+    /* 43200 = 30 days in minutes
+     * 10080 = 7 days in minutes
+     * 635040 = 63 weeks in minutes
+     * 40320 = 4 weeks in minutes
+     */
+    if (validity > 43200 && validity <= 635040) {
+        /* 5 weeks up to 63 weeks */
+        if (validity % 10080)
+            validity += 10080;  /* round up to next week */
+        validity = MIN (validity, 635040);
+        return 196 + ((validity - 40320) / 10080);
+    }
+
+    return 255; /* 63 weeks */
+}
+
+MMSmsPart *
+mm_sms_part_3gpp_new_from_pdu (guint index,
+                               const gchar *hexpdu,
+                               GError **error)
+{
+    gsize pdu_len;
+    guint8 *pdu;
+    MMSmsPart *part;
+
+    /* Convert PDU from hex to binary */
+    pdu = (guint8 *) mm_utils_hexstr2bin (hexpdu, &pdu_len);
+    if (!pdu) {
+        g_set_error_literal (error,
+                             MM_CORE_ERROR,
+                             MM_CORE_ERROR_FAILED,
+                             "Couldn't convert 3GPP PDU from hex to binary");
+        return NULL;
+    }
+
+    part = mm_sms_part_3gpp_new_from_binary_pdu (index, pdu, pdu_len, error);
+    g_free (pdu);
+
+    return part;
+}
+
+MMSmsPart *
+mm_sms_part_3gpp_new_from_binary_pdu (guint index,
+                                      const guint8 *pdu,
+                                      gsize pdu_len,
+                                      GError **error)
+{
+    MMSmsPart *sms_part;
+    guint8 pdu_type;
+    guint offset;
+    guint smsc_addr_size_bytes;
+    guint tp_addr_size_digits;
+    guint tp_addr_size_bytes;
+    guint8 validity_format = 0;
+    gboolean has_udh = FALSE;
+    /* The following offsets are OPTIONAL, as STATUS REPORTs may not have
+     * them; we use '0' to indicate their absence */
+    guint tp_pid_offset = 0;
+    guint tp_dcs_offset = 0;
+    guint tp_user_data_len_offset = 0;
+    MMSmsEncoding user_data_encoding = MM_SMS_ENCODING_UNKNOWN;
+
+    /* Create the new MMSmsPart */
+    sms_part = mm_sms_part_new (index, MM_SMS_PDU_TYPE_UNKNOWN);
+
+    if (index != SMS_PART_INVALID_INDEX)
+        mm_dbg ("Parsing PDU (%u)...", index);
+    else
+        mm_dbg ("Parsing PDU...");
+
+#define PDU_SIZE_CHECK(required_size, check_descr_str)                 \
+    if (pdu_len < required_size) {                                     \
+        g_set_error (error,                                            \
+                     MM_CORE_ERROR,                                    \
+                     MM_CORE_ERROR_FAILED,                             \
+                     "PDU too short, %s: %" G_GSIZE_FORMAT " < %u",    \
+                     check_descr_str,                                  \
+                     pdu_len,                                          \
+                     required_size);                                   \
+        mm_sms_part_free (sms_part);                                   \
+        return NULL;                                                   \
+    }
+
+    offset = 0;
+
+    /* ---------------------------------------------------------------------- */
+    /* SMSC, in address format, precedes the TPDU
+     * First byte represents the number of BYTES for the address value */
+    PDU_SIZE_CHECK (1, "cannot read SMSC address length");
+    smsc_addr_size_bytes = pdu[offset++];
+    if (smsc_addr_size_bytes > 0) {
+        PDU_SIZE_CHECK (offset + smsc_addr_size_bytes, "cannot read SMSC address");
+        /* SMSC may not be given in DELIVER PDUs */
+        mm_sms_part_take_smsc (sms_part,
+                               sms_decode_address (&pdu[1], 2 * (smsc_addr_size_bytes - 1)));
+        mm_dbg ("  SMSC address parsed: '%s'", mm_sms_part_get_smsc (sms_part));
+        offset += smsc_addr_size_bytes;
+    } else
+        mm_dbg ("  No SMSC address given");
+
+
+    /* ---------------------------------------------------------------------- */
+    /* TP-MTI (1 byte) */
+    PDU_SIZE_CHECK (offset + 1, "cannot read TP-MTI");
+
+    pdu_type = (pdu[offset] & SMS_TP_MTI_MASK);
+    switch (pdu_type) {
+    case SMS_TP_MTI_SMS_DELIVER:
+        mm_dbg ("  Deliver type PDU detected");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_DELIVER);
+        break;
+    case SMS_TP_MTI_SMS_SUBMIT:
+        mm_dbg ("  Submit type PDU detected");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_SUBMIT);
+        break;
+    case SMS_TP_MTI_SMS_STATUS_REPORT:
+        mm_dbg ("  Status report type PDU detected");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_STATUS_REPORT);
+        break;
+    default:
+        mm_sms_part_free (sms_part);
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_FAILED,
+                     "Unhandled message type: 0x%02x",
+                     pdu_type);
+        return NULL;
+    }
+
+    /* Delivery report was requested? */
+    if (pdu[offset] & 0x20)
+        mm_sms_part_set_delivery_report_request (sms_part, TRUE);
+
+    /* PDU with validity? (only in SUBMIT PDUs) */
+    if (pdu_type == SMS_TP_MTI_SMS_SUBMIT)
+        validity_format = pdu[offset] & 0x18;
+
+    /* PDU with user data header? */
+    if (pdu[offset] & 0x40)
+        has_udh = TRUE;
+
+    offset++;
+
+    /* ---------------------------------------------------------------------- */
+    /* TP-MR (1 byte, in STATUS_REPORT and SUBMIT PDUs */
+    if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT ||
+        pdu_type == SMS_TP_MTI_SMS_SUBMIT) {
+        PDU_SIZE_CHECK (offset + 1, "cannot read message reference");
+
+        mm_dbg ("  message reference: %u", (guint)pdu[offset]);
+        mm_sms_part_set_message_reference (sms_part, pdu[offset]);
+        offset++;
+    }
+
+
+    /* ---------------------------------------------------------------------- */
+    /* TP-DA or TP-OA or TP-RA
+     * First byte represents the number of DIGITS in the number.
+     * Round the sender address length up to an even number of
+     * semi-octets, and thus an integral number of octets.
+     */
+    PDU_SIZE_CHECK (offset + 1, "cannot read number of digits in number");
+    tp_addr_size_digits = pdu[offset++];
+    tp_addr_size_bytes = (tp_addr_size_digits + 1) >> 1;
+
+    PDU_SIZE_CHECK (offset + tp_addr_size_bytes, "cannot read number");
+    mm_sms_part_take_number (sms_part,
+                             sms_decode_address (&pdu[offset],
+                                                 tp_addr_size_digits));
+    mm_dbg ("  Number parsed: '%s'", mm_sms_part_get_number (sms_part));
+    offset += (1 + tp_addr_size_bytes); /* +1 due to the Type of Address byte */
+
+    /* ---------------------------------------------------------------------- */
+    /* Get timestamps and indexes for TP-PID, TP-DCS and TP-UDL/TP-UD */
+
+    if (pdu_type == SMS_TP_MTI_SMS_DELIVER) {
+        PDU_SIZE_CHECK (offset + 9,
+                        "cannot read PID/DCS/Timestamp"); /* 1+1+7=9 */
+
+        /* ------ TP-PID (1 byte) ------ */
+        tp_pid_offset = offset++;
+
+        /* ------ TP-DCS (1 byte) ------ */
+        tp_dcs_offset = offset++;
+
+        /* ------ Timestamp (7 bytes) ------ */
+        mm_sms_part_take_timestamp (sms_part,
+                                    sms_decode_timestamp (&pdu[offset]));
+        offset += 7;
+
+        tp_user_data_len_offset = offset;
+    } else if (pdu_type == SMS_TP_MTI_SMS_SUBMIT) {
+        PDU_SIZE_CHECK (offset + 2 + !!validity_format,
+                        "cannot read PID/DCS/Validity"); /* 1+1=2 */
+
+        /* ------ TP-PID (1 byte) ------ */
+        tp_pid_offset = offset++;
+
+        /* ------ TP-DCS (1 byte) ------ */
+        tp_dcs_offset = offset++;
+
+        /* ----------- TP-Validity-Period (1 byte) ----------- */
+        if (validity_format) {
+            switch (validity_format) {
+            case 0x10:
+                mm_dbg ("  validity available, format relative");
+                mm_sms_part_set_validity_relative (sms_part,
+                                                   relative_to_validity (pdu[offset]));
+                offset++;
+                break;
+            case 0x08:
+                /* TODO: support enhanced format; GSM 03.40 */
+                mm_dbg ("  validity available, format enhanced (not implemented)");
+                /* 7 bytes for enhanced validity */
+                offset += 7;
+                break;
+            case 0x18:
+                /* TODO: support absolute format; GSM 03.40 */
+                mm_dbg ("  validity available, format absolute (not implemented)");
+                /* 7 bytes for absolute validity */
+                offset += 7;
+                break;
+            default:
+                /* Cannot happen as we AND with the 0x18 mask */
+                g_assert_not_reached();
+            }
+        }
+
+        tp_user_data_len_offset = offset;
+    }
+    else if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT) {
+        /* We have 2 timestamps in status report PDUs:
+         *  first, the timestamp for when the PDU was received in the SMSC
+         *  second, the timestamp for when the PDU was forwarded by the SMSC
+         */
+        PDU_SIZE_CHECK (offset + 15, "cannot read Timestamps/TP-STATUS"); /* 7+7+1=15 */
+
+        /* ------ Timestamp (7 bytes) ------ */
+        mm_sms_part_take_timestamp (sms_part,
+                                    sms_decode_timestamp (&pdu[offset]));
+        offset += 7;
+
+        /* ------ Discharge Timestamp (7 bytes) ------ */
+        mm_sms_part_take_discharge_timestamp (sms_part,
+                                              sms_decode_timestamp (&pdu[offset]));
+        offset += 7;
+
+        /* ----- TP-STATUS (1 byte) ------ */
+        mm_dbg ("  delivery state: %u", (guint)pdu[offset]);
+        mm_sms_part_set_delivery_state (sms_part, pdu[offset]);
+        offset++;
+
+        /* ------ TP-PI (1 byte) OPTIONAL ------ */
+        if (offset < pdu_len) {
+            guint next_optional_field_offset = offset + 1;
+
+            /* TP-PID? */
+            if (pdu[offset] & 0x01)
+                tp_pid_offset = next_optional_field_offset++;
+
+            /* TP-DCS? */
+            if (pdu[offset] & 0x02)
+                tp_dcs_offset = next_optional_field_offset++;
+
+            /* TP-UserData? */
+            if (pdu[offset] & 0x04)
+                tp_user_data_len_offset = next_optional_field_offset;
+        }
+    } else
+        g_assert_not_reached ();
+
+    if (tp_pid_offset > 0) {
+        PDU_SIZE_CHECK (tp_pid_offset + 1, "cannot read TP-PID");
+        mm_dbg ("  PID: %u", (guint)pdu[tp_pid_offset]);
+    }
+
+    /* Grab user data encoding and message class */
+    if (tp_dcs_offset > 0) {
+        PDU_SIZE_CHECK (tp_dcs_offset + 1, "cannot read TP-DCS");
+
+        /* Encoding given in the 'alphabet' bits */
+        user_data_encoding = sms_encoding_type(pdu[tp_dcs_offset]);
+        switch (user_data_encoding) {
+        case MM_SMS_ENCODING_GSM7:
+            mm_dbg ("  user data encoding is GSM7");
+            break;
+        case MM_SMS_ENCODING_UCS2:
+            mm_dbg ("  user data encoding is UCS2");
+            break;
+        case MM_SMS_ENCODING_8BIT:
+            mm_dbg ("  user data encoding is 8bit");
+            break;
+        default:
+            mm_dbg ("  user data encoding is unknown");
+            break;
+        }
+        mm_sms_part_set_encoding (sms_part, user_data_encoding);
+
+        /* Class */
+        if (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID)
+            mm_sms_part_set_class (sms_part,
+                                   pdu[tp_dcs_offset] & SMS_DCS_CLASS_MASK);
+    }
+
+    if (tp_user_data_len_offset > 0) {
+        guint tp_user_data_size_elements;
+        guint tp_user_data_size_bytes;
+        guint tp_user_data_offset;
+        guint bit_offset;
+
+        PDU_SIZE_CHECK (tp_user_data_len_offset + 1, "cannot read TP-UDL");
+        tp_user_data_size_elements = pdu[tp_user_data_len_offset];
+        mm_dbg ("  user data length: %u elements", tp_user_data_size_elements);
+
+        if (user_data_encoding == MM_SMS_ENCODING_GSM7)
+            tp_user_data_size_bytes = (7 * (tp_user_data_size_elements + 1 )) / 8;
+        else
+            tp_user_data_size_bytes = tp_user_data_size_elements;
+        mm_dbg ("  user data length: %u bytes", tp_user_data_size_bytes);
+
+        tp_user_data_offset = tp_user_data_len_offset + 1;
+        PDU_SIZE_CHECK (tp_user_data_offset + tp_user_data_size_bytes, "cannot read TP-UD");
+
+        bit_offset = 0;
+        if (has_udh) {
+            guint udhl, end;
+
+            udhl = pdu[tp_user_data_offset] + 1;
+            end = tp_user_data_offset + udhl;
+
+            PDU_SIZE_CHECK (tp_user_data_offset + udhl, "cannot read UDH");
+
+            for (offset = tp_user_data_offset + 1; (offset + 1) < end;) {
+                guint8 ie_id, ie_len;
+
+                ie_id = pdu[offset++];
+                ie_len = pdu[offset++];
+
+                switch (ie_id) {
+                case 0x00:
+                    if (offset + 2 >= end)
+                        break;
+                    /*
+                     * Ignore the IE if one of the following is true:
+                     *  - it claims to be part 0 of M
+                     *  - it claims to be part N of M, N > M
+                     */
+                    if (pdu[offset + 2] == 0 ||
+                        pdu[offset + 2] > pdu[offset + 1])
+                        break;
+
+                    mm_sms_part_set_concat_reference (sms_part, pdu[offset]);
+                    mm_sms_part_set_concat_max (sms_part, pdu[offset + 1]);
+                    mm_sms_part_set_concat_sequence (sms_part, pdu[offset + 2]);
+                    break;
+                case 0x08:
+                    if (offset + 3 >= end)
+                        break;
+                    /* Concatenated short message, 16-bit reference */
+                    if (pdu[offset + 3] == 0 ||
+                        pdu[offset + 3] > pdu[offset + 2])
+                        break;
+
+                    mm_sms_part_set_concat_reference (sms_part, (pdu[offset] << 8) | pdu[offset + 1]);
+                    mm_sms_part_set_concat_max (sms_part,pdu[offset + 2]);
+                    mm_sms_part_set_concat_sequence (sms_part, pdu[offset + 3]);
+                    break;
+                }
+
+                offset += ie_len;
+            }
+
+            /*
+             * Move past the user data headers to prevent it from being
+             * decoded into garbage text.
+             */
+            tp_user_data_offset += udhl;
+            tp_user_data_size_bytes -= udhl;
+            if (user_data_encoding == MM_SMS_ENCODING_GSM7) {
+                /*
+                 * Find the number of bits we need to add to the length of the
+                 * user data to get a multiple of 7 (the padding).
+                 */
+                bit_offset = (7 - udhl % 7) % 7;
+                tp_user_data_size_elements -= (udhl * 8 + bit_offset) / 7;
+            } else
+                tp_user_data_size_elements -= udhl;
+        }
+
+        switch (user_data_encoding) {
+        case MM_SMS_ENCODING_GSM7:
+        case MM_SMS_ENCODING_UCS2:
+            /* Otherwise if it's 7-bit or UCS2 we can decode it */
+            mm_dbg ("Decoding SMS text with '%u' elements", tp_user_data_size_elements);
+            mm_sms_part_take_text (sms_part,
+                                   sms_decode_text (&pdu[tp_user_data_offset],
+                                                    tp_user_data_size_elements,
+                                                    user_data_encoding,
+                                                    bit_offset));
+            g_warn_if_fail (mm_sms_part_get_text (sms_part) != NULL);
+            break;
+
+        default:
+            {
+                GByteArray *raw;
+
+                mm_dbg ("Skipping SMS text: Unknown encoding (0x%02X)", user_data_encoding);
+
+                PDU_SIZE_CHECK (tp_user_data_offset + tp_user_data_size_bytes, "cannot read user data");
+
+                /* 8-bit encoding is usually binary data, and we have no idea what
+                 * actual encoding the data is in so we can't convert it.
+                 */
+                raw = g_byte_array_sized_new (tp_user_data_size_bytes);
+                g_byte_array_append (raw, &pdu[tp_user_data_offset], tp_user_data_size_bytes);
+                mm_sms_part_take_data (sms_part, raw);
+                break;
+            }
+        }
+    }
+
+    return sms_part;
+}
+
+/**
+ * mm_sms_part_3gpp_encode_address:
+ *
+ * @address: the phone number to encode
+ * @buf: the buffer to encode @address in
+ * @buflen: the size  of @buf
+ * @is_smsc: if %TRUE encode size as number of octets of address infromation,
+ *   otherwise if %FALSE encode size as number of digits of @address
+ *
+ * Returns: the size in bytes of the data added to @buf
+ **/
+guint
+mm_sms_part_3gpp_encode_address (const gchar *address,
+                                 guint8 *buf,
+                                 gsize buflen,
+                                 gboolean is_smsc)
+{
+    gsize len;
+
+    g_return_val_if_fail (address != NULL, 0);
+    g_return_val_if_fail (buf != NULL, 0);
+    g_return_val_if_fail (buflen >= 2, 0);
+
+    /* Handle number type & plan */
+    buf[1] = 0x80;  /* Bit 7 always 1 */
+    if (address[0] == '+') {
+        buf[1] |= SMS_NUMBER_TYPE_INTL;
+        address++;
+    }
+    buf[1] |= SMS_NUMBER_PLAN_TELEPHONE;
+
+    len = sms_string_to_bcd_semi_octets (&buf[2], buflen, address);
+
+    if (is_smsc)
+        buf[0] = len + 1;  /* addr length + size byte */
+    else
+        buf[0] = strlen (address);  /* number of digits in address */
+
+    return len ? len + 2 : 0;  /* addr length + size byte + number type/plan */
+}
+
+/**
+ * mm_sms_part_3gpp_get_submit_pdu:
+ *
+ * @part: the SMS message part
+ * @out_pdulen: on success, the size of the returned PDU in bytes
+ * @out_msgstart: on success, the byte index in the returned PDU where the
+ *  message starts (ie, skipping the SMSC length byte and address, if present)
+ * @error: on error, filled with the error that occurred
+ *
+ * Constructs a single-part SMS message with the given details, preferring to
+ * use the UCS2 character set when the message will fit, otherwise falling back
+ * to the GSM character set.
+ *
+ * Returns: the constructed PDU data on success, or %NULL on error
+ **/
+guint8 *
+mm_sms_part_3gpp_get_submit_pdu (MMSmsPart *part,
+                                 guint *out_pdulen,
+                                 guint *out_msgstart,
+                                 GError **error)
+{
+    guint8 *pdu;
+    guint len, offset = 0;
+    guint shift = 0;
+    guint8 *udl_ptr;
+
+    g_return_val_if_fail (mm_sms_part_get_number (part) != NULL, NULL);
+    g_return_val_if_fail (mm_sms_part_get_text (part) != NULL || mm_sms_part_get_data (part) != NULL, NULL);
+
+    if (mm_sms_part_get_pdu_type (part) != MM_SMS_PDU_TYPE_SUBMIT) {
+        g_set_error (error,
+                     MM_MESSAGE_ERROR,
+                     MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
+                     "Invalid PDU type to generate a 'submit' PDU: '%s'",
+                     mm_sms_pdu_type_get_string (mm_sms_part_get_pdu_type (part)));
+        return NULL;
+    }
+
+    mm_dbg ("Creating PDU for part...");
+
+    /* Build up the PDU */
+    pdu = g_malloc0 (PDU_SIZE);
+
+    if (mm_sms_part_get_smsc (part)) {
+        mm_dbg ("  adding SMSC to PDU...");
+        len = mm_sms_part_3gpp_encode_address (mm_sms_part_get_smsc (part), pdu, PDU_SIZE, TRUE);
+        if (len == 0) {
+            g_set_error (error,
+                         MM_MESSAGE_ERROR,
+                         MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
+                         "Invalid SMSC address '%s'", mm_sms_part_get_smsc (part));
+            goto error;
+        }
+        offset += len;
+    } else {
+        /* No SMSC, use default */
+        pdu[offset++] = 0x00;
+    }
+
+    if (out_msgstart)
+        *out_msgstart = offset;
+
+    /* ----------- First BYTE ----------- */
+    pdu[offset] = 0;
+
+    /* TP-VP present; format RELATIVE */
+    if (mm_sms_part_get_validity_relative (part) > 0) {
+        mm_dbg ("  adding validity to PDU...");
+        pdu[offset] |= 0x10;
+    }
+
+    /* Concatenation sequence only found in multipart SMS */
+    if (mm_sms_part_get_concat_sequence (part)) {
+        mm_dbg ("  adding UDHI to PDU...");
+        pdu[offset] |= 0x40; /* UDHI */
+    }
+
+    /* Delivery report requested in singlepart messages or in the last PDU of
+     * multipart messages */
+    if (mm_sms_part_get_delivery_report_request (part) &&
+        (!mm_sms_part_get_concat_sequence (part) ||
+         mm_sms_part_get_concat_max (part) == mm_sms_part_get_concat_sequence (part))) {
+        mm_dbg ("  requesting delivery report...");
+        pdu[offset] |= 0x20;
+    }
+
+    /* TP-MTI = SMS-SUBMIT */
+    pdu[offset++] |= 0x01;
+
+
+    /* ----------- TP-MR (1 byte) ----------- */
+
+    pdu[offset++] = 0x00;     /* TP-Message-Reference: filled by device */
+
+    /* ----------- Destination address ----------- */
+
+    len = mm_sms_part_3gpp_encode_address (mm_sms_part_get_number (part), &pdu[offset], PDU_SIZE - offset, FALSE);
+    if (len == 0) {
+        g_set_error (error,
+                     MM_MESSAGE_ERROR,
+                     MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
+                     "Invalid number '%s'", mm_sms_part_get_number (part));
+        goto error;
+    }
+    offset += len;
+
+    /* ----------- TP-PID (1 byte) ----------- */
+
+    pdu[offset++] = 0x00;
+
+    /* ----------- TP-DCS (1 byte) ----------- */
+    pdu[offset] = 0x00;
+
+    if (mm_sms_part_get_class (part) >= 0 && mm_sms_part_get_class (part) <= 3) {
+        mm_dbg ("  using class %d...", mm_sms_part_get_class (part));
+        pdu[offset] |= SMS_DCS_CLASS_VALID;
+        pdu[offset] |= mm_sms_part_get_class (part);
+    }
+
+    switch (mm_sms_part_get_encoding (part)) {
+    case MM_SMS_ENCODING_UCS2:
+        mm_dbg ("  using UCS2 encoding...");
+        pdu[offset] |= SMS_DCS_CODING_UCS2;
+        break;
+    case MM_SMS_ENCODING_GSM7:
+        mm_dbg ("  using GSM7 encoding...");
+        pdu[offset] |= SMS_DCS_CODING_DEFAULT;  /* GSM */
+        break;
+    default:
+        mm_dbg ("  using 8bit encoding...");
+        pdu[offset] |= SMS_DCS_CODING_8BIT;
+        break;
+    }
+    offset++;
+
+    /* ----------- TP-Validity-Period (1 byte): 4 days ----------- */
+    /* Only if TP-VPF was set in first byte */
+
+    if (mm_sms_part_get_validity_relative (part) > 0)
+        pdu[offset++] = validity_to_relative (mm_sms_part_get_validity_relative (part));
+
+    /* ----------- TP-User-Data-Length ----------- */
+    /* Set to zero initially, and keep a ptr for easy access later */
+    udl_ptr = &pdu[offset];
+    pdu[offset++] = 0;
+
+    /* Build UDH */
+    if (mm_sms_part_get_concat_sequence (part)) {
+        mm_dbg ("  adding UDH header in PDU... (reference: %u, max: %u, sequence: %u)",
+                mm_sms_part_get_concat_reference (part),
+                mm_sms_part_get_concat_max (part),
+                mm_sms_part_get_concat_sequence (part));
+        pdu[offset++] = 0x05; /* udh len */
+        pdu[offset++] = 0x00; /* mid */
+        pdu[offset++] = 0x03; /* data len */
+        pdu[offset++] = (guint8)mm_sms_part_get_concat_reference (part);
+        pdu[offset++] = (guint8)mm_sms_part_get_concat_max (part);
+        pdu[offset++] = (guint8)mm_sms_part_get_concat_sequence (part);
+
+        /* if a UDH is present and the data encoding is the default 7-bit
+         * alphabet, the user data must be 7-bit word aligned after the
+         * UDH. This means up to 6 bits of zeros need to be inserted at the
+         * start of the message.
+         *
+         * In our case the UDH is 6 bytes long, 48bits. The next multiple of
+         * 7 is therefore 49, so we only need to include one bit of padding.
+         */
+        shift = 1;
+    }
+
+    if (mm_sms_part_get_encoding (part) == MM_SMS_ENCODING_GSM7) {
+        guint8 *unpacked, *packed;
+        guint32 unlen = 0, packlen = 0;
+
+        unpacked = mm_charset_utf8_to_unpacked_gsm (mm_sms_part_get_text (part), &unlen);
+        if (!unpacked || unlen == 0) {
+            g_free (unpacked);
+            g_set_error_literal (error,
+                                 MM_MESSAGE_ERROR,
+                                 MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
+                                 "Failed to convert message text to GSM");
+            goto error;
+        }
+
+        /* Set real data length, in septets
+         * If we had UDH, add 7 septets
+         */
+        *udl_ptr = mm_sms_part_get_concat_sequence (part) ? (7 + unlen) : unlen;
+        mm_dbg ("  user data length is '%u' septets (%s UDH)",
+                *udl_ptr,
+                mm_sms_part_get_concat_sequence (part) ? "with" : "without");
+
+        packed = gsm_pack (unpacked, unlen, shift, &packlen);
+        g_free (unpacked);
+        if (!packed || packlen == 0) {
+            g_free (packed);
+            g_set_error_literal (error,
+                                 MM_MESSAGE_ERROR,
+                                 MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
+                                 "Failed to pack message text to GSM");
+            goto error;
+        }
+
+        memcpy (&pdu[offset], packed, packlen);
+        g_free (packed);
+        offset += packlen;
+    } else if (mm_sms_part_get_encoding (part) == MM_SMS_ENCODING_UCS2) {
+        GByteArray *array;
+
+        /* Try to guess a good value for the array */
+        array = g_byte_array_sized_new (strlen (mm_sms_part_get_text (part)) * 2);
+        if (!mm_modem_charset_byte_array_append (array, mm_sms_part_get_text (part), FALSE, MM_MODEM_CHARSET_UCS2)) {
+            g_byte_array_free (array, TRUE);
+            g_set_error_literal (error,
+                                 MM_MESSAGE_ERROR,
+                                 MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
+                                 "Failed to convert message text to UCS2");
+            goto error;
+        }
+
+        /* Set real data length, in octets
+         * If we had UDH, add 6 octets
+         */
+        *udl_ptr = mm_sms_part_get_concat_sequence (part) ? (6 + array->len) : array->len;
+        mm_dbg ("  user data length is '%u' octets (%s UDH)",
+                *udl_ptr,
+                mm_sms_part_get_concat_sequence (part) ? "with" : "without");
+
+        memcpy (&pdu[offset], array->data, array->len);
+        offset += array->len;
+        g_byte_array_free (array, TRUE);
+    } else if (mm_sms_part_get_encoding (part) == MM_SMS_ENCODING_8BIT) {
+        const GByteArray *data;
+
+        data = mm_sms_part_get_data (part);
+
+        /* Set real data length, in octets
+         * If we had UDH, add 6 octets
+         */
+        *udl_ptr = mm_sms_part_get_concat_sequence (part) ? (6 + data->len) : data->len;
+        mm_dbg ("  binary user data length is '%u' octets (%s UDH)",
+                *udl_ptr,
+                mm_sms_part_get_concat_sequence (part) ? "with" : "without");
+
+        memcpy (&pdu[offset], data->data, data->len);
+        offset += data->len;
+    } else
+        g_assert_not_reached ();
+
+    if (out_pdulen)
+        *out_pdulen = offset;
+    return pdu;
+
+error:
+    g_free (pdu);
+    return NULL;
+}
+
+gchar **
+mm_sms_part_3gpp_util_split_text (const gchar *text,
+                                  MMSmsEncoding *encoding)
+{
+    guint gsm_unsupported = 0;
+    gchar **out;
+    guint n_chunks;
+    guint i;
+    guint j;
+    gsize in_len;
+
+    if (!text)
+        return NULL;
+
+    in_len = strlen (text);
+
+    /* Some info about the rules for splitting.
+     *
+     * The User Data can be up to 140 bytes in the SMS part:
+     *  0) If we only need one chunk, it can be of up to 140 bytes.
+     *     If we need more than one chunk, these have to be of 140 - 6 = 134
+     *     bytes each, as we need place for the UDH header.
+     *  1) If we're using GSM7 encoding, this gives us up to 160 characters,
+     *     as we can pack 160 characters of 7bits each into 140 bytes.
+     *      160 * 7 = 140 * 8 = 1120.
+     *     If we only have 134 bytes allowed, that would mean that we can pack
+     *     up to 153 input characters:
+     *      134 * 8 = 1072; 1072/7=153.14
+     *  2) If we're using UCS2 encoding, we can pack up to 70 characters in
+     *     140 bytes (each with 2 bytes), or up to 67 characters in 134 bytes.
+     *
+     * This method does the split of the input string into N strings, so that
+     * each of the strings can be placed in a SMS part.
+     */
+
+    /* Check if we can do GSM encoding */
+    mm_charset_get_encoded_len (text,
+                                MM_MODEM_CHARSET_GSM,
+                                &gsm_unsupported);
+    if (gsm_unsupported > 0) {
+        /* If cannot do it in GSM encoding, do it in UCS-2 */
+        GByteArray *array;
+
+        *encoding = MM_SMS_ENCODING_UCS2;
+
+        /* Guess more or less the size of the output array to avoid multiple
+         * allocations */
+        array = g_byte_array_sized_new (in_len * 2);
+        if (!mm_modem_charset_byte_array_append (array,
+                                                 text,
+                                                 FALSE,
+                                                 MM_MODEM_CHARSET_UCS2)) {
+            g_byte_array_unref (array);
+            return NULL;
+        }
+
+        /* Our bytearray has it in UCS-2 now.
+         * UCS-2 is a fixed-size encoding, which means that the text has exactly
+         * 2 bytes for each unicode point. We can now split this array into
+         * chunks of 67 UCS-2 characters (134 bytes).
+         *
+         * Note that UCS-2 covers unicode points between U+0000 and U+FFFF, which
+         * means that there is no direct relationship between the size of the
+         * input text in UTF-8 and the size of the text in UCS-2. A 3-byte UTF-8
+         * encoded character will still be represented with 2 bytes in UCS-2.
+         */
+        if (array->len <= 140) {
+            out = g_new (gchar *, 2);
+            out[0] = g_strdup (text);
+            out[1] = NULL;
+        } else {
+            n_chunks = array->len / 134;
+            if (array->len % 134 != 0)
+                n_chunks++;
+
+            out = g_new0 (gchar *, n_chunks + 1);
+            for (i = 0, j = 0; i < n_chunks; i++, j += 134) {
+                out[i] = sms_decode_text (&array->data[j],
+                                          MIN (array->len - j, 134),
+                                          MM_SMS_ENCODING_UCS2,
+                                          0);
+            }
+        }
+        g_byte_array_unref (array);
+    } else {
+        /* Do it with GSM encoding */
+        *encoding = MM_SMS_ENCODING_GSM7;
+
+        if (in_len <= 160) {
+            out = g_new (gchar *, 2);
+            out[0] = g_strdup (text);
+            out[1] = NULL;
+        } else {
+            n_chunks = in_len / 153;
+            if (in_len % 153 != 0)
+                n_chunks++;
+
+            out = g_new0 (gchar *, n_chunks + 1);
+            for (i = 0, j = 0; i < n_chunks; i++, j += 153) {
+                out[i] = g_strndup (&text[j], 153);
+            }
+        }
+    }
+
+    return out;
+}
+
+GByteArray **
+mm_sms_part_3gpp_util_split_data (const guint8 *data,
+                                  gsize data_len)
+{
+    GByteArray **out;
+
+    /* Some info about the rules for splitting.
+     *
+     * The User Data can be up to 140 bytes in the SMS part:
+     *  0) If we only need one chunk, it can be of up to 140 bytes.
+     *     If we need more than one chunk, these have to be of 140 - 6 = 134
+     *     bytes each, as we need place for the UDH header.
+     */
+
+    if (data_len <= 140) {
+        out = g_new0 (GByteArray *, 2);
+        out[0] = g_byte_array_append (g_byte_array_sized_new (data_len),
+                                      data,
+                                      data_len);
+    } else {
+        guint n_chunks;
+        guint i;
+        guint j;
+
+        n_chunks = data_len / 134;
+        if (data_len % 134 != 0)
+            n_chunks ++;
+
+        out = g_new0 (GByteArray *, n_chunks + 1);
+        for (i = 0, j = 0; i < n_chunks; i++, j+= 134) {
+            out[i] = g_byte_array_append (g_byte_array_sized_new (134),
+                                          &data[j],
+                                          MIN (data_len - j, 134));
+        }
+    }
+
+    return out;
+}
diff --git a/src/mm-sms-part-3gpp.h b/src/mm-sms-part-3gpp.h
new file mode 100644
index 0000000..82709a2
--- /dev/null
+++ b/src/mm-sms-part-3gpp.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2011 - 2012 Red Hat, Inc.
+ * Copyright (C) 2013 Google, Inc.
+ */
+
+#ifndef MM_SMS_PART_3GPP_H
+#define MM_SMS_PART_3GPP_H
+
+#include <glib.h>
+#include <ModemManager-enums.h>
+
+#include "mm-sms-part.h"
+
+#define MM_SMS_PART_3GPP_MAX_PDU_LEN 344
+
+MMSmsPart *mm_sms_part_3gpp_new_from_pdu  (guint index,
+                                           const gchar *hexpdu,
+                                           GError **error);
+
+MMSmsPart *mm_sms_part_3gpp_new_from_binary_pdu (guint index,
+                                                 const guint8 *pdu,
+                                                 gsize pdu_len,
+                                                 GError **error);
+
+guint8    *mm_sms_part_3gpp_get_submit_pdu (MMSmsPart *part,
+                                            guint *out_pdulen,
+                                            guint *out_msgstart,
+                                            GError **error);
+
+/* For testcases only */
+
+guint mm_sms_part_3gpp_encode_address (const gchar *address,
+                                       guint8 *buf,
+                                       gsize buflen,
+                                       gboolean is_smsc);
+
+gchar **mm_sms_part_3gpp_util_split_text (const gchar *text,
+                                          MMSmsEncoding *encoding);
+
+GByteArray **mm_sms_part_3gpp_util_split_data (const guint8 *data,
+                                               gsize data_len);
+
+#endif /* MM_SMS_PART_3GPP_H */
diff --git a/src/mm-sms-part-cdma.c b/src/mm-sms-part-cdma.c
new file mode 100644
index 0000000..2f0c99a
--- /dev/null
+++ b/src/mm-sms-part-cdma.c
@@ -0,0 +1,1616 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2013 Google, Inc.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-charsets.h"
+#include "mm-sms-part-cdma.h"
+#include "mm-log.h"
+
+/*
+ * Documentation that you may want to have around:
+ *
+ *   3GPP2 C.S0015-B: Short Message Service (SMS) for Wideband Spread Spectrum
+ *                    Systems.
+ *
+ *   3GPP2 C.R1001-G: Administration of Parameter Value Assignments for CDMA2000
+ *                    Spread Spectrum Standards.
+ *
+ *   3GPP2 X.S0004-550-E: Mobile Application Part (MAP).
+ *
+ *   3GPP2 C.S0005-E: Upper Layer (Layer 3) Signaling Standard for CDMA2000
+ *                    Spread Spectrum Systems.
+ *
+ *   3GPP2 N.S0005-O: Cellular Radiotelecommunications Intersystem Operations.
+ */
+
+/* 3GPP2 C.S0015-B, section 3.4, table 3.4-1 */
+typedef enum {
+    MESSAGE_TYPE_POINT_TO_POINT = 0,
+    MESSAGE_TYPE_BROADCAST      = 1,
+    MESSAGE_TYPE_ACKNOWLEDGE    = 2
+} MessageType;
+
+/* 3GPP2 C.S0015-B, section 3.4.3, table 3.4.3-1 */
+typedef enum {
+    PARAMETER_ID_TELESERVICE_ID         = 0,
+    PARAMETER_ID_SERVICE_CATEGORY       = 1,
+    PARAMETER_ID_ORIGINATING_ADDRESS    = 2,
+    PARAMETER_ID_ORIGINATING_SUBADDRESS = 3,
+    PARAMETER_ID_DESTINATION_ADDRESS    = 4,
+    PARAMETER_ID_DESTINATION_SUBADDRESS = 5,
+    PARAMETER_ID_BEARER_REPLY_OPTION    = 6,
+    PARAMETER_ID_CAUSE_CODES            = 7,
+    PARAMETER_ID_BEARER_DATA            = 8
+} ParameterId;
+
+/* 3GPP2 C.S0015-B, section 3.4.3.3 */
+typedef enum {
+    DIGIT_MODE_DTMF  = 0,
+    DIGIT_MODE_ASCII = 1
+} DigitMode;
+
+/* 3GPP2 C.S0015-B, section 3.4.3.3 */
+typedef enum {
+    NUMBER_MODE_DIGIT                = 0,
+    NUMBER_MODE_DATA_NETWORK_ADDRESS = 1
+} NumberMode;
+
+/* 3GPP2 C.S0005-E, section 2.7.1.3.2.4, table 2.7.1.3.2.4-2 */
+typedef enum {
+    NUMBER_TYPE_UNKNOWN          = 0,
+    NUMBER_TYPE_INTERNATIONAL    = 1,
+    NUMBER_TYPE_NATIONAL         = 2,
+    NUMBER_TYPE_NETWORK_SPECIFIC = 3,
+    NUMBER_TYPE_SUBSCRIBER       = 4,
+    /* 5 reserved */
+    NUMBER_TYPE_ABBREVIATED      = 6,
+    /* 7 reserved */
+} NumberType;
+
+/* 3GPP2 C.S0015-B, section 3.4.3.3, table 3.4.3.3-1 */
+typedef enum {
+    DATA_NETWORK_ADDRESS_TYPE_UNKNOWN                = 0,
+    DATA_NETWORK_ADDRESS_TYPE_INTERNET_PROTOCOL      = 1,
+    DATA_NETWORK_ADDRESS_TYPE_INTERNET_EMAIL_ADDRESS = 2
+} DataNetworkAddressType;
+
+/* 3GPP2 C.S0005-E, section 2.7.1.3.2.4, table 2.7.1.3.2.4-3 */
+typedef enum {
+    NUMBERING_PLAN_UNKNOWN = 0,
+    NUMBERING_PLAN_ISDN    = 1,
+    NUMBERING_PLAN_DATA    = 3,
+    NUMBERING_PLAN_TELEX   = 4,
+    NUMBERING_PLAN_PRIVATE = 9,
+    /* 15 reserved */
+} NumberingPlan;
+
+/* 3GPP2 C.S0015-B, section 3.4.3.6 */
+typedef enum {
+    ERROR_CLASS_NO_ERROR  = 0,
+    /* 1 reserved */
+    ERROR_CLASS_TEMPORARY = 2,
+    ERROR_CLASS_PERMANENT = 3
+} ErrorClass;
+
+/* 3GPP2 N.S0005-O, section 6.5.2.125*/
+typedef enum {
+    CAUSE_CODE_NETWORK_PROBLEM_ADDRESS_VACANT              = 0,
+    CAUSE_CODE_NETWORK_PROBLEM_ADDRESS_TRANSLATION_FAILURE = 1,
+    CAUSE_CODE_NETWORK_PROBLEM_NETWORK_RESOURCE_OUTAGE     = 2,
+    CAUSE_CODE_NETWORK_PROBLEM_NETWORK_FAILURE             = 3,
+    CAUSE_CODE_NETWORK_PROBLEM_INVALID_TELESERVICE_ID      = 4,
+    CAUSE_CODE_NETWORK_PROBLEM_OTHER                       = 5,
+    /* 6 to 31 reserved, treat as CAUSE_CODE_NETWORK_PROBLEM_OTHER */
+    CAUSE_CODE_TERMINAL_PROBLEM_NO_PAGE_RESPONSE                      = 32,
+    CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_BUSY                      = 33,
+    CAUSE_CODE_TERMINAL_PROBLEM_NO_ACKNOWLEDGMENT                     = 34,
+    CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_RESOURCE_SHORTAGE         = 35,
+    CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED                = 36,
+    CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_OUT_OF_SERVICE            = 37,
+    CAUSE_CODE_TERMINAL_PROBLEM_DESTINATION_NO_LONGER_AT_THIS_ADDRESS = 38,
+    CAUSE_CODE_TERMINAL_PROBLEM_OTHER                                 = 39,
+    /* 40 to 47 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_OTHER */
+    /* 48 to 63 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED */
+    CAUSE_CODE_RADIO_INTERFACE_PROBLEM_RESOURCE_SHORTAGE = 64,
+    CAUSE_CODE_RADIO_INTERFACE_PROBLEM_INCOMPATIBILITY   = 65,
+    CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER             = 66,
+    /* 67 to 95 reserved, treat as CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER */
+    CAUSE_CODE_GENERAL_PROBLEM_ENCODING                            = 96,
+    CAUSE_CODE_GENERAL_PROBLEM_SMS_ORIGINATION_DENIED              = 97,
+    CAUSE_CODE_GENERAL_PROBLEM_SMS_TERMINATION_DENIED              = 98,
+    CAUSE_CODE_GENERAL_PROBLEM_SUPPLEMENTARY_SERVICE_NOT_SUPPORTED = 99,
+    CAUSE_CODE_GENERAL_PROBLEM_SMS_NOT_SUPPORTED                   = 100,
+    /* 101 reserved */
+    CAUSE_CODE_GENERAL_PROBLEM_MISSING_EXPECTED_PARAMETER   = 102,
+    CAUSE_CODE_GENERAL_PROBLEM_MISSING_MANDATORY_PARAMETER  = 103,
+    CAUSE_CODE_GENERAL_PROBLEM_UNRECOGNIZED_PARAMETER_VALUE = 104,
+    CAUSE_CODE_GENERAL_PROBLEM_UNEXPECTED_PARAMETER_VALUE   = 105,
+    CAUSE_CODE_GENERAL_PROBLEM_USER_DATA_SIZE_ERROR         = 106,
+    CAUSE_CODE_GENERAL_PROBLEM_OTHER                        = 107,
+    /* 108 to 223 reserved, treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER */
+    /* 224 to 255 reserved for TIA/EIA-41 extension, otherwise treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER */
+} CauseCode;
+
+/* 3GPP2 C.S0015-B, section 4.5, table 4.5-1 */
+typedef enum {
+    SUBPARAMETER_ID_MESSAGE_ID                      = 0,
+    SUBPARAMETER_ID_USER_DATA                       = 1,
+    SUBPARAMETER_ID_USER_RESPONSE_CODE              = 2,
+    SUBPARAMETER_ID_MESSAGE_CENTER_TIME_STAMP       = 3,
+    SUBPARAMETER_ID_VALIDITY_PERIOD_ABSOLUTE        = 4,
+    SUBPARAMETER_ID_VALIDITY_PERIOD_RELATIVE        = 5,
+    SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_ABSOLUTE = 6,
+    SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_RELATIVE = 7,
+    SUBPARAMETER_ID_PRIORITY_INDICATOR              = 8,
+    SUBPARAMETER_ID_PRIVACY_INDICATOR               = 9,
+    SUBPARAMETER_ID_REPLY_OPTION                    = 10,
+    SUBPARAMETER_ID_NUMBER_OF_MESSAGES              = 11,
+    SUBPARAMETER_ID_ALERT_ON_MESSAGE_DELIVERY       = 12,
+    SUBPARAMETER_ID_LANGUAGE_INDICATOR              = 13,
+    SUBPARAMETER_ID_CALL_BACK_NUMBER                = 14,
+    SUBPARAMETER_ID_MESSAGE_DISPLAY_MODE            = 15,
+    SUBPARAMETER_ID_MULTIPLE_ENCODING_USER_DATA     = 16,
+    SUBPARAMETER_ID_MESSAGE_DEPOSIT_INDEX           = 17,
+    SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_DATA   = 18,
+    SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_RESULT = 19,
+    SUBPARAMETER_ID_MESSAGE_STATUS                  = 20,
+    SUBPARAMETER_ID_TP_FAILURE_CAUSE                = 21,
+    SUBPARAMETER_ID_ENHANCED_VMN                    = 22,
+    SUBPARAMETER_ID_ENHANCED_VMN_ACK                = 23,
+} SubparameterId;
+
+/* 3GPP2 C.S0015-B, section 4.5.1, table 4.5.1-1 */
+typedef enum {
+    TELESERVICE_MESSAGE_TYPE_UNKNOWN                  = 0,
+    TELESERVICE_MESSAGE_TYPE_DELIVER                  = 1,
+    TELESERVICE_MESSAGE_TYPE_SUBMIT                   = 2,
+    TELESERVICE_MESSAGE_TYPE_CANCELLATION             = 3,
+    TELESERVICE_MESSAGE_TYPE_DELIVERY_ACKNOWLEDGEMENT = 4,
+    TELESERVICE_MESSAGE_TYPE_USER_ACKNOWLEDGEMENT     = 5,
+    TELESERVICE_MESSAGE_TYPE_READ_ACKNOWLEDGEMENT     = 6,
+} TeleserviceMessageType;
+
+/* C.R1001-G, section 9.1, table 9.1-1 */
+typedef enum {
+    ENCODING_OCTET                     = 0,
+    ENCODING_EXTENDED_PROTOCOL_MESSAGE = 1,
+    ENCODING_ASCII_7BIT                = 2,
+    ENCODING_IA5                       = 3,
+    ENCODING_UNICODE                   = 4,
+    ENCODING_SHIFT_JIS                 = 5,
+    ENCODING_KOREAN                    = 6,
+    ENCODING_LATIN_HEBREW              = 7,
+    ENCODING_LATIN                     = 8,
+    ENCODING_GSM_7BIT                  = 9,
+    ENCODING_GSM_DCS                   = 10,
+} Encoding;
+
+static const gchar *
+encoding_to_string (Encoding encoding)
+{
+    static const gchar *encoding_str[] = {
+        "octet",
+        "extend protocol message",
+        "7-bit ASCII",
+        "IA5",
+        "unicode",
+        "shift-j is",
+        "korean",
+        "latin/hebrew",
+        "latin",
+        "7-bit GSM",
+        "GSM data coding scheme"
+    };
+
+    if (encoding >= ENCODING_OCTET && encoding <= ENCODING_GSM_DCS)
+        return encoding_str[encoding];
+
+    return "unknown";
+}
+
+/*****************************************************************************/
+/* Read bits; o_bits < 8; n_bits <= 8
+ *
+ * Byte 0            Byte 1
+ * [7|6|5|4|3|2|1|0] [7|6|5|4|3|2|1|0]
+ *
+ * o_bits+n_bits <= 16
+ *
+ */
+static guint8
+read_bits (const guint8 *bytes,
+           guint8 o_bits,
+           guint8 n_bits)
+{
+    guint8 bits_in_first;
+    guint8 bits_in_second;
+
+    g_assert (o_bits < 8);
+    g_assert (n_bits <= 8);
+    g_assert (o_bits + n_bits <= 16);
+
+    /* Read only from the first byte */
+    if (o_bits + n_bits <= 8)
+        return (bytes[0] >> (8 - o_bits - n_bits)) & ((1 << n_bits) - 1);
+
+    /* Read (8 - o_bits) from the first byte and (n_bits - (8 - o_bits)) from the second byte */
+    bits_in_first = 8 - o_bits;
+    bits_in_second = n_bits - bits_in_first;
+    return (read_bits (&bytes[0], o_bits, bits_in_first) << bits_in_second) | read_bits (&bytes[1], 0, bits_in_second);
+}
+
+/*****************************************************************************/
+/* Cause code to delivery state */
+
+static MMSmsDeliveryState
+cause_code_to_delivery_state (guint8 error_class,
+                              guint8 cause_code)
+{
+    guint delivery_state = 0;
+
+    switch (error_class) {
+    case ERROR_CLASS_NO_ERROR:
+        return MM_SMS_DELIVERY_STATE_COMPLETED_RECEIVED;
+    case ERROR_CLASS_TEMPORARY:
+        delivery_state += 0x300;
+    case ERROR_CLASS_PERMANENT:
+        delivery_state += 0x200;
+    default:
+        return MM_SMS_DELIVERY_STATE_UNKNOWN;
+    }
+
+    /* Fixes for unknown cause codes */
+
+    if (cause_code >= 6 && cause_code <= 31)
+        /* 6 to 31 reserved, treat as CAUSE_CODE_NETWORK_PROBLEM_OTHER */
+        delivery_state += CAUSE_CODE_NETWORK_PROBLEM_OTHER;
+    else if (cause_code >= 40 && cause_code <= 47)
+        /* 40 to 47 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_OTHER */
+        delivery_state += CAUSE_CODE_TERMINAL_PROBLEM_OTHER;
+    else if (cause_code >= 48 && cause_code <= 63)
+        /* 48 to 63 reserved, treat as CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED */
+        delivery_state += CAUSE_CODE_TERMINAL_PROBLEM_SMS_DELIVERY_POSTPONED;
+    else if (cause_code >= 67 && cause_code <= 95)
+        /* 67 to 95 reserved, treat as CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER */
+        delivery_state += CAUSE_CODE_RADIO_INTERFACE_PROBLEM_OTHER;
+    else if (cause_code == 101)
+        /* 101 reserved */
+        delivery_state += CAUSE_CODE_GENERAL_PROBLEM_OTHER;
+    else if (cause_code >= 108 && cause_code <= 255)
+        /* 108 to 223 reserved, treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER
+         * 224 to 255 reserved for TIA/EIA-41 extension, otherwise treat as CAUSE_CODE_GENERAL_PROBLEM_OTHER */
+        delivery_state += CAUSE_CODE_GENERAL_PROBLEM_OTHER;
+    else
+        /* direct relationship */
+        delivery_state += cause_code;
+
+    return (MMSmsDeliveryState) delivery_state;
+}
+
+/*****************************************************************************/
+
+MMSmsPart *
+mm_sms_part_cdma_new_from_pdu (guint index,
+                               const gchar *hexpdu,
+                               GError **error)
+{
+    gsize pdu_len;
+    guint8 *pdu;
+    MMSmsPart *part;
+
+    /* Convert PDU from hex to binary */
+    pdu = (guint8 *) mm_utils_hexstr2bin (hexpdu, &pdu_len);
+    if (!pdu) {
+        g_set_error_literal (error,
+                             MM_CORE_ERROR,
+                             MM_CORE_ERROR_FAILED,
+                             "Couldn't convert CDMA PDU from hex to binary");
+        return NULL;
+    }
+
+    part = mm_sms_part_cdma_new_from_binary_pdu (index, pdu, pdu_len, error);
+    g_free (pdu);
+
+    return part;
+}
+
+struct Parameter {
+    guint8 parameter_id;
+    guint8 parameter_len;
+    guint8 parameter_value[];
+} __attribute__((packed));
+
+static void
+read_teleservice_id (MMSmsPart *sms_part,
+                     const struct Parameter *parameter)
+{
+    guint16 teleservice_id;
+
+    g_assert (parameter->parameter_id == PARAMETER_ID_TELESERVICE_ID);
+
+    if (parameter->parameter_len != 2) {
+        mm_dbg ("        invalid teleservice ID length found (%u != 2): ignoring",
+                parameter->parameter_len);
+        return;
+    }
+
+    memcpy (&teleservice_id, &parameter->parameter_value[0], 2);
+    teleservice_id = GUINT16_FROM_BE (teleservice_id);
+
+    switch (teleservice_id){
+	case MM_SMS_CDMA_TELESERVICE_ID_CMT91:
+	case MM_SMS_CDMA_TELESERVICE_ID_WPT:
+	case MM_SMS_CDMA_TELESERVICE_ID_WMT:
+	case MM_SMS_CDMA_TELESERVICE_ID_VMN:
+	case MM_SMS_CDMA_TELESERVICE_ID_WAP:
+	case MM_SMS_CDMA_TELESERVICE_ID_WEMT:
+	case MM_SMS_CDMA_TELESERVICE_ID_SCPT:
+	case MM_SMS_CDMA_TELESERVICE_ID_CATPT:
+        break;
+    default:
+        mm_dbg ("        invalid teleservice ID found (%u): ignoring", teleservice_id);
+        return;
+    }
+
+    mm_dbg ("        teleservice ID: %s (%u)",
+            mm_sms_cdma_teleservice_id_get_string (teleservice_id),
+            teleservice_id);
+
+    mm_sms_part_set_cdma_teleservice_id (sms_part,
+                                         (MMSmsCdmaTeleserviceId)teleservice_id);
+}
+
+static void
+read_service_category (MMSmsPart *sms_part,
+                       const struct Parameter *parameter)
+{
+    guint16 service_category;
+
+    g_assert (parameter->parameter_id == PARAMETER_ID_SERVICE_CATEGORY);
+
+    if (parameter->parameter_len != 2) {
+        mm_dbg ("        invalid service category length found (%u != 2): ignoring",
+                parameter->parameter_len);
+        return;
+    }
+
+    memcpy (&service_category, &parameter->parameter_value[0], 2);
+    service_category = GUINT16_FROM_BE (service_category);
+
+    switch (service_category) {
+    case MM_SMS_CDMA_SERVICE_CATEGORY_EMERGENCY_BROADCAST:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_ADMINISTRATIVE:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_MAINTENANCE:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_LOCAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_REGIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_NATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_GENERAL_NEWS_INTERNATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_LOCAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_REGIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_NATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_BUSINESS_NEWS_INTERNATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_LOCAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_REGIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_NATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_SPORTS_NEWS_INTERNATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_LOCAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_REGIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_NATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_ENTERTAINMENT_NEWS_INTERNATIONAL:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_LOCAL_WEATHER:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_TRAFFIC_REPORT:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_FLIGHT_SCHEDULES:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_RESTAURANTS:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_LODGINGS:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_RETAIL_DIRECTORY:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_ADVERTISEMENTS:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_STOCK_QUOTES:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_EMPLOYMENT:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_HOSPITALS:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_TECHNOLOGY_NEWS:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_MULTICATEGORY:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_PRESIDENTIAL_ALERT:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+    case MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_TEST:
+        break;
+    default:
+        mm_dbg ("        invalid service category found (%u): ignoring", service_category);
+        return;
+    }
+
+    mm_dbg ("        service category: %s (%u)",
+            mm_sms_cdma_service_category_get_string (service_category),
+            service_category);
+
+    mm_sms_part_set_cdma_service_category (sms_part,
+                                         (MMSmsCdmaServiceCategory)service_category);
+}
+
+static guint8
+dtmf_to_ascii (guint8 dtmf)
+{
+    static const gchar dtmf_to_ascii_digits[13] = {
+        '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '*', '#' };
+
+    if (dtmf > 0 && dtmf < 13)
+        return dtmf_to_ascii_digits[dtmf];
+
+    mm_dbg ("        invalid dtmf digit: %u", dtmf);
+    return '\0';
+}
+
+static void
+read_address (MMSmsPart *sms_part,
+              const struct Parameter *parameter)
+{
+    guint8 digit_mode;
+    guint8 number_mode;
+    guint8 number_type;
+    guint8 numbering_plan;
+    guint8 num_fields;
+    guint byte_offset = 0;
+    guint bit_offset = 0;
+    guint i;
+    gchar *number = NULL;
+
+#define OFFSETS_UPDATE(n_bits) do { \
+        bit_offset += n_bits;       \
+        if (bit_offset >= 8) {      \
+            bit_offset-=8;          \
+            byte_offset++;          \
+        }                           \
+    } while (0)
+
+#define PARAMETER_SIZE_CHECK(required_size)                             \
+    if (parameter->parameter_len < required_size) {                     \
+        mm_dbg ("        cannot read address, need at least %u bytes (got %u)", \
+                required_size,                                          \
+                parameter->parameter_len);                              \
+        return;                                                         \
+    }
+
+    /* Readability of digit mode and number mode (first 2 bits, i.e. first byte) */
+    PARAMETER_SIZE_CHECK (1);
+
+    /* Digit mode */
+    digit_mode = read_bits (&parameter->parameter_value[byte_offset], bit_offset, 1);
+    OFFSETS_UPDATE (1);
+    g_assert (digit_mode <= 1);
+    switch (digit_mode) {
+    case DIGIT_MODE_DTMF:
+        mm_dbg ("        digit mode: dtmf");
+        break;
+    case DIGIT_MODE_ASCII:
+        mm_dbg ("        digit mode: ascii");
+        break;
+    default:
+        g_assert_not_reached ();
+    }
+
+    /* Number mode */
+    number_mode = read_bits (&parameter->parameter_value[byte_offset], bit_offset, 1);
+    OFFSETS_UPDATE (1);
+    switch (number_mode) {
+    case NUMBER_MODE_DIGIT:
+        mm_dbg ("        number mode: digit");
+        break;
+    case NUMBER_MODE_DATA_NETWORK_ADDRESS:
+        mm_dbg ("        number mode: data network address");
+        break;
+    default:
+        g_assert_not_reached ();
+    }
+
+    /* Number type */
+    if (digit_mode == DIGIT_MODE_ASCII) {
+        /* No need for readability check, still in first byte always */
+        number_type = read_bits (&parameter->parameter_value[byte_offset], bit_offset, 3);
+        OFFSETS_UPDATE (3);
+        switch (number_type) {
+        case NUMBER_TYPE_UNKNOWN:
+            mm_dbg ("        number type: unknown");
+            break;
+        case NUMBER_TYPE_INTERNATIONAL:
+            mm_dbg ("        number type: international");
+            break;
+        case NUMBER_TYPE_NATIONAL:
+            mm_dbg ("        number type: national");
+            break;
+        case NUMBER_TYPE_NETWORK_SPECIFIC:
+            mm_dbg ("        number type: specific");
+            break;
+        case NUMBER_TYPE_SUBSCRIBER:
+            mm_dbg ("        number type: subscriber");
+            break;
+        case NUMBER_TYPE_ABBREVIATED:
+            mm_dbg ("        number type: abbreviated");
+            break;
+        default:
+            mm_dbg ("        number type unknown (%u)", number_type);
+            break;
+        }
+    } else
+        number_type = 0xFF;
+
+    /* Numbering plan */
+    if (digit_mode == DIGIT_MODE_ASCII && number_mode == NUMBER_MODE_DIGIT) {
+        /* Readability of numbering plan; may go to second byte */
+        PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + 4) / 8));
+        numbering_plan = read_bits (&parameter->parameter_value[byte_offset], bit_offset, 4);
+        OFFSETS_UPDATE (4);
+        switch (numbering_plan) {
+        case NUMBERING_PLAN_UNKNOWN:
+            mm_dbg ("        numbering plan: unknown");
+            break;
+        case NUMBERING_PLAN_ISDN:
+            mm_dbg ("        numbering plan: isdn");
+            break;
+        case NUMBERING_PLAN_DATA:
+            mm_dbg ("        numbering plan: data");
+            break;
+        case NUMBERING_PLAN_TELEX:
+            mm_dbg ("        numbering plan: telex");
+            break;
+        case NUMBERING_PLAN_PRIVATE:
+            mm_dbg ("        numbering plan: private");
+            break;
+        default:
+            mm_dbg ("        numbering plan unknown (%u)", numbering_plan);
+            break;
+        }
+    } else
+        numbering_plan = 0xFF;
+
+    /* Readability of num_fields; will go to third byte (((bit_offset + 8) / 8) == 1) */
+    PARAMETER_SIZE_CHECK (byte_offset + 2);
+    num_fields = read_bits (&parameter->parameter_value[byte_offset], bit_offset, 8);
+    OFFSETS_UPDATE (8);
+    mm_dbg ("        num fields: %u", num_fields);
+
+    /* Address string */
+
+    if (digit_mode == DIGIT_MODE_DTMF) {
+        /* DTMF */
+        PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 4)) / 8));
+        number = g_malloc (num_fields + 1);
+        for (i = 0; i < num_fields; i++) {
+            number[i] = dtmf_to_ascii (read_bits (&parameter->parameter_value[byte_offset], bit_offset, 4));
+            OFFSETS_UPDATE (4);
+        }
+        number[i] = '\0';
+    } else if (number_mode == NUMBER_MODE_DIGIT) {
+        /* ASCII
+         * TODO: should we expose numbering plan and number type? */
+        PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8));
+        number = g_malloc (num_fields + 1);
+        for (i = 0; i < num_fields; i++) {
+            number[i] = read_bits (&parameter->parameter_value[byte_offset], bit_offset, 8);
+            OFFSETS_UPDATE (8);
+        }
+        number[i] = '\0';
+    } else if (number_type == DATA_NETWORK_ADDRESS_TYPE_INTERNET_EMAIL_ADDRESS) {
+        /* Internet e-mail address (ASCII) */
+        PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8));
+        number = g_malloc (num_fields + 1);
+        for (i = 0; i < num_fields; i++) {
+            number[i] = read_bits (&parameter->parameter_value[byte_offset], bit_offset, 8);
+            OFFSETS_UPDATE (8);
+        }
+        number[i] = '\0';
+    } else if (number_type == DATA_NETWORK_ADDRESS_TYPE_INTERNET_PROTOCOL) {
+        GString *str;
+
+        /* Binary data network address (most significant first)
+         * For now, just print the hex string (e.g. FF:01...) */
+        PARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8));
+        str = g_string_sized_new (num_fields * 2);
+        for (i = 0; i < num_fields; i++) {
+            g_string_append_printf (str, "%.2X", read_bits (&parameter->parameter_value[byte_offset], bit_offset, 8));
+            OFFSETS_UPDATE (8);
+        }
+        number = g_string_free (str, FALSE);
+    } else
+        mm_dbg ("        data network address number type unknown (%u)", number_type);
+
+    mm_dbg ("        address: %s", number);
+
+    mm_sms_part_set_number (sms_part, number);
+    g_free (number);
+
+#undef OFFSETS_UPDATE
+#undef PARAMETER_SIZE_CHECK
+}
+
+static void
+read_bearer_reply_option (MMSmsPart *sms_part,
+                          const struct Parameter *parameter)
+{
+    guint8 sequence;
+
+    g_assert (parameter->parameter_id == PARAMETER_ID_BEARER_REPLY_OPTION);
+
+    if (parameter->parameter_len != 1) {
+        mm_dbg ("        invalid bearer reply option length found (%u != 1): ignoring",
+                parameter->parameter_len);
+        return;
+    }
+
+    sequence = read_bits (&parameter->parameter_value[0], 0, 6);
+    mm_dbg ("        sequence: %u", sequence);
+
+    mm_sms_part_set_message_reference (sms_part, sequence);
+}
+
+static void
+read_cause_codes (MMSmsPart *sms_part,
+                  const struct Parameter *parameter)
+{
+    guint8 sequence;
+    guint8 error_class;
+    guint8 cause_code;
+    MMSmsDeliveryState delivery_state;
+
+    g_assert (parameter->parameter_id == PARAMETER_ID_BEARER_REPLY_OPTION);
+
+    if (parameter->parameter_len != 1 && parameter->parameter_len != 2) {
+        mm_dbg ("        invalid cause codes length found (%u): ignoring",
+                parameter->parameter_len);
+        return;
+    }
+
+    sequence = read_bits (&parameter->parameter_value[0], 0, 6);
+    mm_dbg ("        sequence: %u", sequence);
+
+    error_class = read_bits (&parameter->parameter_value[0], 6, 2);
+    mm_dbg ("        error class: %u", error_class);
+
+    if (error_class != ERROR_CLASS_NO_ERROR) {
+        if (parameter->parameter_len != 2) {
+            mm_dbg ("        invalid cause codes length found (%u != 2): ignoring",
+                    parameter->parameter_len);
+            return;
+        }
+        cause_code = parameter->parameter_value[1];
+        mm_dbg ("        cause code: %u", cause_code);
+    } else
+        cause_code = 0;
+
+    delivery_state = cause_code_to_delivery_state (error_class, cause_code);
+    mm_dbg ("        delivery state: %s", mm_sms_delivery_state_get_string (delivery_state));
+
+    mm_sms_part_set_message_reference (sms_part, sequence);
+    mm_sms_part_set_delivery_state (sms_part, delivery_state);
+}
+
+static void
+read_bearer_data_message_identifier (MMSmsPart *sms_part,
+                                     const struct Parameter *subparameter)
+{
+    guint8 message_type;
+    guint16 message_id;
+    guint8 header_ind;
+
+    g_assert (subparameter->parameter_id == SUBPARAMETER_ID_MESSAGE_ID);
+
+    if (subparameter->parameter_len != 3) {
+        mm_dbg ("        invalid message identifier length found (%u): ignoring",
+                subparameter->parameter_len);
+        return;
+    }
+
+    message_type = read_bits (&subparameter->parameter_value[0], 0, 4);
+    switch (message_type) {
+    case TELESERVICE_MESSAGE_TYPE_UNKNOWN:
+        mm_dbg ("            message type: unknown");
+        break;
+    case TELESERVICE_MESSAGE_TYPE_DELIVER:
+        mm_dbg ("            message type: deliver");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_DELIVER);
+        break;
+    case TELESERVICE_MESSAGE_TYPE_SUBMIT:
+        mm_dbg ("            message type: submit");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_SUBMIT);
+        break;
+    case TELESERVICE_MESSAGE_TYPE_CANCELLATION:
+        mm_dbg ("            message type: cancellation");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_CANCELLATION);
+        break;
+    case TELESERVICE_MESSAGE_TYPE_DELIVERY_ACKNOWLEDGEMENT:
+        mm_dbg ("            message type: delivery acknowledgement");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_DELIVERY_ACKNOWLEDGEMENT);
+        break;
+    case TELESERVICE_MESSAGE_TYPE_USER_ACKNOWLEDGEMENT:
+        mm_dbg ("            message type: user acknowledgement");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_USER_ACKNOWLEDGEMENT);
+        break;
+    case TELESERVICE_MESSAGE_TYPE_READ_ACKNOWLEDGEMENT:
+        mm_dbg ("            message type: read acknowledgement");
+        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_CDMA_READ_ACKNOWLEDGEMENT);
+        break;
+    default:
+        mm_dbg ("            message type unknown (%u)", message_type);
+        break;
+    }
+
+    message_id = ((read_bits (&subparameter->parameter_value[0], 4, 8) << 8) |
+                  (read_bits (&subparameter->parameter_value[1], 4, 8)));
+    message_id = GUINT16_FROM_BE (message_id);
+    mm_dbg ("            message id: %u", (guint) message_id);
+
+    header_ind = read_bits (&subparameter->parameter_value[2], 4, 1);
+    mm_dbg ("            header indicator: %u", header_ind);
+}
+
+static void
+read_bearer_data_user_data (MMSmsPart *sms_part,
+                            const struct Parameter *subparameter)
+{
+    guint8 message_encoding;
+    guint8 message_type = 0;
+    guint8 num_fields;
+    guint byte_offset = 0;
+    guint bit_offset = 0;
+
+#define OFFSETS_UPDATE(n_bits) do { \
+        bit_offset += n_bits;       \
+        if (bit_offset >= 8) {      \
+            bit_offset-=8;          \
+            byte_offset++;          \
+        }                           \
+    } while (0)
+
+#define SUBPARAMETER_SIZE_CHECK(required_size)                             \
+    if (subparameter->parameter_len < required_size) {                  \
+        mm_dbg ("        cannot read user data, need at least %u bytes (got %u)", \
+                required_size,                                          \
+                subparameter->parameter_len);                           \
+        return;                                                         \
+    }
+
+    g_assert (subparameter->parameter_id == SUBPARAMETER_ID_USER_DATA);
+
+    /* Message encoding */
+    SUBPARAMETER_SIZE_CHECK (1);
+    message_encoding = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 5);
+    OFFSETS_UPDATE (5);
+    mm_dbg ("            message encoding: %s", encoding_to_string (message_encoding));
+
+    /* Message type, only if extended protocol message */
+    if (message_encoding == ENCODING_EXTENDED_PROTOCOL_MESSAGE) {
+        SUBPARAMETER_SIZE_CHECK (2);
+        message_type = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8);
+        OFFSETS_UPDATE (8);
+        mm_dbg ("            message type: %u", message_type);
+    }
+
+    /* Number of fields */
+    SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + 8) / 8));
+    num_fields = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8);
+    OFFSETS_UPDATE (8);
+    mm_dbg ("            num fields: %u", num_fields);
+
+    /* Now, process actual text or data */
+    switch (message_encoding) {
+    case ENCODING_OCTET: {
+        GByteArray *data;
+        guint i;
+
+        SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8));
+
+        data = g_byte_array_sized_new (num_fields);
+        g_byte_array_set_size (data, num_fields);
+        for (i = 0; i < num_fields; i++) {
+            data->data[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8);
+            OFFSETS_UPDATE (8);
+        }
+
+        mm_dbg ("            data: (%u bytes)", num_fields);
+        mm_sms_part_take_data (sms_part, data);
+        break;
+    }
+
+    case ENCODING_ASCII_7BIT: {
+        gchar *text;
+        guint i;
+
+        SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 7)) / 8));
+
+        text = g_malloc (num_fields + 1);
+        for (i = 0; i < num_fields; i++) {
+            text[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 7);
+            OFFSETS_UPDATE (7);
+        }
+        text[i] = '\0';
+
+        mm_dbg ("            text: '%s'", text);
+        mm_sms_part_take_text (sms_part, text);
+        break;
+    }
+
+    case ENCODING_LATIN: {
+        gchar *latin;
+        gchar *text;
+        guint i;
+
+        SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_fields * 8)) / 8));
+
+        latin = g_malloc (num_fields + 1);
+        for (i = 0; i < num_fields; i++) {
+            latin[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8);
+            OFFSETS_UPDATE (8);
+        }
+        latin[i] = '\0';
+
+        text = g_convert (latin, -1, "UTF-8", "ISO−8859−1", NULL, NULL, NULL);
+        if (!text) {
+            mm_dbg ("            text/data: ignored (latin to UTF-8 conversion error)");
+        } else {
+            mm_dbg ("            text: '%s'", text);
+            mm_sms_part_take_text (sms_part, text);
+        }
+
+        g_free (latin);
+        break;
+    }
+
+    case ENCODING_UNICODE: {
+        gchar *utf16;
+        gchar *text;
+        guint i;
+        guint num_bytes;
+
+        /* 2 bytes per field! */
+        num_bytes = num_fields * 2;
+
+        SUBPARAMETER_SIZE_CHECK (byte_offset + 1 + ((bit_offset + (num_bytes * 8)) / 8));
+
+        utf16 = g_malloc (num_bytes);
+        for (i = 0; i < num_bytes; i++) {
+            utf16[i] = read_bits (&subparameter->parameter_value[byte_offset], bit_offset, 8);
+            OFFSETS_UPDATE (8);
+        }
+
+        text = g_convert (utf16, num_bytes, "UTF-8", "UCS-2BE", NULL, NULL, NULL);
+        if (!text) {
+            mm_dbg ("            text/data: ignored (UTF-16 to UTF-8 conversion error)");
+        } else {
+            mm_dbg ("            text: '%s'", text);
+            mm_sms_part_take_text (sms_part, text);
+        }
+
+        g_free (utf16);
+        break;
+    }
+
+    default:
+        mm_dbg ("            text/data: ignored (unsupported encoding)");
+    }
+
+#undef OFFSETS_UPDATE
+#undef SUBPARAMETER_SIZE_CHECK
+}
+
+static void
+read_bearer_data (MMSmsPart *sms_part,
+                  const struct Parameter *parameter)
+{
+    guint offset;
+
+#define PARAMETER_SIZE_CHECK(required_size)                             \
+    if (parameter->parameter_len < required_size) {                     \
+        mm_dbg ("        cannot read bearer data, need at least %u bytes (got %u)", \
+                required_size,                                          \
+                parameter->parameter_len);                              \
+        return;                                                         \
+    }
+
+    offset = 0;
+    while (offset < parameter->parameter_len) {
+        const struct Parameter *subparameter;
+
+        PARAMETER_SIZE_CHECK (offset + 2);
+        subparameter = (const struct Parameter *)&parameter->parameter_value[offset];
+        offset += 2;
+
+        PARAMETER_SIZE_CHECK (offset + subparameter->parameter_len);
+        offset += subparameter->parameter_len;
+
+        switch (subparameter->parameter_id) {
+        case SUBPARAMETER_ID_MESSAGE_ID:
+            mm_dbg ("        reading message ID...");
+            read_bearer_data_message_identifier (sms_part, subparameter);
+            break;
+        case SUBPARAMETER_ID_USER_DATA:
+            mm_dbg ("        reading user data...");
+            read_bearer_data_user_data (sms_part, subparameter);
+            break;
+        case SUBPARAMETER_ID_USER_RESPONSE_CODE:
+            mm_dbg ("        skipping user response code...");
+            break;
+        case SUBPARAMETER_ID_MESSAGE_CENTER_TIME_STAMP:
+            mm_dbg ("        skipping message center timestamp...");
+            break;
+        case SUBPARAMETER_ID_VALIDITY_PERIOD_ABSOLUTE:
+            mm_dbg ("        skipping absolute validity period...");
+            break;
+        case SUBPARAMETER_ID_VALIDITY_PERIOD_RELATIVE:
+            mm_dbg ("        skipping relative validity period...");
+            break;
+        case SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_ABSOLUTE:
+            mm_dbg ("        skipping absolute deferred delivery time...");
+            break;
+        case SUBPARAMETER_ID_DEFERRED_DELIVERY_TIME_RELATIVE:
+            mm_dbg ("        skipping relative deferred delivery time...");
+            break;
+        case SUBPARAMETER_ID_PRIORITY_INDICATOR:
+            mm_dbg ("        skipping priority indicator...");
+            break;
+        case SUBPARAMETER_ID_PRIVACY_INDICATOR:
+            mm_dbg ("        skipping privacy indicator...");
+            break;
+        case SUBPARAMETER_ID_REPLY_OPTION:
+            mm_dbg ("        skipping reply option...");
+            break;
+        case SUBPARAMETER_ID_NUMBER_OF_MESSAGES:
+            mm_dbg ("        skipping number of messages...");
+            break;
+        case SUBPARAMETER_ID_ALERT_ON_MESSAGE_DELIVERY:
+            mm_dbg ("        skipping alert on message delivery...");
+            break;
+        case SUBPARAMETER_ID_LANGUAGE_INDICATOR:
+            mm_dbg ("        skipping language indicator...");
+            break;
+        case SUBPARAMETER_ID_CALL_BACK_NUMBER:
+            mm_dbg ("        skipping call back number...");
+            break;
+        case SUBPARAMETER_ID_MESSAGE_DISPLAY_MODE:
+            mm_dbg ("        skipping message display mode...");
+            break;
+        case SUBPARAMETER_ID_MULTIPLE_ENCODING_USER_DATA:
+            mm_dbg ("        skipping multiple encoding user data...");
+            break;
+        case SUBPARAMETER_ID_MESSAGE_DEPOSIT_INDEX:
+            mm_dbg ("        skipping message deposit index...");
+            break;
+        case SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_DATA:
+            mm_dbg ("        skipping service category program data...");
+            break;
+        case SUBPARAMETER_ID_SERVICE_CATEGORY_PROGRAM_RESULT:
+            mm_dbg ("        skipping service category program result...");
+            break;
+        case SUBPARAMETER_ID_MESSAGE_STATUS:
+            mm_dbg ("        skipping message status...");
+            break;
+        case SUBPARAMETER_ID_TP_FAILURE_CAUSE:
+            mm_dbg ("        skipping TP failure case...");
+            break;
+        case SUBPARAMETER_ID_ENHANCED_VMN:
+            mm_dbg ("        skipping enhanced vmn...");
+            break;
+        case SUBPARAMETER_ID_ENHANCED_VMN_ACK:
+            mm_dbg ("        skipping enhanced vmn ack...");
+            break;
+        default:
+            mm_dbg ("    unknown subparameter found: '%u' (ignoring)",
+                    subparameter->parameter_id);
+            break;
+        }
+    }
+
+#undef PARAMETER_SIZE_CHECK
+}
+
+MMSmsPart *
+mm_sms_part_cdma_new_from_binary_pdu (guint index,
+                                      const guint8 *pdu,
+                                      gsize pdu_len,
+                                      GError **error)
+{
+    MMSmsPart *sms_part;
+    guint offset;
+    guint message_type;
+
+    /* Create the new MMSmsPart */
+    sms_part = mm_sms_part_new (index, MM_SMS_PDU_TYPE_UNKNOWN);
+
+    if (index != SMS_PART_INVALID_INDEX)
+        mm_dbg ("Parsing CDMA PDU (%u)...", index);
+    else
+        mm_dbg ("Parsing CDMA PDU...");
+
+#define PDU_SIZE_CHECK(required_size, check_descr_str)                 \
+    if (pdu_len < required_size) {                                     \
+        g_set_error (error,                                            \
+                     MM_CORE_ERROR,                                    \
+                     MM_CORE_ERROR_FAILED,                             \
+                     "CDMA PDU too short, %s: %" G_GSIZE_FORMAT " < %u",    \
+                     check_descr_str,                                  \
+                     pdu_len,                                          \
+                     required_size);                                   \
+        mm_sms_part_free (sms_part);                                   \
+        return NULL;                                                   \
+    }
+
+    offset = 0;
+
+    /* First byte: SMS message type */
+    PDU_SIZE_CHECK (offset + 1, "cannot read SMS message type");
+    message_type = pdu[offset++];
+    switch (message_type) {
+    case MESSAGE_TYPE_POINT_TO_POINT:
+    case MESSAGE_TYPE_BROADCAST:
+    case MESSAGE_TYPE_ACKNOWLEDGE:
+        break;
+    default:
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_FAILED,
+                     "Invalid SMS message type (%u)",
+                     message_type);
+        mm_sms_part_free (sms_part);
+        return NULL;
+    }
+
+    /* Now walk parameters one by one */
+    while (offset < pdu_len) {
+        const struct Parameter *parameter;
+
+        PDU_SIZE_CHECK (offset + 2, "cannot read parameter header");
+        parameter = (const struct Parameter *)&pdu[offset];
+        offset += 2;
+
+        PDU_SIZE_CHECK (offset + parameter->parameter_len, "cannot read parameter value");
+        offset += parameter->parameter_len;
+
+        switch (parameter->parameter_id) {
+        case PARAMETER_ID_TELESERVICE_ID:
+            mm_dbg ("    reading teleservice ID...");
+            read_teleservice_id (sms_part, parameter);
+            break;
+        case PARAMETER_ID_SERVICE_CATEGORY:
+            mm_dbg ("    reading service category...");
+            read_service_category (sms_part, parameter);
+            break;
+        case PARAMETER_ID_ORIGINATING_ADDRESS:
+            mm_dbg ("    reading originating address...");
+            if (mm_sms_part_get_number (sms_part))
+                mm_dbg ("        cannot read originating address; an address field was already read");
+            else
+                read_address (sms_part, parameter);
+            break;
+        case PARAMETER_ID_ORIGINATING_SUBADDRESS:
+            mm_dbg ("    skipping originating subaddress...");
+            break;
+        case PARAMETER_ID_DESTINATION_ADDRESS:
+            mm_dbg ("    reading destination address...");
+            if (mm_sms_part_get_number (sms_part))
+                mm_dbg ("        cannot read destination address; an address field was already read");
+            else
+                read_address (sms_part, parameter);
+            break;
+        case PARAMETER_ID_DESTINATION_SUBADDRESS:
+            mm_dbg ("    skipping destination subaddress...");
+            break;
+        case PARAMETER_ID_BEARER_REPLY_OPTION:
+            mm_dbg ("    reading bearer reply option...");
+            read_bearer_reply_option (sms_part, parameter);
+            break;
+        case PARAMETER_ID_CAUSE_CODES:
+            mm_dbg ("    reading cause codes...");
+            read_cause_codes (sms_part, parameter);
+            break;
+        case PARAMETER_ID_BEARER_DATA:
+            mm_dbg ("    reading bearer data...");
+            read_bearer_data (sms_part, parameter);
+            break;
+        default:
+            mm_dbg ("    unknown parameter found: '%u' (ignoring)",
+                    parameter->parameter_id);
+            break;
+        }
+    }
+
+    /* Check mandatory parameters */
+    switch (message_type) {
+    case MESSAGE_TYPE_POINT_TO_POINT:
+        if (mm_sms_part_get_cdma_teleservice_id (sms_part) == MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN)
+            mm_dbg ("    mandatory parameter missing: teleservice ID not found or invalid in point-to-point message");
+        break;
+    case MESSAGE_TYPE_BROADCAST:
+        if (mm_sms_part_get_cdma_service_category (sms_part) == MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN)
+            mm_dbg ("    mandatory parameter missing: service category not found or invalid in broadcast message");
+        break;
+    case MESSAGE_TYPE_ACKNOWLEDGE:
+        if (mm_sms_part_get_message_reference (sms_part) == 0)
+            mm_dbg ("    mandatory parameter missing: cause codes not found or invalid in acknowledge message");
+        break;
+    }
+
+#undef PDU_SIZE_CHECK
+
+    return sms_part;
+}
+
+/*****************************************************************************/
+/* Write bits; o_bits < 8; n_bits <= 8
+ *
+ * Byte 0            Byte 1
+ * [7|6|5|4|3|2|1|0] [7|6|5|4|3|2|1|0]
+ *
+ * o_bits+n_bits <= 16
+ *
+ * NOTE! The bits being set should be 0 initially.
+ */
+static void
+write_bits (guint8 *bytes,
+            guint8 o_bits,
+            guint8 n_bits,
+            guint8 bits)
+{
+    guint8 bits_in_first;
+    guint8 bits_in_second;
+
+    g_assert (o_bits < 8);
+    g_assert (n_bits <= 8);
+    g_assert (o_bits + n_bits <= 16);
+
+    /* Write only in the first byte */
+    if (o_bits + n_bits <= 8) {
+        bytes[0] |= (bits & ((1 << n_bits) - 1)) << (8 - o_bits - n_bits);
+        return;
+    }
+
+    /* Write (8 - o_bits) in the first byte and (n_bits - (8 - o_bits)) in the second byte */
+    bits_in_first = 8 - o_bits;
+    bits_in_second = n_bits - bits_in_first;
+
+    write_bits (&bytes[0], o_bits, bits_in_first, (bits >> bits_in_second));
+    write_bits (&bytes[1], 0, bits_in_second, bits);
+}
+
+/*****************************************************************************/
+
+static guint8
+dtmf_from_ascii (guint8 ascii)
+{
+    if (ascii >= '1' && ascii <= '9')
+        return ascii - '0';
+    if (ascii == '0')
+        return 10;
+    if (ascii == '*')
+        return 11;
+    if (ascii == '#')
+        return 12;
+
+    mm_dbg ("        invalid ascii digit in dtmf conversion: %c", ascii);
+    return 0;
+}
+
+static gboolean
+write_teleservice_id (MMSmsPart *part,
+                      guint8 *pdu,
+                      guint *absolute_offset,
+                      GError **error)
+{
+    guint16 aux16;
+
+    mm_dbg ("    writing teleservice ID...");
+
+    if (mm_sms_part_get_cdma_teleservice_id (part) != MM_SMS_CDMA_TELESERVICE_ID_WMT) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Teleservice '%s' not supported",
+                     mm_sms_cdma_teleservice_id_get_string (
+                         mm_sms_part_get_cdma_teleservice_id (part)));
+        return FALSE;
+    }
+
+    mm_dbg ("        teleservice ID: %s (%u)",
+            mm_sms_cdma_teleservice_id_get_string (MM_SMS_CDMA_TELESERVICE_ID_WMT),
+            MM_SMS_CDMA_TELESERVICE_ID_WMT);
+
+    /* Teleservice ID: WMT always */
+    pdu[0] = PARAMETER_ID_TELESERVICE_ID;
+    pdu[1] = 2; /* parameter_len, always 2 */
+    aux16 = GUINT16_TO_BE (MM_SMS_CDMA_TELESERVICE_ID_WMT);
+    memcpy (&pdu[2], &aux16, 2);
+
+    *absolute_offset += 4;
+    return TRUE;
+}
+
+static gboolean
+write_destination_address (MMSmsPart *part,
+                           guint8 *pdu,
+                           guint *absolute_offset,
+                           GError **error)
+{
+    const gchar *number;
+    guint bit_offset;
+    guint byte_offset;
+    guint n_digits;
+    guint i;
+
+    mm_dbg ("    writing destination address...");
+
+#define OFFSETS_UPDATE(n_bits) do { \
+        bit_offset += n_bits;       \
+        if (bit_offset >= 8) {      \
+            bit_offset-=8;          \
+            byte_offset++;          \
+        }                           \
+    } while (0)
+
+    number = mm_sms_part_get_number (part);
+    n_digits = strlen (number);
+
+    pdu[0] = PARAMETER_ID_DESTINATION_ADDRESS;
+    /* Write parameter length at the end */
+
+    byte_offset = 2;
+    bit_offset = 0;
+
+    /* Digit mode: DTMF always */
+    mm_dbg ("        digit mode: dtmf");
+    write_bits (&pdu[byte_offset], bit_offset, 1, DIGIT_MODE_DTMF);
+    OFFSETS_UPDATE (1);
+
+    /* Number mode: DIGIT always */
+    mm_dbg ("        number mode: digit");
+    write_bits (&pdu[byte_offset], bit_offset, 1, NUMBER_MODE_DIGIT);
+    OFFSETS_UPDATE (1);
+
+    /* Number type and numbering plan only needed in ASCII digit mode, so skip */
+
+    /* Number of fields */
+    if (n_digits > 256) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Number too long (max 256 digits, %u given)",
+                     n_digits);
+        return FALSE;
+    }
+    mm_dbg ("        num fields: %u", n_digits);
+    write_bits (&pdu[byte_offset], bit_offset, 8, n_digits);
+    OFFSETS_UPDATE (8);
+
+    /* Actual DTMF encoded number */
+    mm_dbg ("        address: %s", number);
+    for (i = 0; i < n_digits; i++) {
+        guint8 dtmf;
+
+        dtmf = dtmf_from_ascii (number[i]);
+        if (!dtmf) {
+            g_set_error (error,
+                         MM_CORE_ERROR,
+                         MM_CORE_ERROR_UNSUPPORTED,
+                         "Unsupported character in number: '%c'. Cannot convert to DTMF",
+                         number[i]);
+            return FALSE;
+        }
+        write_bits (&pdu[byte_offset], bit_offset, 4, dtmf);
+        OFFSETS_UPDATE (4);
+    }
+
+#undef OFFSETS_UPDATE
+
+    /* Write parameter length (remove header length to offset) */
+    byte_offset += !!bit_offset - 2;
+    if (byte_offset > 256) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Number too long (max 256 bytes, %u given)",
+                     byte_offset);
+        return FALSE;
+    }
+    pdu[1] = byte_offset;
+
+    *absolute_offset += (2 + pdu[1]);
+    return TRUE;
+}
+
+static gboolean
+write_bearer_data_message_identifier (MMSmsPart *part,
+                                      guint8 *pdu,
+                                      guint *parameter_offset,
+                                      GError **error)
+{
+    pdu[0] = SUBPARAMETER_ID_MESSAGE_ID;
+    pdu[1] = 3; /* subparameter_len, always 3 */
+
+    mm_dbg ("        writing message identifier: submit");
+
+    /* Message type */
+    write_bits (&pdu[2], 0, 4, TELESERVICE_MESSAGE_TYPE_SUBMIT);
+
+    /* Skip adding a message id; assume it's filled in by device */
+
+    /* And no need for a header ind value, always false */
+
+    *parameter_offset += 5;
+    return TRUE;
+}
+
+static void
+decide_best_encoding (const gchar *text,
+                      GByteArray **out,
+                      guint *num_fields,
+                      guint *num_bits_per_field,
+                      Encoding *encoding)
+{
+    guint latin_unsupported = 0;
+    guint ascii_unsupported = 0;
+    guint i;
+    guint len;
+
+    len = strlen (text);
+
+    /* Check if we can do ASCII-7 */
+    for (i = 0; i < len; i++) {
+        if (text[i] & 0x80) {
+            ascii_unsupported++;
+            break;
+        }
+    }
+
+    /* If ASCII-7 already supported, done we are */
+    if (!ascii_unsupported) {
+        *out = g_byte_array_sized_new (len);
+        g_byte_array_append (*out, (const guint8 *)text, len);
+        *num_fields = len;
+        *num_bits_per_field = 7;
+        *encoding = ENCODING_ASCII_7BIT;
+        return;
+    }
+
+    /* Check if we can do Latin encoding */
+    mm_charset_get_encoded_len (text,
+                                MM_MODEM_CHARSET_8859_1,
+                                &latin_unsupported);
+    if (!latin_unsupported) {
+        *out = g_byte_array_sized_new (len);
+        mm_modem_charset_byte_array_append (*out,
+                                            text,
+                                            FALSE,
+                                            MM_MODEM_CHARSET_8859_1);
+        *num_fields = (*out)->len;
+        *num_bits_per_field = 8;
+        *encoding = ENCODING_LATIN;
+        return;
+    }
+
+    /* If no Latin and no ASCII, default to UTF-16 */
+    *out = g_byte_array_sized_new (len * 2);
+    mm_modem_charset_byte_array_append (*out,
+                                        text,
+                                        FALSE,
+                                        MM_MODEM_CHARSET_UCS2);
+    *num_fields = (*out)->len / 2;
+    *num_bits_per_field = 16;
+    *encoding = ENCODING_UNICODE;
+}
+
+static gboolean
+write_bearer_data_user_data (MMSmsPart *part,
+                             guint8 *pdu,
+                             guint *parameter_offset,
+                             GError **error)
+{
+    const gchar *text;
+    const GByteArray *data;
+    guint bit_offset = 0;
+    guint byte_offset = 0;
+    guint num_fields;
+    guint num_bits_per_field;
+    guint i;
+    Encoding encoding;
+    GByteArray *converted = NULL;
+    const GByteArray *aux;
+    guint num_bits_per_iter;
+
+    mm_dbg ("        writing user data...");
+
+#define OFFSETS_UPDATE(n_bits) do { \
+        bit_offset += n_bits;       \
+        if (bit_offset >= 8) {      \
+            bit_offset-=8;          \
+            byte_offset++;          \
+        }                           \
+    } while (0)
+
+    text = mm_sms_part_get_text (part);
+    data = mm_sms_part_get_data (part);
+    g_assert (text || data);
+    g_assert (!(!text && !data));
+
+    pdu[0] = SUBPARAMETER_ID_USER_DATA;
+    /* Write parameter length at the end */
+    byte_offset = 2;
+    bit_offset = 0;
+
+    /* Text or Data */
+    if (text) {
+        decide_best_encoding (text,
+                              &converted,
+                              &num_fields,
+                              &num_bits_per_field,
+                              &encoding);
+        aux = (const GByteArray *)converted;
+    } else {
+        aux = data;
+        num_fields = data->len;
+        num_bits_per_field = 8;
+        encoding = ENCODING_OCTET;
+    }
+
+    /* Message encoding*/
+    mm_dbg ("            message encoding: %s", encoding_to_string (encoding));
+    write_bits (&pdu[byte_offset], bit_offset, 5, encoding);
+    OFFSETS_UPDATE (5);
+
+    /* Number of fields */
+    if (num_fields > 256) {
+        if (converted)
+            g_byte_array_unref (converted);
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Data too long (max 256 fields, %u given)",
+                     num_fields);
+        return FALSE;
+    }
+    mm_dbg ("            num fields: %u", num_fields);
+    write_bits (&pdu[byte_offset], bit_offset, 8, num_fields);
+    OFFSETS_UPDATE (8);
+
+    /* For ASCII-7, write 7 bits in each iteration; for the remaining ones
+     * go byte per byte */
+    if (text)
+        mm_dbg ("            text: '%s'", text);
+    else
+        mm_dbg ("            data: (%u bytes)", num_fields);
+    num_bits_per_iter = num_bits_per_field < 8 ? num_bits_per_field : 8;
+    for (i = 0; i < aux->len; i++) {
+        write_bits (&pdu[byte_offset], bit_offset, num_bits_per_iter, aux->data[i]);
+        OFFSETS_UPDATE (num_bits_per_iter);
+    }
+
+    if (converted)
+        g_byte_array_unref (converted);
+
+#undef OFFSETS_UPDATE
+
+    /* Write subparameter length (remove header length to offset) */
+    byte_offset += !!bit_offset - 2;
+    if (byte_offset > 256) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Data or Text too long (max 256 bytes, %u given)",
+                     byte_offset);
+        return FALSE;
+    }
+    pdu[1] = byte_offset;
+
+    *parameter_offset += (2 + pdu[1]);
+    return TRUE;
+}
+
+static gboolean
+write_bearer_data (MMSmsPart *part,
+                   guint8 *pdu,
+                   guint *absolute_offset,
+                   GError **error)
+{
+    GError *inner_error = NULL;
+    guint offset = 0;
+
+    mm_dbg ("    writing bearer data...");
+
+    pdu[0] = PARAMETER_ID_BEARER_DATA;
+    /* Write parameter length at the end */
+
+    offset = 2;
+    if (!write_bearer_data_message_identifier (part, &pdu[offset], &offset, &inner_error))
+        mm_dbg ("Error writing message identifier: %s", inner_error->message);
+    else if (!write_bearer_data_user_data (part, &pdu[offset], &offset, &inner_error))
+        mm_dbg ("Error writing user data: %s", inner_error->message);
+
+    if (inner_error) {
+        g_propagate_error (error, inner_error);
+        g_prefix_error (error, "Error writing bearer data: ");
+        return FALSE;
+    }
+
+    /* Write parameter length (remove header length to offset) */
+    offset -= 2;
+    if (offset > 256) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Bearer data too long (max 256 bytes, %u given)",
+                     offset);
+        return FALSE;
+    }
+    pdu[1] = offset;
+
+    *absolute_offset += (2 + pdu[1]);
+    return TRUE;
+}
+
+guint8 *
+mm_sms_part_cdma_get_submit_pdu (MMSmsPart *part,
+                                 guint *out_pdulen,
+                                 GError **error)
+{
+    GError *inner_error = NULL;
+    guint offset = 0;
+    guint8 *pdu;
+
+    g_return_val_if_fail (mm_sms_part_get_number (part) != NULL, NULL);
+    g_return_val_if_fail (mm_sms_part_get_text (part) != NULL || mm_sms_part_get_data (part) != NULL, NULL);
+
+    if (mm_sms_part_get_pdu_type (part) != MM_SMS_PDU_TYPE_CDMA_SUBMIT) {
+        g_set_error (error,
+                     MM_MESSAGE_ERROR,
+                     MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
+                     "Invalid PDU type to generate a 'submit' PDU: '%s'",
+                     mm_sms_pdu_type_get_string (mm_sms_part_get_pdu_type (part)));
+        return NULL;
+    }
+
+    mm_dbg ("Creating PDU for part...");
+
+    /* Current max size estimations:
+     *  Message type: 1 byte
+     *  Teleservice ID: 5 bytes
+     *  Destination address: 2 + 256 bytes
+     *  Bearer data: 2 + 256 bytes
+     */
+    pdu = g_malloc0 (1024);
+
+    /* First byte: SMS message type */
+    pdu[offset++] = MESSAGE_TYPE_POINT_TO_POINT;
+
+    if (!write_teleservice_id (part, &pdu[offset], &offset, &inner_error))
+        mm_dbg ("Error writing Teleservice ID: %s", inner_error->message);
+    else if (!write_destination_address (part, &pdu[offset], &offset, &inner_error))
+        mm_dbg ("Error writing destination address: %s", inner_error->message);
+    else if (!write_bearer_data (part, &pdu[offset], &offset, &inner_error))
+        mm_dbg ("Error writing bearer data: %s", inner_error->message);
+
+    if (inner_error) {
+        g_propagate_error (error, inner_error);
+        g_prefix_error (error, "Cannot create CDMA SMS part: ");
+        g_free (pdu);
+        return NULL;
+    }
+
+    *out_pdulen = offset;
+    return pdu;
+}
diff --git a/src/mm-sms-part-cdma.h b/src/mm-sms-part-cdma.h
new file mode 100644
index 0000000..14c2c1d
--- /dev/null
+++ b/src/mm-sms-part-cdma.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2013 Google, Inc.
+ */
+
+#ifndef MM_SMS_PART_CDMA_H
+#define MM_SMS_PART_CDMA_H
+
+#include <glib.h>
+#include <ModemManager-enums.h>
+
+#include "mm-sms-part.h"
+
+MMSmsPart *mm_sms_part_cdma_new_from_pdu  (guint index,
+                                           const gchar *hexpdu,
+                                           GError **error);
+
+MMSmsPart *mm_sms_part_cdma_new_from_binary_pdu (guint index,
+                                                 const guint8 *pdu,
+                                                 gsize pdu_len,
+                                                 GError **error);
+
+guint8 *mm_sms_part_cdma_get_submit_pdu (MMSmsPart *part,
+                                         guint *out_pdulen,
+                                         GError **error);
+
+#endif /* MM_SMS_PART_CDMA_H */
diff --git a/src/mm-sms-part.c b/src/mm-sms-part.c
index 77fa3b8..9aafcc3 100644
--- a/src/mm-sms-part.c
+++ b/src/mm-sms-part.c
@@ -27,294 +27,6 @@
 #include "mm-charsets.h"
 #include "mm-log.h"
 
-#define PDU_SIZE 200
-
-#define SMS_TP_MTI_MASK               0x03
-#define  SMS_TP_MTI_SMS_DELIVER       0x00
-#define  SMS_TP_MTI_SMS_SUBMIT        0x01
-#define  SMS_TP_MTI_SMS_STATUS_REPORT 0x02
-
-#define SMS_NUMBER_TYPE_MASK          0x70
-#define SMS_NUMBER_TYPE_UNKNOWN       0x00
-#define SMS_NUMBER_TYPE_INTL          0x10
-#define SMS_NUMBER_TYPE_ALPHA         0x50
-
-#define SMS_NUMBER_PLAN_MASK          0x0f
-#define SMS_NUMBER_PLAN_TELEPHONE     0x01
-
-#define SMS_TP_MMS                    0x04
-#define SMS_TP_SRI                    0x20
-#define SMS_TP_UDHI                   0x40
-#define SMS_TP_RP                     0x80
-
-#define SMS_DCS_CODING_MASK           0xec
-#define  SMS_DCS_CODING_DEFAULT       0x00
-#define  SMS_DCS_CODING_8BIT          0x04
-#define  SMS_DCS_CODING_UCS2          0x08
-
-#define SMS_DCS_CLASS_VALID           0x10
-#define SMS_DCS_CLASS_MASK            0x03
-
-#define SMS_TIMESTAMP_LEN 7
-#define SMS_MIN_PDU_LEN (7 + SMS_TIMESTAMP_LEN)
-
-static char sms_bcd_chars[] = "0123456789*#abc\0\0";
-
-static void
-sms_semi_octets_to_bcd_string (char *dest, const guint8 *octets, int num_octets)
-{
-    int i;
-
-    for (i = 0 ; i < num_octets; i++) {
-        *dest++ = sms_bcd_chars[octets[i] & 0xf];
-        *dest++ = sms_bcd_chars[(octets[i] >> 4) & 0xf];
-    }
-    *dest++ = '\0';
-}
-
-static gboolean
-char_to_bcd (char in, guint8 *out)
-{
-    guint32 z;
-
-    if (isdigit (in)) {
-        *out = in - 0x30;
-        return TRUE;
-    }
-
-    for (z = 10; z < 16; z++) {
-        if (in == sms_bcd_chars[z]) {
-            *out = z;
-            return TRUE;
-        }
-    }
-    return FALSE;
-}
-
-static gsize
-sms_string_to_bcd_semi_octets (guint8 *buf, gsize buflen, const char *string)
-{
-    guint i;
-    guint8 bcd;
-    gsize addrlen, slen;
-
-    addrlen = slen = strlen (string);
-    if (addrlen % 2)
-        addrlen++;
-    g_return_val_if_fail (buflen >= addrlen, 0);
-
-    for (i = 0; i < addrlen; i += 2) {
-        if (!char_to_bcd (string[i], &bcd))
-            return 0;
-        buf[i / 2] = bcd & 0xF;
-
-        if (i >= slen - 1) {
-            /* PDU address gets padded with 0xF if string is odd length */
-            bcd = 0xF;
-        } else if (!char_to_bcd (string[i + 1], &bcd))
-            return 0;
-        buf[i / 2] |= bcd << 4;
-    }
-    return addrlen / 2;
-}
-
-/* len is in semi-octets */
-static char *
-sms_decode_address (const guint8 *address, int len)
-{
-    guint8 addrtype, addrplan;
-    char *utf8;
-
-    addrtype = address[0] & SMS_NUMBER_TYPE_MASK;
-    addrplan = address[0] & SMS_NUMBER_PLAN_MASK;
-    address++;
-
-    if (addrtype == SMS_NUMBER_TYPE_ALPHA) {
-        guint8 *unpacked;
-        guint32 unpacked_len;
-        unpacked = gsm_unpack (address, (len * 4) / 7, 0, &unpacked_len);
-        utf8 = (char *)mm_charset_gsm_unpacked_to_utf8 (unpacked,
-                                                        unpacked_len);
-        g_free(unpacked);
-    } else if (addrtype == SMS_NUMBER_TYPE_INTL &&
-               addrplan == SMS_NUMBER_PLAN_TELEPHONE) {
-        /* International telphone number, format as "+1234567890" */
-        utf8 = g_malloc (len + 3); /* '+' + digits + possible trailing 0xf + NUL */
-        utf8[0] = '+';
-        sms_semi_octets_to_bcd_string (utf8 + 1, address, (len + 1) / 2);
-    } else {
-        /*
-         * All non-alphanumeric types and plans are just digits, but
-         * don't apply any special formatting if we don't know the
-         * format.
-         */
-        utf8 = g_malloc (len + 2); /* digits + possible trailing 0xf + NUL */
-        sms_semi_octets_to_bcd_string (utf8, address, (len + 1) / 2);
-    }
-
-    return utf8;
-}
-
-static char *
-sms_decode_timestamp (const guint8 *timestamp)
-{
-    /* YYMMDDHHMMSS+ZZ */
-    char *timestr;
-    int quarters, hours;
-
-    timestr = g_malloc0 (16);
-    sms_semi_octets_to_bcd_string (timestr, timestamp, 6);
-    quarters = ((timestamp[6] & 0x7) * 10) + ((timestamp[6] >> 4) & 0xf);
-    hours = quarters / 4;
-    if (timestamp[6] & 0x08)
-        timestr[12] = '-';
-    else
-        timestr[12] = '+';
-    timestr[13] = (hours / 10) + '0';
-    timestr[14] = (hours % 10) + '0';
-    /* TODO(njw): Change timestamp rep to something that includes quarter-hours */
-    return timestr;
-}
-
-static MMSmsEncoding
-sms_encoding_type (int dcs)
-{
-    MMSmsEncoding scheme = MM_SMS_ENCODING_UNKNOWN;
-
-    switch ((dcs >> 4) & 0xf) {
-        /* General data coding group */
-        case 0: case 1:
-        case 2: case 3:
-            switch (dcs & 0x0c) {
-                case 0x08:
-                    scheme = MM_SMS_ENCODING_UCS2;
-                    break;
-                case 0x00:
-                    /* fallthrough */
-                    /* reserved - spec says to treat it as default alphabet */
-                case 0x0c:
-                    scheme = MM_SMS_ENCODING_GSM7;
-                    break;
-                case 0x04:
-                    scheme = MM_SMS_ENCODING_8BIT;
-                    break;
-            }
-            break;
-
-            /* Message waiting group (default alphabet) */
-        case 0xc:
-        case 0xd:
-            scheme = MM_SMS_ENCODING_GSM7;
-            break;
-
-            /* Message waiting group (UCS2 alphabet) */
-        case 0xe:
-            scheme = MM_SMS_ENCODING_UCS2;
-            break;
-
-            /* Data coding/message class group */
-        case 0xf:
-            switch (dcs & 0x04) {
-                case 0x00:
-                    scheme = MM_SMS_ENCODING_GSM7;
-                    break;
-                case 0x04:
-                    scheme = MM_SMS_ENCODING_8BIT;
-                    break;
-            }
-            break;
-
-            /* Reserved coding group values - spec says to treat it as default alphabet */
-        default:
-            scheme = MM_SMS_ENCODING_GSM7;
-            break;
-    }
-
-    return scheme;
-}
-
-static char *
-sms_decode_text (const guint8 *text, int len, MMSmsEncoding encoding, int bit_offset)
-{
-    char *utf8;
-    guint8 *unpacked;
-    guint32 unpacked_len;
-
-    if (encoding == MM_SMS_ENCODING_GSM7) {
-        mm_dbg ("Converting SMS part text from GSM7 to UTF8...");
-        unpacked = gsm_unpack ((const guint8 *) text, len, bit_offset, &unpacked_len);
-        utf8 = (char *) mm_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len);
-        mm_dbg ("   Got UTF-8 text: '%s'", utf8);
-        g_free (unpacked);
-    } else if (encoding == MM_SMS_ENCODING_UCS2) {
-        mm_dbg ("Converting SMS part text from UCS-2BE to UTF8...");
-        utf8 = g_convert ((char *) text, len, "UTF8", "UCS-2BE", NULL, NULL, NULL);
-        mm_dbg ("   Got UTF-8 text: '%s'", utf8);
-    } else {
-        g_warn_if_reached ();
-        utf8 = g_strdup ("");
-    }
-
-    return utf8;
-}
-
-static guint
-relative_to_validity (guint8 relative)
-{
-    if (relative <= 143)
-        return (relative + 1) * 5;
-
-    if (relative <= 167)
-        return 720 + (relative - 143) * 30;
-
-    return (relative - 166) * 1440;
-}
-
-static guint8
-validity_to_relative (guint validity)
-{
-    if (validity == 0)
-        return 167; /* 24 hours */
-
-    if (validity <= 720) {
-        /* 5 minute units up to 12 hours */
-        if (validity % 5)
-            validity += 5;
-        return (validity / 5) - 1;
-    }
-
-    if (validity > 720 && validity <= 1440) {
-        /* 12 hours + 30 minute units up to 1 day */
-        if (validity % 30)
-            validity += 30;  /* round up to next 30 minutes */
-        validity = MIN (validity, 1440);
-        return 143 + ((validity - 720) / 30);
-    }
-
-    if (validity > 1440 && validity <= 43200) {
-        /* 2 days up to 1 month */
-        if (validity % 1440)
-            validity += 1440;  /* round up to next day */
-        validity = MIN (validity, 43200);
-        return 167 + ((validity - 1440) / 1440);
-    }
-
-    /* 43200 = 30 days in minutes
-     * 10080 = 7 days in minutes
-     * 635040 = 63 weeks in minutes
-     * 40320 = 4 weeks in minutes
-     */
-    if (validity > 43200 && validity <= 635040) {
-        /* 5 weeks up to 63 weeks */
-        if (validity % 10080)
-            validity += 10080;  /* round up to next week */
-        validity = MIN (validity, 635040);
-        return 196 + ((validity - 40320) / 10080);
-    }
-
-    return 255; /* 63 weeks */
-}
-
 struct _MMSmsPart {
     guint index;
     MMSmsPduType pdu_type;
@@ -336,6 +48,10 @@
     guint concat_reference;
     guint concat_max;
     guint concat_sequence;
+
+    /* CDMA specific */
+    MMSmsCdmaTeleserviceId cdma_teleservice_id;
+    MMSmsCdmaServiceCategory cdma_service_category;
 };
 
 void
@@ -450,6 +166,11 @@
     return self->should_concat;
 }
 
+PART_GET_FUNC (MMSmsCdmaTeleserviceId, cdma_teleservice_id)
+PART_SET_FUNC (MMSmsCdmaTeleserviceId, cdma_teleservice_id)
+PART_GET_FUNC (MMSmsCdmaServiceCategory, cdma_service_category)
+PART_SET_FUNC (MMSmsCdmaServiceCategory, cdma_service_category)
+
 MMSmsPart *
 mm_sms_part_new (guint index,
                  MMSmsPduType pdu_type)
@@ -461,843 +182,9 @@
     sms_part->pdu_type = pdu_type;
     sms_part->encoding = MM_SMS_ENCODING_UNKNOWN;
     sms_part->delivery_state = MM_SMS_DELIVERY_STATE_UNKNOWN;
+    sms_part->cdma_teleservice_id = MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN;
+    sms_part->cdma_service_category = MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN;
     sms_part->class = -1;
 
     return sms_part;
 }
-
-MMSmsPart *
-mm_sms_part_new_from_pdu (guint index,
-                          const gchar *hexpdu,
-                          GError **error)
-{
-    gsize pdu_len;
-    guint8 *pdu;
-    MMSmsPart *part;
-
-    /* Convert PDU from hex to binary */
-    pdu = (guint8 *) mm_utils_hexstr2bin (hexpdu, &pdu_len);
-    if (!pdu) {
-        g_set_error_literal (error,
-                             MM_CORE_ERROR,
-                             MM_CORE_ERROR_FAILED,
-                             "Couldn't convert PDU from hex to binary");
-        return NULL;
-    }
-
-    part = mm_sms_part_new_from_binary_pdu (index, pdu, pdu_len, error);
-    g_free (pdu);
-
-    return part;
-}
-
-MMSmsPart *
-mm_sms_part_new_from_binary_pdu (guint index,
-                                 const guint8 *pdu,
-                                 gsize pdu_len,
-                                 GError **error)
-{
-    MMSmsPart *sms_part;
-    guint8 pdu_type;
-    guint offset;
-    guint smsc_addr_size_bytes;
-    guint tp_addr_size_digits;
-    guint tp_addr_size_bytes;
-    guint8 validity_format = 0;
-    gboolean has_udh = FALSE;
-    /* The following offsets are OPTIONAL, as STATUS REPORTs may not have
-     * them; we use '0' to indicate their absence */
-    guint tp_pid_offset = 0;
-    guint tp_dcs_offset = 0;
-    guint tp_user_data_len_offset = 0;
-    MMSmsEncoding user_data_encoding = MM_SMS_ENCODING_UNKNOWN;
-
-    /* Create the new MMSmsPart */
-    sms_part = mm_sms_part_new (index, MM_SMS_PDU_TYPE_UNKNOWN);
-
-    if (index != SMS_PART_INVALID_INDEX)
-        mm_dbg ("Parsing PDU (%u)...", index);
-    else
-        mm_dbg ("Parsing PDU...");
-
-#define PDU_SIZE_CHECK(required_size, check_descr_str)                 \
-    if (pdu_len < required_size) {                                     \
-        g_set_error (error,                                            \
-                     MM_CORE_ERROR,                                    \
-                     MM_CORE_ERROR_FAILED,                             \
-                     "PDU too short, %s: %" G_GSIZE_FORMAT " < %u",    \
-                     check_descr_str,                                  \
-                     pdu_len,                                          \
-                     required_size);                                   \
-        mm_sms_part_free (sms_part);                                   \
-        return NULL;                                                   \
-    }
-
-    offset = 0;
-
-    /* ---------------------------------------------------------------------- */
-    /* SMSC, in address format, precedes the TPDU
-     * First byte represents the number of BYTES for the address value */
-    PDU_SIZE_CHECK (1, "cannot read SMSC address length");
-    smsc_addr_size_bytes = pdu[offset++];
-    if (smsc_addr_size_bytes > 0) {
-        PDU_SIZE_CHECK (offset + smsc_addr_size_bytes, "cannot read SMSC address");
-        /* SMSC may not be given in DELIVER PDUs */
-        mm_sms_part_take_smsc (sms_part,
-                               sms_decode_address (&pdu[1], 2 * (smsc_addr_size_bytes - 1)));
-        mm_dbg ("  SMSC address parsed: '%s'", mm_sms_part_get_smsc (sms_part));
-        offset += smsc_addr_size_bytes;
-    } else
-        mm_dbg ("  No SMSC address given");
-
-
-    /* ---------------------------------------------------------------------- */
-    /* TP-MTI (1 byte) */
-    PDU_SIZE_CHECK (offset + 1, "cannot read TP-MTI");
-
-    pdu_type = (pdu[offset] & SMS_TP_MTI_MASK);
-    switch (pdu_type) {
-    case SMS_TP_MTI_SMS_DELIVER:
-        mm_dbg ("  Deliver type PDU detected");
-        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_DELIVER);
-        break;
-    case SMS_TP_MTI_SMS_SUBMIT:
-        mm_dbg ("  Submit type PDU detected");
-        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_SUBMIT);
-        break;
-    case SMS_TP_MTI_SMS_STATUS_REPORT:
-        mm_dbg ("  Status report type PDU detected");
-        mm_sms_part_set_pdu_type (sms_part, MM_SMS_PDU_TYPE_STATUS_REPORT);
-        break;
-    default:
-        mm_sms_part_free (sms_part);
-        g_set_error (error,
-                     MM_CORE_ERROR,
-                     MM_CORE_ERROR_FAILED,
-                     "Unhandled message type: 0x%02x",
-                     pdu_type);
-        return NULL;
-    }
-
-    /* Delivery report was requested? */
-    if (pdu[offset] & 0x20)
-        mm_sms_part_set_delivery_report_request (sms_part, TRUE);
-
-    /* PDU with validity? (only in SUBMIT PDUs) */
-    if (pdu_type == SMS_TP_MTI_SMS_SUBMIT)
-        validity_format = pdu[offset] & 0x18;
-
-    /* PDU with user data header? */
-    if (pdu[offset] & 0x40)
-        has_udh = TRUE;
-
-    offset++;
-
-    /* ---------------------------------------------------------------------- */
-    /* TP-MR (1 byte, in STATUS_REPORT and SUBMIT PDUs */
-    if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT ||
-        pdu_type == SMS_TP_MTI_SMS_SUBMIT) {
-        PDU_SIZE_CHECK (offset + 1, "cannot read message reference");
-
-        mm_dbg ("  message reference: %u", (guint)pdu[offset]);
-        mm_sms_part_set_message_reference (sms_part, pdu[offset]);
-        offset++;
-    }
-
-
-    /* ---------------------------------------------------------------------- */
-    /* TP-DA or TP-OA or TP-RA
-     * First byte represents the number of DIGITS in the number.
-     * Round the sender address length up to an even number of
-     * semi-octets, and thus an integral number of octets.
-     */
-    PDU_SIZE_CHECK (offset + 1, "cannot read number of digits in number");
-    tp_addr_size_digits = pdu[offset++];
-    tp_addr_size_bytes = (tp_addr_size_digits + 1) >> 1;
-
-    PDU_SIZE_CHECK (offset + tp_addr_size_bytes, "cannot read number");
-    mm_sms_part_take_number (sms_part,
-                             sms_decode_address (&pdu[offset],
-                                                 tp_addr_size_digits));
-    mm_dbg ("  Number parsed: '%s'", mm_sms_part_get_number (sms_part));
-    offset += (1 + tp_addr_size_bytes); /* +1 due to the Type of Address byte */
-
-    /* ---------------------------------------------------------------------- */
-    /* Get timestamps and indexes for TP-PID, TP-DCS and TP-UDL/TP-UD */
-
-    if (pdu_type == SMS_TP_MTI_SMS_DELIVER) {
-        PDU_SIZE_CHECK (offset + 9,
-                        "cannot read PID/DCS/Timestamp"); /* 1+1+7=9 */
-
-        /* ------ TP-PID (1 byte) ------ */
-        tp_pid_offset = offset++;
-
-        /* ------ TP-DCS (1 byte) ------ */
-        tp_dcs_offset = offset++;
-
-        /* ------ Timestamp (7 bytes) ------ */
-        mm_sms_part_take_timestamp (sms_part,
-                                    sms_decode_timestamp (&pdu[offset]));
-        offset += 7;
-
-        tp_user_data_len_offset = offset;
-    } else if (pdu_type == SMS_TP_MTI_SMS_SUBMIT) {
-        PDU_SIZE_CHECK (offset + 2 + !!validity_format,
-                        "cannot read PID/DCS/Validity"); /* 1+1=2 */
-
-        /* ------ TP-PID (1 byte) ------ */
-        tp_pid_offset = offset++;
-
-        /* ------ TP-DCS (1 byte) ------ */
-        tp_dcs_offset = offset++;
-
-        /* ----------- TP-Validity-Period (1 byte) ----------- */
-        if (validity_format) {
-            switch (validity_format) {
-            case 0x10:
-                mm_dbg ("  validity available, format relative");
-                mm_sms_part_set_validity_relative (sms_part,
-                                                   relative_to_validity (pdu[offset]));
-                offset++;
-                break;
-            case 0x08:
-                /* TODO: support enhanced format; GSM 03.40 */
-                mm_dbg ("  validity available, format enhanced (not implemented)");
-                /* 7 bytes for enhanced validity */
-                offset += 7;
-                break;
-            case 0x18:
-                /* TODO: support absolute format; GSM 03.40 */
-                mm_dbg ("  validity available, format absolute (not implemented)");
-                /* 7 bytes for absolute validity */
-                offset += 7;
-                break;
-            default:
-                /* Cannot happen as we AND with the 0x18 mask */
-                g_assert_not_reached();
-            }
-        }
-
-        tp_user_data_len_offset = offset;
-    }
-    else if (pdu_type == SMS_TP_MTI_SMS_STATUS_REPORT) {
-        /* We have 2 timestamps in status report PDUs:
-         *  first, the timestamp for when the PDU was received in the SMSC
-         *  second, the timestamp for when the PDU was forwarded by the SMSC
-         */
-        PDU_SIZE_CHECK (offset + 15, "cannot read Timestamps/TP-STATUS"); /* 7+7+1=15 */
-
-        /* ------ Timestamp (7 bytes) ------ */
-        mm_sms_part_take_timestamp (sms_part,
-                                    sms_decode_timestamp (&pdu[offset]));
-        offset += 7;
-
-        /* ------ Discharge Timestamp (7 bytes) ------ */
-        mm_sms_part_take_discharge_timestamp (sms_part,
-                                              sms_decode_timestamp (&pdu[offset]));
-        offset += 7;
-
-        /* ----- TP-STATUS (1 byte) ------ */
-        mm_dbg ("  delivery state: %u", (guint)pdu[offset]);
-        mm_sms_part_set_delivery_state (sms_part, pdu[offset]);
-        offset++;
-
-        /* ------ TP-PI (1 byte) OPTIONAL ------ */
-        if (offset < pdu_len) {
-            guint next_optional_field_offset = offset + 1;
-
-            /* TP-PID? */
-            if (pdu[offset] & 0x01)
-                tp_pid_offset = next_optional_field_offset++;
-
-            /* TP-DCS? */
-            if (pdu[offset] & 0x02)
-                tp_dcs_offset = next_optional_field_offset++;
-
-            /* TP-UserData? */
-            if (pdu[offset] & 0x04)
-                tp_user_data_len_offset = next_optional_field_offset;
-        }
-    } else
-        g_assert_not_reached ();
-
-    if (tp_pid_offset > 0) {
-        PDU_SIZE_CHECK (tp_pid_offset + 1, "cannot read TP-PID");
-        mm_dbg ("  PID: %u", (guint)pdu[tp_pid_offset]);
-    }
-
-    /* Grab user data encoding and message class */
-    if (tp_dcs_offset > 0) {
-        PDU_SIZE_CHECK (tp_dcs_offset + 1, "cannot read TP-DCS");
-
-        /* Encoding given in the 'alphabet' bits */
-        user_data_encoding = sms_encoding_type(pdu[tp_dcs_offset]);
-        switch (user_data_encoding) {
-        case MM_SMS_ENCODING_GSM7:
-            mm_dbg ("  user data encoding is GSM7");
-            break;
-        case MM_SMS_ENCODING_UCS2:
-            mm_dbg ("  user data encoding is UCS2");
-            break;
-        case MM_SMS_ENCODING_8BIT:
-            mm_dbg ("  user data encoding is 8bit");
-            break;
-        default:
-            mm_dbg ("  user data encoding is unknown");
-            break;
-        }
-        mm_sms_part_set_encoding (sms_part, user_data_encoding);
-
-        /* Class */
-        if (pdu[tp_dcs_offset] & SMS_DCS_CLASS_VALID)
-            mm_sms_part_set_class (sms_part,
-                                   pdu[tp_dcs_offset] & SMS_DCS_CLASS_MASK);
-    }
-
-    if (tp_user_data_len_offset > 0) {
-        guint tp_user_data_size_elements;
-        guint tp_user_data_size_bytes;
-        guint tp_user_data_offset;
-        guint bit_offset;
-
-        PDU_SIZE_CHECK (tp_user_data_len_offset + 1, "cannot read TP-UDL");
-        tp_user_data_size_elements = pdu[tp_user_data_len_offset];
-        mm_dbg ("  user data length: %u elements", tp_user_data_size_elements);
-
-        if (user_data_encoding == MM_SMS_ENCODING_GSM7)
-            tp_user_data_size_bytes = (7 * (tp_user_data_size_elements + 1 )) / 8;
-        else
-            tp_user_data_size_bytes = tp_user_data_size_elements;
-        mm_dbg ("  user data length: %u bytes", tp_user_data_size_bytes);
-
-        tp_user_data_offset = tp_user_data_len_offset + 1;
-        PDU_SIZE_CHECK (tp_user_data_offset + tp_user_data_size_bytes, "cannot read TP-UD");
-
-        bit_offset = 0;
-        if (has_udh) {
-            guint udhl, end;
-
-            udhl = pdu[tp_user_data_offset] + 1;
-            end = tp_user_data_offset + udhl;
-
-            PDU_SIZE_CHECK (tp_user_data_offset + udhl, "cannot read UDH");
-
-            for (offset = tp_user_data_offset + 1; (offset + 1) < end;) {
-                guint8 ie_id, ie_len;
-
-                ie_id = pdu[offset++];
-                ie_len = pdu[offset++];
-
-                switch (ie_id) {
-                case 0x00:
-                    if (offset + 2 >= end)
-                        break;
-                    /*
-                     * Ignore the IE if one of the following is true:
-                     *  - it claims to be part 0 of M
-                     *  - it claims to be part N of M, N > M
-                     */
-                    if (pdu[offset + 2] == 0 ||
-                        pdu[offset + 2] > pdu[offset + 1])
-                        break;
-
-                    mm_sms_part_set_concat_reference (sms_part, pdu[offset]);
-                    mm_sms_part_set_concat_max (sms_part, pdu[offset + 1]);
-                    mm_sms_part_set_concat_sequence (sms_part, pdu[offset + 2]);
-                    break;
-                case 0x08:
-                    if (offset + 3 >= end)
-                        break;
-                    /* Concatenated short message, 16-bit reference */
-                    if (pdu[offset + 3] == 0 ||
-                        pdu[offset + 3] > pdu[offset + 2])
-                        break;
-
-                    mm_sms_part_set_concat_reference (sms_part, (pdu[offset] << 8) | pdu[offset + 1]);
-                    mm_sms_part_set_concat_max (sms_part,pdu[offset + 2]);
-                    mm_sms_part_set_concat_sequence (sms_part, pdu[offset + 3]);
-                    break;
-                }
-
-                offset += ie_len;
-            }
-
-            /*
-             * Move past the user data headers to prevent it from being
-             * decoded into garbage text.
-             */
-            tp_user_data_offset += udhl;
-            tp_user_data_size_bytes -= udhl;
-            if (user_data_encoding == MM_SMS_ENCODING_GSM7) {
-                /*
-                 * Find the number of bits we need to add to the length of the
-                 * user data to get a multiple of 7 (the padding).
-                 */
-                bit_offset = (7 - udhl % 7) % 7;
-                tp_user_data_size_elements -= (udhl * 8 + bit_offset) / 7;
-            } else
-                tp_user_data_size_elements -= udhl;
-        }
-
-        switch (user_data_encoding) {
-        case MM_SMS_ENCODING_GSM7:
-        case MM_SMS_ENCODING_UCS2:
-            /* Otherwise if it's 7-bit or UCS2 we can decode it */
-            mm_dbg ("Decoding SMS text with '%u' elements", tp_user_data_size_elements);
-            mm_sms_part_take_text (sms_part,
-                                   sms_decode_text (&pdu[tp_user_data_offset],
-                                                    tp_user_data_size_elements,
-                                                    user_data_encoding,
-                                                    bit_offset));
-            g_warn_if_fail (sms_part->text != NULL);
-            break;
-
-        default:
-            {
-                GByteArray *raw;
-
-                mm_dbg ("Skipping SMS text: Unknown encoding (0x%02X)", user_data_encoding);
-
-                PDU_SIZE_CHECK (tp_user_data_offset + tp_user_data_size_bytes, "cannot read user data");
-
-                /* 8-bit encoding is usually binary data, and we have no idea what
-                 * actual encoding the data is in so we can't convert it.
-                 */
-                raw = g_byte_array_sized_new (tp_user_data_size_bytes);
-                g_byte_array_append (raw, &pdu[tp_user_data_offset], tp_user_data_size_bytes);
-                mm_sms_part_take_data (sms_part, raw);
-                break;
-            }
-        }
-    }
-
-    return sms_part;
-}
-
-/**
- * mm_sms_part_encode_address:
- *
- * @address: the phone number to encode
- * @buf: the buffer to encode @address in
- * @buflen: the size  of @buf
- * @is_smsc: if %TRUE encode size as number of octets of address infromation,
- *   otherwise if %FALSE encode size as number of digits of @address
- *
- * Returns: the size in bytes of the data added to @buf
- **/
-guint
-mm_sms_part_encode_address (const gchar *address,
-                            guint8 *buf,
-                            gsize buflen,
-                            gboolean is_smsc)
-{
-    gsize len;
-
-    g_return_val_if_fail (address != NULL, 0);
-    g_return_val_if_fail (buf != NULL, 0);
-    g_return_val_if_fail (buflen >= 2, 0);
-
-    /* Handle number type & plan */
-    buf[1] = 0x80;  /* Bit 7 always 1 */
-    if (address[0] == '+') {
-        buf[1] |= SMS_NUMBER_TYPE_INTL;
-        address++;
-    }
-    buf[1] |= SMS_NUMBER_PLAN_TELEPHONE;
-
-    len = sms_string_to_bcd_semi_octets (&buf[2], buflen, address);
-
-    if (is_smsc)
-        buf[0] = len + 1;  /* addr length + size byte */
-    else
-        buf[0] = strlen (address);  /* number of digits in address */
-
-    return len ? len + 2 : 0;  /* addr length + size byte + number type/plan */
-}
-
-/**
- * mm_sms_part_get_submit_pdu:
- *
- * @part: the SMS message part
- * @out_pdulen: on success, the size of the returned PDU in bytes
- * @out_msgstart: on success, the byte index in the returned PDU where the
- *  message starts (ie, skipping the SMSC length byte and address, if present)
- * @error: on error, filled with the error that occurred
- *
- * Constructs a single-part SMS message with the given details, preferring to
- * use the UCS2 character set when the message will fit, otherwise falling back
- * to the GSM character set.
- *
- * Returns: the constructed PDU data on success, or %NULL on error
- **/
-guint8 *
-mm_sms_part_get_submit_pdu (MMSmsPart *part,
-                            guint *out_pdulen,
-                            guint *out_msgstart,
-                            GError **error)
-{
-    guint8 *pdu;
-    guint len, offset = 0;
-    guint shift = 0;
-    guint8 *udl_ptr;
-
-    g_return_val_if_fail (part->number != NULL, NULL);
-    g_return_val_if_fail (part->text != NULL || part->data != NULL, NULL);
-
-    mm_dbg ("Creating PDU for part...");
-
-    /* Build up the PDU */
-    pdu = g_malloc0 (PDU_SIZE);
-
-    if (part->smsc) {
-        mm_dbg ("  adding SMSC to PDU...");
-        len = mm_sms_part_encode_address (part->smsc, pdu, PDU_SIZE, TRUE);
-        if (len == 0) {
-            g_set_error (error,
-                         MM_MESSAGE_ERROR,
-                         MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
-                         "Invalid SMSC address '%s'", part->smsc);
-            goto error;
-        }
-        offset += len;
-    } else {
-        /* No SMSC, use default */
-        pdu[offset++] = 0x00;
-    }
-
-    if (out_msgstart)
-        *out_msgstart = offset;
-
-    /* ----------- First BYTE ----------- */
-    pdu[offset] = 0;
-
-    /* TP-VP present; format RELATIVE */
-    if (part->validity_relative > 0) {
-        mm_dbg ("  adding validity to PDU...");
-        pdu[offset] |= 0x10;
-    }
-
-    /* Concatenation sequence only found in multipart SMS */
-    if (part->concat_sequence) {
-        mm_dbg ("  adding UDHI to PDU...");
-        pdu[offset] |= 0x40; /* UDHI */
-    }
-
-    /* Delivery report requested in singlepart messages or in the last PDU of
-     * multipart messages */
-    if (part->delivery_report_request &&
-        (!part->concat_sequence ||
-         part->concat_max == part->concat_sequence)) {
-        mm_dbg ("  requesting delivery report...");
-        pdu[offset] |= 0x20;
-    }
-
-    /* TP-MTI = SMS-SUBMIT */
-    pdu[offset++] |= 0x01;
-
-
-    /* ----------- TP-MR (1 byte) ----------- */
-
-    pdu[offset++] = 0x00;     /* TP-Message-Reference: filled by device */
-
-    /* ----------- Destination address ----------- */
-
-    len = mm_sms_part_encode_address (part->number, &pdu[offset], PDU_SIZE - offset, FALSE);
-    if (len == 0) {
-        g_set_error (error,
-                     MM_MESSAGE_ERROR,
-                     MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
-                     "Invalid number '%s'", part->number);
-        goto error;
-    }
-    offset += len;
-
-    /* ----------- TP-PID (1 byte) ----------- */
-
-    pdu[offset++] = 0x00;
-
-    /* ----------- TP-DCS (1 byte) ----------- */
-    pdu[offset] = 0x00;
-
-    if (part->class >= 0 && part->class <= 3) {
-        mm_dbg ("  using class %d...", part->class);
-        pdu[offset] |= SMS_DCS_CLASS_VALID;
-        pdu[offset] |= part->class;
-    }
-
-    if (part->encoding == MM_SMS_ENCODING_UCS2) {
-        mm_dbg ("  using UCS2 encoding...");
-        pdu[offset] |= SMS_DCS_CODING_UCS2;
-    } else if (part->encoding == MM_SMS_ENCODING_GSM7) {
-        mm_dbg ("  using GSM7 encoding...");
-        pdu[offset] |= SMS_DCS_CODING_DEFAULT;  /* GSM */
-    } else {
-        mm_dbg ("  using 8bit encoding...");
-        pdu[offset] |= SMS_DCS_CODING_8BIT;
-    }
-    offset++;
-
-    /* ----------- TP-Validity-Period (1 byte): 4 days ----------- */
-    /* Only if TP-VPF was set in first byte */
-
-    if (part->validity_relative > 0)
-        pdu[offset++] = validity_to_relative (part->validity_relative);
-
-    /* ----------- TP-User-Data-Length ----------- */
-    /* Set to zero initially, and keep a ptr for easy access later */
-    udl_ptr = &pdu[offset];
-    pdu[offset++] = 0;
-
-    /* Build UDH */
-    if (part->concat_sequence) {
-        mm_dbg ("  adding UDH header in PDU... (reference: %u, max: %u, sequence: %u)",
-                part->concat_reference,
-                part->concat_max,
-                part->concat_sequence);
-        pdu[offset++] = 0x05; /* udh len */
-        pdu[offset++] = 0x00; /* mid */
-        pdu[offset++] = 0x03; /* data len */
-        pdu[offset++] = (guint8)part->concat_reference;
-        pdu[offset++] = (guint8)part->concat_max;
-        pdu[offset++] = (guint8)part->concat_sequence;
-
-        /* if a UDH is present and the data encoding is the default 7-bit
-         * alphabet, the user data must be 7-bit word aligned after the
-         * UDH. This means up to 6 bits of zeros need to be inserted at the
-         * start of the message.
-         *
-         * In our case the UDH is 6 bytes long, 48bits. The next multiple of
-         * 7 is therefore 49, so we only need to include one bit of padding.
-         */
-        shift = 1;
-    }
-
-    if (part->encoding == MM_SMS_ENCODING_GSM7) {
-        guint8 *unpacked, *packed;
-        guint32 unlen = 0, packlen = 0;
-
-        unpacked = mm_charset_utf8_to_unpacked_gsm (part->text, &unlen);
-        if (!unpacked || unlen == 0) {
-            g_free (unpacked);
-            g_set_error_literal (error,
-                                 MM_MESSAGE_ERROR,
-                                 MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
-                                 "Failed to convert message text to GSM");
-            goto error;
-        }
-
-        /* Set real data length, in septets
-         * If we had UDH, add 7 septets
-         */
-        *udl_ptr = part->concat_sequence ? (7 + unlen) : unlen;
-        mm_dbg ("  user data length is '%u' septets (%s UDH)",
-                *udl_ptr,
-                part->concat_sequence ? "with" : "without");
-
-        packed = gsm_pack (unpacked, unlen, shift, &packlen);
-        g_free (unpacked);
-        if (!packed || packlen == 0) {
-            g_free (packed);
-            g_set_error_literal (error,
-                                 MM_MESSAGE_ERROR,
-                                 MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
-                                 "Failed to pack message text to GSM");
-            goto error;
-        }
-
-        memcpy (&pdu[offset], packed, packlen);
-        g_free (packed);
-        offset += packlen;
-    } else if (part->encoding == MM_SMS_ENCODING_UCS2) {
-        GByteArray *array;
-
-        /* Try to guess a good value for the array */
-        array = g_byte_array_sized_new (strlen (part->text) * 2);
-        if (!mm_modem_charset_byte_array_append (array, part->text, FALSE, MM_MODEM_CHARSET_UCS2)) {
-            g_byte_array_free (array, TRUE);
-            g_set_error_literal (error,
-                                 MM_MESSAGE_ERROR,
-                                 MM_MESSAGE_ERROR_INVALID_PDU_PARAMETER,
-                                 "Failed to convert message text to UCS2");
-            goto error;
-        }
-
-        /* Set real data length, in octets
-         * If we had UDH, add 6 octets
-         */
-        *udl_ptr = part->concat_sequence ? (6 + array->len) : array->len;
-        mm_dbg ("  user data length is '%u' octets (%s UDH)",
-                *udl_ptr,
-                part->concat_sequence ? "with" : "without");
-
-        memcpy (&pdu[offset], array->data, array->len);
-        offset += array->len;
-        g_byte_array_free (array, TRUE);
-    } else if (part->encoding == MM_SMS_ENCODING_8BIT) {
-        /* Set real data length, in octets
-         * If we had UDH, add 6 octets
-         */
-        *udl_ptr = part->concat_sequence ? (6 + part->data->len) : part->data->len;
-        mm_dbg ("  binary user data length is '%u' octets (%s UDH)",
-                *udl_ptr,
-                part->concat_sequence ? "with" : "without");
-
-        memcpy (&pdu[offset], part->data->data, part->data->len);
-        offset += part->data->len;
-    } else
-        g_assert_not_reached ();
-
-    if (out_pdulen)
-        *out_pdulen = offset;
-    return pdu;
-
-error:
-    g_free (pdu);
-    return NULL;
-}
-
-gchar **
-mm_sms_part_util_split_text (const gchar *text,
-                             MMSmsEncoding *encoding)
-{
-    guint gsm_unsupported = 0;
-    gchar **out;
-    guint n_chunks;
-    guint i;
-    guint j;
-    gsize in_len;
-
-    if (!text)
-        return NULL;
-
-    in_len = strlen (text);
-
-    /* Some info about the rules for splitting.
-     *
-     * The User Data can be up to 140 bytes in the SMS part:
-     *  0) If we only need one chunk, it can be of up to 140 bytes.
-     *     If we need more than one chunk, these have to be of 140 - 6 = 134
-     *     bytes each, as we need place for the UDH header.
-     *  1) If we're using GSM7 encoding, this gives us up to 160 characters,
-     *     as we can pack 160 characters of 7bits each into 140 bytes.
-     *      160 * 7 = 140 * 8 = 1120.
-     *     If we only have 134 bytes allowed, that would mean that we can pack
-     *     up to 153 input characters:
-     *      134 * 8 = 1072; 1072/7=153.14
-     *  2) If we're using UCS2 encoding, we can pack up to 70 characters in
-     *     140 bytes (each with 2 bytes), or up to 67 characters in 134 bytes.
-     *
-     * This method does the split of the input string into N strings, so that
-     * each of the strings can be placed in a SMS part.
-     */
-
-    /* Check if we can do GSM encoding */
-    mm_charset_get_encoded_len (text,
-                                MM_MODEM_CHARSET_GSM,
-                                &gsm_unsupported);
-    if (gsm_unsupported > 0) {
-        /* If cannot do it in GSM encoding, do it in UCS-2 */
-        GByteArray *array;
-
-        *encoding = MM_SMS_ENCODING_UCS2;
-
-        /* Guess more or less the size of the output array to avoid multiple
-         * allocations */
-        array = g_byte_array_sized_new (in_len * 2);
-        if (!mm_modem_charset_byte_array_append (array,
-                                                 text,
-                                                 FALSE,
-                                                 MM_MODEM_CHARSET_UCS2)) {
-            g_byte_array_unref (array);
-            return NULL;
-        }
-
-        /* Our bytearray has it in UCS-2 now.
-         * UCS-2 is a fixed-size encoding, which means that the text has exactly
-         * 2 bytes for each unicode point. We can now split this array into
-         * chunks of 67 UCS-2 characters (134 bytes).
-         *
-         * Note that UCS-2 covers unicode points between U+0000 and U+FFFF, which
-         * means that there is no direct relationship between the size of the
-         * input text in UTF-8 and the size of the text in UCS-2. A 3-byte UTF-8
-         * encoded character will still be represented with 2 bytes in UCS-2.
-         */
-        if (array->len <= 140) {
-            out = g_new (gchar *, 2);
-            out[0] = g_strdup (text);
-            out[1] = NULL;
-        } else {
-            n_chunks = array->len / 134;
-            if (array->len % 134 != 0)
-                n_chunks++;
-
-            out = g_new0 (gchar *, n_chunks + 1);
-            for (i = 0, j = 0; i < n_chunks; i++, j += 134) {
-                out[i] = sms_decode_text (&array->data[j],
-                                          MIN (array->len - j, 134),
-                                          MM_SMS_ENCODING_UCS2,
-                                          0);
-            }
-        }
-        g_byte_array_unref (array);
-    } else {
-        /* Do it with GSM encoding */
-        *encoding = MM_SMS_ENCODING_GSM7;
-
-        if (in_len <= 160) {
-            out = g_new (gchar *, 2);
-            out[0] = g_strdup (text);
-            out[1] = NULL;
-        } else {
-            n_chunks = in_len / 153;
-            if (in_len % 153 != 0)
-                n_chunks++;
-
-            out = g_new0 (gchar *, n_chunks + 1);
-            for (i = 0, j = 0; i < n_chunks; i++, j += 153) {
-                out[i] = g_strndup (&text[j], 153);
-            }
-        }
-    }
-
-    return out;
-}
-
-GByteArray **
-mm_sms_part_util_split_data (const guint8 *data,
-                             gsize data_len)
-{
-    GByteArray **out;
-
-    /* Some info about the rules for splitting.
-     *
-     * The User Data can be up to 140 bytes in the SMS part:
-     *  0) If we only need one chunk, it can be of up to 140 bytes.
-     *     If we need more than one chunk, these have to be of 140 - 6 = 134
-     *     bytes each, as we need place for the UDH header.
-     */
-
-    if (data_len <= 140) {
-        out = g_new0 (GByteArray *, 2);
-        out[0] = g_byte_array_append (g_byte_array_sized_new (data_len),
-                                      data,
-                                      data_len);
-    } else {
-        guint n_chunks;
-        guint i;
-        guint j;
-
-        n_chunks = data_len / 134;
-        if (data_len % 134 != 0)
-            n_chunks ++;
-
-        out = g_new0 (GByteArray *, n_chunks + 1);
-        for (i = 0, j = 0; i < n_chunks; i++, j+= 134) {
-            out[i] = g_byte_array_append (g_byte_array_sized_new (134),
-                                          &data[j],
-                                          MIN (data_len - j, 134));
-        }
-    }
-
-    return out;
-}
diff --git a/src/mm-sms-part.h b/src/mm-sms-part.h
index d44ed5b..9626295 100644
--- a/src/mm-sms-part.h
+++ b/src/mm-sms-part.h
@@ -29,25 +29,20 @@
 
 typedef struct _MMSmsPart MMSmsPart;
 
-#define SMS_MAX_PDU_LEN 344
 #define SMS_PART_INVALID_INDEX G_MAXUINT
 
+#define MM_SMS_PART_IS_3GPP(part)                                       \
+    (mm_sms_part_get_pdu_type (part) >= MM_SMS_PDU_TYPE_DELIVER &&      \
+     mm_sms_part_get_pdu_type (part) <= MM_SMS_PDU_TYPE_STATUS_REPORT)
+
+#define MM_SMS_PART_IS_CDMA(part)                                       \
+    (mm_sms_part_get_pdu_type (part) >= MM_SMS_PDU_TYPE_CDMA_DELIVER && \
+     mm_sms_part_get_pdu_type (part) <= MM_SMS_PDU_TYPE_CDMA_READ_ACKNOWLEDGEMENT)
+
 MMSmsPart *mm_sms_part_new  (guint index,
                              MMSmsPduType type);
-MMSmsPart *mm_sms_part_new_from_pdu  (guint index,
-                                      const gchar *hexpdu,
-                                      GError **error);
-MMSmsPart *mm_sms_part_new_from_binary_pdu  (guint index,
-                                             const guint8 *pdu,
-                                             gsize pdu_len,
-                                             GError **error);
 void       mm_sms_part_free (MMSmsPart *part);
 
-guint8    *mm_sms_part_get_submit_pdu (MMSmsPart *part,
-                                       guint *out_pdulen,
-                                       guint *out_msgstart,
-                                       GError **error);
-
 guint             mm_sms_part_get_index              (MMSmsPart *part);
 void              mm_sms_part_set_index              (MMSmsPart *part,
                                                       guint index);
@@ -129,16 +124,12 @@
 
 gboolean          mm_sms_part_should_concat          (MMSmsPart *part);
 
-/* For testcases only */
-guint mm_sms_part_encode_address (const gchar *address,
-                                  guint8 *buf,
-                                  gsize buflen,
-                                  gboolean is_smsc);
-
-gchar **mm_sms_part_util_split_text (const gchar *text,
-                                     MMSmsEncoding *encoding);
-
-GByteArray **mm_sms_part_util_split_data (const guint8 *data,
-                                          gsize data_len);
+/* CDMA specific */
+MMSmsCdmaTeleserviceId   mm_sms_part_get_cdma_teleservice_id   (MMSmsPart *part);
+void                     mm_sms_part_set_cdma_teleservice_id   (MMSmsPart *part,
+                                                                MMSmsCdmaTeleserviceId cdma_teleservice_id);
+MMSmsCdmaServiceCategory mm_sms_part_get_cdma_service_category (MMSmsPart *part);
+void                     mm_sms_part_set_cdma_service_category (MMSmsPart *part,
+                                                                MMSmsCdmaServiceCategory cdma_service_category);
 
 #endif /* MM_SMS_PART_H */
diff --git a/src/mm-sms-qmi.c b/src/mm-sms-qmi.c
index 85ca516..c7d11c7 100644
--- a/src/mm-sms-qmi.c
+++ b/src/mm-sms-qmi.c
@@ -25,9 +25,12 @@
 #include <libmm-glib.h>
 
 #include "mm-modem-helpers-qmi.h"
+#include "mm-iface-modem.h"
 #include "mm-iface-modem-messaging.h"
 #include "mm-sms-qmi.h"
 #include "mm-base-modem.h"
+#include "mm-sms-part-3gpp.h"
+#include "mm-sms-part-cdma.h"
 #include "mm-log.h"
 
 G_DEFINE_TYPE (MMSmsQmi, mm_sms_qmi, MM_TYPE_SMS);
@@ -82,6 +85,33 @@
 }
 
 /*****************************************************************************/
+
+static gboolean
+check_sms_type_support (MMSmsQmi *self,
+                        MMBaseModem *modem,
+                        MMSmsPart *first_part,
+                        GError **error)
+{
+    if (MM_SMS_PART_IS_3GPP (first_part) && !mm_iface_modem_is_3gpp (MM_IFACE_MODEM (modem))) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Non-3GPP modem doesn't support 3GPP SMS");
+        return FALSE;
+    }
+
+    if (MM_SMS_PART_IS_CDMA (first_part) && !mm_iface_modem_is_cdma (MM_IFACE_MODEM (modem))) {
+        g_set_error (error,
+                     MM_CORE_ERROR,
+                     MM_CORE_ERROR_UNSUPPORTED,
+                     "Non-CDMA modem doesn't support CDMA SMS");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
 /* Store the SMS */
 
 typedef struct {
@@ -96,7 +126,7 @@
 static void
 sms_store_context_complete_and_free (SmsStoreContext *ctx)
 {
-    g_simple_async_result_complete (ctx->result);
+    g_simple_async_result_complete_in_idle (ctx->result);
     g_object_unref (ctx->result);
     g_object_unref (ctx->client);
     g_object_unref (ctx->modem);
@@ -159,7 +189,7 @@
 sms_store_next_part (SmsStoreContext *ctx)
 {
     QmiMessageWmsRawWriteInput *input;
-    guint8 *pdu;
+    guint8 *pdu = NULL;
     guint pdulen = 0;
     guint msgstart = 0;
     GArray *array;
@@ -173,10 +203,22 @@
     }
 
     /* Get PDU */
-    pdu = mm_sms_part_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &msgstart, &error);
+    if (MM_SMS_PART_IS_3GPP ((MMSmsPart *)ctx->current->data))
+        pdu = mm_sms_part_3gpp_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &msgstart, &error);
+    else if (MM_SMS_PART_IS_CDMA ((MMSmsPart *)ctx->current->data))
+        pdu = mm_sms_part_cdma_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &error);
+
     if (!pdu) {
-        /* 'error' should already be set */
-        g_simple_async_result_take_error (ctx->result, error);
+        if (error)
+            g_simple_async_result_take_error (ctx->result, error);
+        else
+            g_simple_async_result_set_error (ctx->result,
+                                             MM_CORE_ERROR,
+                                             MM_CORE_ERROR_FAILED,
+                                             "Unknown or unsupported PDU type in SMS part: %s",
+                                             mm_sms_pdu_type_get_string (
+                                                 mm_sms_part_get_pdu_type (
+                                                     (MMSmsPart *)ctx->current->data)));
         sms_store_context_complete_and_free (ctx);
         return;
     }
@@ -192,7 +234,9 @@
     qmi_message_wms_raw_write_input_set_raw_message_data (
         input,
         mm_sms_storage_to_qmi_storage_type (ctx->storage),
-        QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT,
+        (MM_SMS_PART_IS_3GPP ((MMSmsPart *)ctx->current->data) ?
+         QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT :
+         QMI_WMS_MESSAGE_FORMAT_CDMA),
         array,
         NULL);
     qmi_client_wms_raw_write (ctx->client,
@@ -213,6 +257,7 @@
 {
     SmsStoreContext *ctx;
     QmiClient *client = NULL;
+    GError *error = NULL;
 
     /* Ensure WMS client */
     if (!ensure_qmi_client (MM_SMS_QMI (self),
@@ -234,6 +279,15 @@
                   NULL);
 
     ctx->current = mm_sms_get_parts (self);
+
+    /* Check whether we support the given SMS type */
+    if (!check_sms_type_support (MM_SMS_QMI (self), ctx->modem, (MMSmsPart *)ctx->current->data, &error)) {
+        g_simple_async_result_take_error (ctx->result, error);
+        sms_store_context_complete_and_free (ctx);
+        return;
+    }
+
+    /* Go on */
     sms_store_next_part (ctx);
 }
 
@@ -325,16 +379,29 @@
 sms_send_generic (SmsSendContext *ctx)
 {
     QmiMessageWmsRawSendInput *input;
-    guint8 *pdu;
+    guint8 *pdu = NULL;
     guint pdulen = 0;
     guint msgstart = 0;
     GArray *array;
     GError *error = NULL;
 
     /* Get PDU */
-    pdu = mm_sms_part_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &msgstart, &error);
+    if (MM_SMS_PART_IS_3GPP ((MMSmsPart *)ctx->current->data))
+        pdu = mm_sms_part_3gpp_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &msgstart, &error);
+    else if (MM_SMS_PART_IS_CDMA ((MMSmsPart *)ctx->current->data))
+        pdu = mm_sms_part_cdma_get_submit_pdu ((MMSmsPart *)ctx->current->data, &pdulen, &error);
+
     if (!pdu) {
-        g_simple_async_result_take_error (ctx->result, error);
+        if (error)
+            g_simple_async_result_take_error (ctx->result, error);
+        else
+            g_simple_async_result_set_error (ctx->result,
+                                             MM_CORE_ERROR,
+                                             MM_CORE_ERROR_FAILED,
+                                             "Unknown or unsupported PDU type in SMS part: %s",
+                                             mm_sms_pdu_type_get_string (
+                                                 mm_sms_part_get_pdu_type (
+                                                     (MMSmsPart *)ctx->current->data)));
         sms_send_context_complete_and_free (ctx);
         return;
     }
@@ -346,10 +413,11 @@
     g_free (pdu);
 
     input = qmi_message_wms_raw_send_input_new ();
-
     qmi_message_wms_raw_send_input_set_raw_message_data (
         input,
-        QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT,
+        (MM_SMS_PART_IS_3GPP ((MMSmsPart *)ctx->current->data) ?
+         QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT :
+         QMI_WMS_MESSAGE_FORMAT_CDMA),
         array,
         NULL);
 
@@ -404,6 +472,8 @@
         } else {
             QmiWmsGsmUmtsRpCause rp_cause;
             QmiWmsGsmUmtsTpCause tp_cause;
+            QmiWmsCdmaCauseCode cdma_cause_code;
+            QmiWmsCdmaErrorClass cdma_error_class;
 
             if (qmi_message_wms_send_from_memory_storage_output_get_gsm_wcdma_cause_info (
                     output,
@@ -417,6 +487,24 @@
                          qmi_wms_gsm_umts_tp_cause_get_string (tp_cause));
             }
 
+            if (qmi_message_wms_send_from_memory_storage_output_get_cdma_cause_code (
+                    output,
+                    &cdma_cause_code,
+                    NULL)) {
+                mm_warn ("Couldn't send SMS; cause code (%u): '%s'",
+                         cdma_cause_code,
+                         qmi_wms_cdma_cause_code_get_string (cdma_cause_code));
+            }
+
+            if (qmi_message_wms_send_from_memory_storage_output_get_cdma_error_class (
+                    output,
+                    &cdma_error_class,
+                    NULL)) {
+                mm_warn ("Couldn't send SMS; error class (%u): '%s'",
+                         cdma_error_class,
+                         qmi_wms_cdma_error_class_get_string (cdma_error_class));
+            }
+
             g_prefix_error (&error, "Couldn't write SMS part: ");
             g_simple_async_result_take_error (ctx->result, error);
             sms_send_context_complete_and_free (ctx);
@@ -448,7 +536,9 @@
         input,
         mm_sms_storage_to_qmi_storage_type (mm_sms_get_storage (ctx->self)),
         mm_sms_part_get_index ((MMSmsPart *)ctx->current->data),
-        QMI_WMS_MESSAGE_MODE_GSM_WCDMA,
+        (MM_SMS_PART_IS_3GPP ((MMSmsPart *)ctx->current->data) ?
+         QMI_WMS_MESSAGE_MODE_GSM_WCDMA :
+         QMI_WMS_MESSAGE_MODE_CDMA),
         NULL);
 
     qmi_client_wms_send_from_memory_storage (
@@ -485,6 +575,7 @@
 {
     SmsSendContext *ctx;
     QmiClient *client = NULL;
+    GError *error = NULL;
 
     /* Ensure WMS client */
     if (!ensure_qmi_client (MM_SMS_QMI (self),
@@ -507,7 +598,15 @@
     /* If the SMS is STORED, try to send from storage */
     ctx->from_storage = (mm_sms_get_storage (self) != MM_SMS_STORAGE_UNKNOWN);
 
-    ctx->current = mm_sms_get_parts (self);;
+    ctx->current = mm_sms_get_parts (self);
+
+    /* Check whether we support the given SMS type */
+    if (!check_sms_type_support (MM_SMS_QMI (self), ctx->modem, (MMSmsPart *)ctx->current->data, &error)) {
+        g_simple_async_result_take_error (ctx->result, error);
+        sms_send_context_complete_and_free (ctx);
+        return;
+    }
+
     sms_send_next_part (ctx);
 }
 
@@ -612,7 +711,9 @@
         NULL);
     qmi_message_wms_delete_input_set_message_mode (
         input,
-        QMI_WMS_MESSAGE_MODE_GSM_WCDMA,
+        (MM_SMS_PART_IS_3GPP ((MMSmsPart *)ctx->current->data) ?
+         QMI_WMS_MESSAGE_MODE_GSM_WCDMA:
+         QMI_WMS_MESSAGE_MODE_CDMA),
         NULL);
     qmi_client_wms_delete (ctx->client,
                            input,
diff --git a/src/mm-sms.c b/src/mm-sms.c
index a7b1d64..672d5bc 100644
--- a/src/mm-sms.c
+++ b/src/mm-sms.c
@@ -30,6 +30,7 @@
 #include "mm-iface-modem.h"
 #include "mm-iface-modem-messaging.h"
 #include "mm-sms.h"
+#include "mm-sms-part-3gpp.h"
 #include "mm-base-modem-at.h"
 #include "mm-base-modem.h"
 #include "mm-log.h"
@@ -94,8 +95,8 @@
 }
 
 static gboolean
-generate_submit_pdus (MMSms *self,
-                      GError **error)
+generate_3gpp_submit_pdus (MMSms *self,
+                           GError **error)
 {
     guint i;
     guint n_parts;
@@ -123,7 +124,7 @@
     g_assert (!(text != NULL && data != NULL));
 
     if (text) {
-        split_text = mm_sms_part_util_split_text (text, &encoding);
+        split_text = mm_sms_part_3gpp_util_split_text (text, &encoding);
         if (!split_text) {
             g_set_error (error,
                          MM_CORE_ERROR,
@@ -134,7 +135,7 @@
         n_parts = g_strv_length (split_text);
     } else if (data) {
         encoding = MM_SMS_ENCODING_8BIT;
-        split_data = mm_sms_part_util_split_data (data, data_len);
+        split_data = mm_sms_part_3gpp_util_split_data (data, data_len);
         g_assert (split_data != NULL);
         /* noop within the for */
         for (n_parts = 0; split_data[n_parts]; n_parts++);
@@ -231,6 +232,90 @@
     return TRUE;
 }
 
+static gboolean
+generate_cdma_submit_pdus (MMSms *self,
+                           GError **error)
+{
+    const gchar *text;
+    GVariant *data_variant;
+    const guint8 *data;
+    gsize data_len = 0;
+
+    MMSmsPart *part;
+
+    g_assert (self->priv->parts == NULL);
+
+    text = mm_gdbus_sms_get_text (MM_GDBUS_SMS (self));
+    data_variant = mm_gdbus_sms_get_data (MM_GDBUS_SMS (self));
+    data = (data_variant ?
+            g_variant_get_fixed_array (data_variant,
+                                       &data_len,
+                                       sizeof (guchar)) :
+            NULL);
+
+    g_assert (text != NULL || data != NULL);
+    g_assert (!(text != NULL && data != NULL));
+
+    /* Create new part */
+    part = mm_sms_part_new (SMS_PART_INVALID_INDEX, MM_SMS_PDU_TYPE_CDMA_SUBMIT);
+    if (text)
+        mm_sms_part_set_text (part, text);
+    else if (data) {
+        GByteArray *part_data;
+
+        part_data = g_byte_array_sized_new (data_len);
+        g_byte_array_append (part_data, data, data_len);
+        mm_sms_part_take_data (part, part_data);
+    } else
+        g_assert_not_reached ();
+    mm_sms_part_set_encoding (part, data ? MM_SMS_ENCODING_8BIT : MM_SMS_ENCODING_UNKNOWN);
+    mm_sms_part_set_number (part, mm_gdbus_sms_get_number (MM_GDBUS_SMS (self)));
+
+    /* If creating a CDMA SMS part but we don't have a Teleservice ID, we default to WMT */
+    if (mm_gdbus_sms_get_teleservice_id (MM_GDBUS_SMS (self)) == MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN) {
+        mm_dbg ("Defaulting to WMT teleservice ID when creating SMS part");
+        mm_sms_part_set_cdma_teleservice_id (part, MM_SMS_CDMA_TELESERVICE_ID_WMT);
+    } else
+        mm_sms_part_set_cdma_teleservice_id (part, mm_gdbus_sms_get_teleservice_id (MM_GDBUS_SMS (self)));
+
+    mm_sms_part_set_cdma_service_category (part, mm_gdbus_sms_get_service_category (MM_GDBUS_SMS (self)));
+
+    mm_dbg ("Created SMS part for CDMA SMS");
+
+    /* Add to the list of parts */
+    self->priv->parts = g_list_append (self->priv->parts, part);
+
+    /* No more parts are expected */
+    self->priv->is_assembled = TRUE;
+
+    return TRUE;
+}
+
+static gboolean
+generate_submit_pdus (MMSms *self,
+                      GError **error)
+{
+    MMBaseModem *modem;
+    gboolean is_3gpp;
+
+    /* First; decide which kind of PDU we'll generate, based on the current modem caps */
+
+    g_object_get (self,
+                  MM_SMS_MODEM, &modem,
+                  NULL);
+    g_assert (modem != NULL);
+
+    is_3gpp = mm_iface_modem_is_3gpp (MM_IFACE_MODEM (modem));
+    g_object_unref (modem);
+
+    /* On a 3GPP-capable modem, create always a 3GPP SMS (even if the modem is 3GPP+3GPP2) */
+    if (is_3gpp)
+        return generate_3gpp_submit_pdus (self, error);
+
+    /* Otherwise, create a 3GPP2 SMS */
+    return generate_cdma_submit_pdus (self, error);
+}
+
 /*****************************************************************************/
 /* Store SMS (DBus call handling) */
 
@@ -257,9 +342,12 @@
 {
     GError *error = NULL;
 
-    if (!MM_SMS_GET_CLASS (self)->store_finish (self, res, &error))
+    if (!MM_SMS_GET_CLASS (self)->store_finish (self, res, &error)) {
+        /* On error, clear up the parts we generated */
+        g_list_free_full (self->priv->parts, (GDestroyNotify)mm_sms_part_free);
+        self->priv->parts = NULL;
         g_dbus_method_invocation_take_error (ctx->invocation, error);
-    else {
+    } else {
         mm_gdbus_sms_set_storage (MM_GDBUS_SMS (ctx->self), ctx->storage);
 
         /* Transition from Unknown->Stored for SMS which were created by the user */
@@ -297,10 +385,12 @@
     /* If the message is a multipart message, we need to set a proper
      * multipart reference. When sending a message which wasn't stored
      * yet, we can just get a random multipart reference. */
-    self->priv->multipart_reference = reference;
-    for (l = self->priv->parts; l; l = g_list_next (l)) {
-        mm_sms_part_set_concat_reference ((MMSmsPart *)l->data,
-                                          self->priv->multipart_reference);
+    if (self->priv->is_multipart) {
+        self->priv->multipart_reference = reference;
+        for (l = self->priv->parts; l; l = g_list_next (l)) {
+            mm_sms_part_set_concat_reference ((MMSmsPart *)l->data,
+                                              self->priv->multipart_reference);
+        }
     }
 
     return TRUE;
@@ -426,9 +516,12 @@
 {
     GError *error = NULL;
 
-    if (!MM_SMS_GET_CLASS (self)->send_finish (self, res, &error))
+    if (!MM_SMS_GET_CLASS (self)->send_finish (self, res, &error)) {
+        /* On error, clear up the parts we generated */
+        g_list_free_full (self->priv->parts, (GDestroyNotify)mm_sms_part_free);
+        self->priv->parts = NULL;
         g_dbus_method_invocation_take_error (ctx->invocation, error);
-    else {
+    } else {
         /* Transition from Unknown->Sent or Stored->Sent */
         if (mm_gdbus_sms_get_state (MM_GDBUS_SMS (ctx->self)) == MM_SMS_STATE_UNKNOWN ||
             mm_gdbus_sms_get_state (MM_GDBUS_SMS (ctx->self)) == MM_SMS_STATE_STORED) {
@@ -464,10 +557,12 @@
     /* If the message is a multipart message, we need to set a proper
      * multipart reference. When sending a message which wasn't stored
      * yet, we can just get a random multipart reference. */
-    self->priv->multipart_reference = g_random_int_range (1,255);
-    for (l = self->priv->parts; l; l = g_list_next (l)) {
-        mm_sms_part_set_concat_reference ((MMSmsPart *)l->data,
-                                          self->priv->multipart_reference);
+    if (self->priv->is_multipart) {
+        self->priv->multipart_reference = g_random_int_range (1,255);
+        for (l = self->priv->parts; l; l = g_list_next (l)) {
+            mm_sms_part_set_concat_reference ((MMSmsPart *)l->data,
+                                              self->priv->multipart_reference);
+        }
     }
 
     return TRUE;
@@ -703,7 +798,7 @@
 
         /* AT+CMGW=<length>[, <stat>]<CR> PDU can be entered. <CTRL-Z>/<ESC> */
 
-        pdu = mm_sms_part_get_submit_pdu (part, &pdulen, &msgstart, error);
+        pdu = mm_sms_part_3gpp_get_submit_pdu (part, &pdulen, &msgstart, error);
         if (!pdu)
             /* 'error' should already be set */
             return FALSE;
@@ -1499,6 +1594,8 @@
                                                         g_byte_array_ref (fulldata)),
                   "smsc",      mm_sms_part_get_smsc (sorted_parts[0]),
                   "class",     mm_sms_part_get_class (sorted_parts[0]),
+                  "teleservice-id",   mm_sms_part_get_cdma_teleservice_id (sorted_parts[0]),
+                  "service-category", mm_sms_part_get_cdma_service_category (sorted_parts[0]),
                   "number",    mm_sms_part_get_number (sorted_parts[0]),
                   "validity",                (validity_relative ?
                                               g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_RELATIVE, g_variant_new_uint32 (validity_relative)) :
@@ -1725,7 +1822,9 @@
                   "state",    MM_SMS_STATE_UNKNOWN,
                   "storage",  MM_SMS_STORAGE_UNKNOWN,
                   "number",   mm_sms_properties_get_number (properties),
-                  "pdu-type", MM_SMS_PDU_TYPE_SUBMIT,
+                  "pdu-type", (mm_sms_properties_get_teleservice_id (properties) == MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN ?
+                               MM_SMS_PDU_TYPE_SUBMIT :
+                               MM_SMS_PDU_TYPE_CDMA_SUBMIT),
                   "text",     text,
                   "data",     (data ?
                                g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
@@ -1737,7 +1836,10 @@
                                NULL),
                   "smsc",     mm_sms_properties_get_smsc (properties),
                   "class",    mm_sms_properties_get_class (properties),
+                  "teleservice-id",          mm_sms_properties_get_teleservice_id (properties),
+                  "service-category",        mm_sms_properties_get_service_category (properties),
                   "delivery-report-request", mm_sms_properties_get_delivery_report_request (properties),
+                  "delivery-state",          MM_SMS_DELIVERY_STATE_UNKNOWN,
                   "validity", (mm_sms_properties_get_validity_type (properties) == MM_SMS_VALIDITY_TYPE_RELATIVE ?
                                g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_RELATIVE, g_variant_new_uint32 (mm_sms_properties_get_validity_relative (properties))) :
                                g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_UNKNOWN, g_variant_new_boolean (FALSE))),
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index dc58366..2d13f25 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -5,7 +5,8 @@
 	test-charsets \
 	test-qcdm-serial-port \
 	test-at-serial-port \
-	test-sms-part
+	test-sms-part-3gpp \
+	test-sms-part-cdma
 
 if WITH_QMI
 noinst_PROGRAMS += test-modem-helpers-qmi
@@ -139,10 +140,10 @@
 
 ################
 
-test_sms_part_SOURCES = \
-	test-sms-part.c
+test_sms_part_3gpp_SOURCES = \
+	test-sms-part-3gpp.c
 
-test_sms_part_CPPFLAGS = \
+test_sms_part_3gpp_CPPFLAGS = \
 	$(MM_CFLAGS) \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/src \
@@ -152,11 +153,35 @@
 	-I$(top_srcdir)/libmm-glib/generated \
 	-I$(top_builddir)/libmm-glib/generated
 
-test_sms_part_LDADD = \
+test_sms_part_3gpp_LDADD = \
 	$(top_builddir)/src/libmodem-helpers.la \
 	$(MM_LIBS)
 
 if WITH_QMI
-test_sms_part_CPPFLAGS += $(QMI_CFLAGS)
-test_sms_part_LDADD += $(QMI_LIBS)
+test_sms_part_3gpp_CPPFLAGS += $(QMI_CFLAGS)
+test_sms_part_3gpp_LDADD += $(QMI_LIBS)
+endif
+
+################
+
+test_sms_part_cdma_SOURCES = \
+	test-sms-part-cdma.c
+
+test_sms_part_cdma_CPPFLAGS = \
+	$(MM_CFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir)/include \
+	-I$(top_srcdir)/libmm-glib \
+	-I$(top_srcdir)/libmm-glib/generated \
+	-I$(top_builddir)/libmm-glib/generated
+
+test_sms_part_cdma_LDADD = \
+	$(top_builddir)/src/libmodem-helpers.la \
+	$(MM_LIBS)
+
+if WITH_QMI
+test_sms_part_cdma_CPPFLAGS += $(QMI_CFLAGS)
+test_sms_part_cdma_LDADD += $(QMI_LIBS)
 endif
diff --git a/src/tests/test-at-serial-port.c b/src/tests/test-at-serial-port.c
index 1e7ff45..0b5f506 100644
--- a/src/tests/test-at-serial-port.c
+++ b/src/tests/test-at-serial-port.c
@@ -71,7 +71,17 @@
          const char *fmt,
          ...)
 {
+#if defined ENABLE_TEST_MESSAGE_TRACES
     /* Dummy log function */
+    va_list args;
+    gchar *msg;
+
+    va_start (args, fmt);
+    msg = g_strdup_vprintf (fmt, args);
+    va_end (args);
+    g_print ("%s\n", msg);
+    g_free (msg);
+#endif
 }
 
 int main (int argc, char **argv)
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index 88ed035..b4ff8a2 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -1707,6 +1707,20 @@
     test_cgdcont_test_results ("Multiple and Ignore", reply, &expected[0], G_N_ELEMENTS (expected));
 }
 
+static void
+test_cgdcont_test_response_single_context (void *f, gpointer d)
+{
+    const gchar *reply =
+        "+CGDCONT: (1),\"IP\",,,(0),(0)\r\n"
+        "+CGDCONT: (1),\"IPV6\",,,(0),(0)\r\n";
+    static MM3gppPdpContextFormat expected[] = {
+        { 1, 1, MM_BEARER_IP_FAMILY_IPV4 },
+        { 1, 1, MM_BEARER_IP_FAMILY_IPV6 }
+    };
+
+    test_cgdcont_test_results ("Single Context", reply, &expected[0], G_N_ELEMENTS (expected));
+}
+
 /*****************************************************************************/
 /* Test CGDCONT read responses */
 
@@ -2317,7 +2331,17 @@
          const char *fmt,
          ...)
 {
+#if defined ENABLE_TEST_MESSAGE_TRACES
     /* Dummy log function */
+    va_list args;
+    gchar *msg;
+
+    va_start (args, fmt);
+    msg = g_strdup_vprintf (fmt, args);
+    va_end (args);
+    g_print ("%s\n", msg);
+    g_free (msg);
+#endif
 }
 
 #define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (GTestFixtureFunc) t, NULL)
@@ -2433,6 +2457,7 @@
     g_test_suite_add (suite, TESTCASE (test_cgdcont_test_response_single, NULL));
     g_test_suite_add (suite, TESTCASE (test_cgdcont_test_response_multiple, NULL));
     g_test_suite_add (suite, TESTCASE (test_cgdcont_test_response_multiple_and_ignore, NULL));
+    g_test_suite_add (suite, TESTCASE (test_cgdcont_test_response_single_context, NULL));
 
 	g_test_suite_add (suite, TESTCASE (test_cgdcont_read_response_nokia, NULL));
 	g_test_suite_add (suite, TESTCASE (test_cgdcont_read_response_samsung, NULL));
diff --git a/src/tests/test-qcdm-serial-port.c b/src/tests/test-qcdm-serial-port.c
index 1185838..1875cb5 100644
--- a/src/tests/test-qcdm-serial-port.c
+++ b/src/tests/test-qcdm-serial-port.c
@@ -451,7 +451,17 @@
          const char *fmt,
          ...)
 {
+#if defined ENABLE_TEST_MESSAGE_TRACES
     /* Dummy log function */
+    va_list args;
+    gchar *msg;
+
+    va_start (args, fmt);
+    msg = g_strdup_vprintf (fmt, args);
+    va_end (args);
+    g_print ("%s\n", msg);
+    g_free (msg);
+#endif
 }
 
 int main (int argc, char **argv)
diff --git a/src/tests/test-sms-part.c b/src/tests/test-sms-part-3gpp.c
similarity index 89%
rename from src/tests/test-sms-part.c
rename to src/tests/test-sms-part-3gpp.c
index 33287e2..58de8a6 100644
--- a/src/tests/test-sms-part.c
+++ b/src/tests/test-sms-part-3gpp.c
@@ -23,7 +23,7 @@
 #define _LIBMM_INSIDE_MM
 #include <libmm-glib.h>
 
-#include "mm-sms-part.h"
+#include "mm-sms-part-3gpp.h"
 #include "mm-log.h"
 
 /* If defined will print debugging traces */
@@ -58,7 +58,7 @@
     MMSmsPart *part;
     GError *error = NULL;
 
-    part = mm_sms_part_new_from_pdu (0, hexpdu, &error);
+    part = mm_sms_part_3gpp_new_from_pdu (0, hexpdu, &error);
     g_assert_no_error (error);
     g_assert (part != NULL);
 
@@ -356,7 +356,7 @@
     };
 
     hexpdu = mm_utils_bin2hexstr (pdu, sizeof (pdu));
-    part = mm_sms_part_new_from_pdu (0, hexpdu, &error);
+    part = mm_sms_part_3gpp_new_from_pdu (0, hexpdu, &error);
     g_assert (part == NULL);
     /* We don't care for the specific error type */
     g_assert (error != NULL);
@@ -464,7 +464,7 @@
     guint8 buf[20];
     gsize enclen;
 
-    enclen = mm_sms_part_encode_address (address, buf, sizeof (buf), smsc);
+    enclen = mm_sms_part_3gpp_encode_address (address, buf, sizeof (buf), smsc);
     g_assert_cmpuint (enclen, ==, expected_size);
     g_assert_cmpint (memcmp (buf, expected, expected_size), ==, 0);
 }
@@ -531,7 +531,7 @@
         MMSmsEncoding encoding = MM_SMS_ENCODING_UNKNOWN;
 
         /* Detect best encoding */
-        mm_sms_part_util_split_text (text, &encoding);
+        mm_sms_part_3gpp_util_split_text (text, &encoding);
         mm_sms_part_set_text (part, text);
         mm_sms_part_set_encoding (part, encoding);
     }
@@ -540,10 +540,10 @@
     if (class >= 0)
         mm_sms_part_set_class (part, class);
 
-    pdu = mm_sms_part_get_submit_pdu (part,
-                                      &len,
-                                      &msgstart,
-                                      &error);
+    pdu = mm_sms_part_3gpp_get_submit_pdu (part,
+                                           &len,
+                                           &msgstart,
+                                           &error);
 
     trace_pdu (pdu, len);
 
@@ -716,7 +716,7 @@
     MMSmsEncoding out_encoding = MM_SMS_ENCODING_UNKNOWN;
     guint i;
 
-    out = mm_sms_part_util_split_text (text, &out_encoding);
+    out = mm_sms_part_3gpp_util_split_text (text, &out_encoding);
 
     g_assert (out != NULL);
     g_assert (out_encoding != MM_SMS_ENCODING_UNKNOWN);
@@ -862,39 +862,39 @@
     g_type_init ();
     g_test_init (&argc, &argv, NULL);
 
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu1", test_pdu1);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu2", test_pdu2);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu3", test_pdu3);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu3-nonzero-pid", test_pdu3_nzpid);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu3-mms", test_pdu3_mms);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu3-natl", test_pdu3_natl);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu3-8bit", test_pdu3_8bit);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu-dcsf1", test_pdu_dcsf1);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu-dcsf-8bit", test_pdu_dcsf_8bit);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu-insufficient-data", test_pdu_insufficient_data);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu-udhi", test_pdu_udhi);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu-multipart", test_pdu_multipart);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu-stored-by-us", test_pdu_stored_by_us);
-    g_test_add_func ("/MM/SMS/PDU-Parser/pdu-not-stored", test_pdu_not_stored);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu1", test_pdu1);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu2", test_pdu2);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu3", test_pdu3);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu3-nonzero-pid", test_pdu3_nzpid);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu3-mms", test_pdu3_mms);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu3-natl", test_pdu3_natl);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu3-8bit", test_pdu3_8bit);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu-dcsf1", test_pdu_dcsf1);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu-dcsf-8bit", test_pdu_dcsf_8bit);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu-insufficient-data", test_pdu_insufficient_data);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu-udhi", test_pdu_udhi);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu-multipart", test_pdu_multipart);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu-stored-by-us", test_pdu_stored_by_us);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Parser/pdu-not-stored", test_pdu_not_stored);
 
-    g_test_add_func ("/MM/SMS/Address-Encoder/smsc-intl", test_address_encode_smsc_intl);
-    g_test_add_func ("/MM/SMS/Address-Encoder/smsc-unknown", test_address_encode_smsc_unknown);
-    g_test_add_func ("/MM/SMS/Address-Encoder/intl", test_address_encode_intl);
-    g_test_add_func ("/MM/SMS/Address-Encoder/unknown", test_address_encode_unknown);
+    g_test_add_func ("/MM/SMS/3GPP/Address-Encoder/smsc-intl", test_address_encode_smsc_intl);
+    g_test_add_func ("/MM/SMS/3GPP/Address-Encoder/smsc-unknown", test_address_encode_smsc_unknown);
+    g_test_add_func ("/MM/SMS/3GPP/Address-Encoder/intl", test_address_encode_intl);
+    g_test_add_func ("/MM/SMS/3GPP/Address-Encoder/unknown", test_address_encode_unknown);
 
-    g_test_add_func ("/MM/SMS/PDU-Creator/UCS2-with-smsc", test_create_pdu_ucs2_with_smsc);
-    g_test_add_func ("/MM/SMS/PDU-Creator/UCS2-no-smsc", test_create_pdu_ucs2_no_smsc);
-    g_test_add_func ("/MM/SMS/PDU-Creator/GSM-with-smsc", test_create_pdu_gsm_with_smsc);
-    g_test_add_func ("/MM/SMS/PDU-Creator/GSM-no-smsc", test_create_pdu_gsm_no_smsc);
-    g_test_add_func ("/MM/SMS/PDU-Creator/GSM-3", test_create_pdu_gsm_3);
-    g_test_add_func ("/MM/SMS/PDU-Creator/GSM-no-validity", test_create_pdu_gsm_no_validity);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Creator/UCS2-with-smsc", test_create_pdu_ucs2_with_smsc);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Creator/UCS2-no-smsc", test_create_pdu_ucs2_no_smsc);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Creator/GSM-with-smsc", test_create_pdu_gsm_with_smsc);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Creator/GSM-no-smsc", test_create_pdu_gsm_no_smsc);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Creator/GSM-3", test_create_pdu_gsm_3);
+    g_test_add_func ("/MM/SMS/3GPP/PDU-Creator/GSM-no-validity", test_create_pdu_gsm_no_validity);
 
-    g_test_add_func ("/MM/SMS/Text-Split/short", test_text_split_short);
-    g_test_add_func ("/MM/SMS/Text-Split/short-UCS2", test_text_split_short_ucs2);
-    g_test_add_func ("/MM/SMS/Text-Split/max-single-pdu", test_text_split_max_single_pdu);
-    g_test_add_func ("/MM/SMS/Text-Split/max-single-pdu-UCS2", test_text_split_max_single_pdu_ucs2);
-    g_test_add_func ("/MM/SMS/Text-Split/two-pdu", test_text_split_two_pdu);
-    g_test_add_func ("/MM/SMS/Text-Split/two-pdu-UCS2", test_text_split_two_pdu_ucs2);
+    g_test_add_func ("/MM/SMS/3GPP/Text-Split/short", test_text_split_short);
+    g_test_add_func ("/MM/SMS/3GPP/Text-Split/short-UCS2", test_text_split_short_ucs2);
+    g_test_add_func ("/MM/SMS/3GPP/Text-Split/max-single-pdu", test_text_split_max_single_pdu);
+    g_test_add_func ("/MM/SMS/3GPP/Text-Split/max-single-pdu-UCS2", test_text_split_max_single_pdu_ucs2);
+    g_test_add_func ("/MM/SMS/3GPP/Text-Split/two-pdu", test_text_split_two_pdu);
+    g_test_add_func ("/MM/SMS/3GPP/Text-Split/two-pdu-UCS2", test_text_split_two_pdu_ucs2);
 
     return g_test_run ();
 }
diff --git a/src/tests/test-sms-part-cdma.c b/src/tests/test-sms-part-cdma.c
new file mode 100644
index 0000000..644de8d
--- /dev/null
+++ b/src/tests/test-sms-part-cdma.c
@@ -0,0 +1,547 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2013 Google, Inc.
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-sms-part-cdma.h"
+#include "mm-log.h"
+
+/* If defined will print debugging traces */
+#ifdef TEST_SMS_PART_ENABLE_TRACE
+#define trace_pdu(pdu, pdu_len) do {      \
+        guint i;                          \
+                                          \
+        g_print ("\n        ");           \
+        for (i = 0; i < len; i++) {       \
+            g_print ("  0x%02X", pdu[i]); \
+            if (((i + 1) % 12) == 0)      \
+                g_print ("\n        ");   \
+        }                                 \
+        g_print ("\n");                   \
+    } while (0)
+#else
+#define trace_pdu(...)
+#endif
+
+/********************* PDU PARSER TESTS *********************/
+
+static void
+common_test_part_from_hexpdu (const gchar *hexpdu,
+                              MMSmsCdmaTeleserviceId expected_teleservice_id,
+                              MMSmsCdmaServiceCategory expected_service_category,
+                              const gchar *expected_address,
+                              guint8 expected_bearer_reply_option,
+                              const gchar *expected_text)
+{
+    MMSmsPart *part;
+    GError *error = NULL;
+
+    mm_dbg (" ");
+    part = mm_sms_part_cdma_new_from_pdu (0, hexpdu, &error);
+    g_assert_no_error (error);
+    g_assert (part != NULL);
+
+    if (expected_teleservice_id != MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN)
+        g_assert_cmpuint (expected_teleservice_id, ==, mm_sms_part_get_cdma_teleservice_id (part));
+    if (expected_service_category != MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN)
+        g_assert_cmpuint (expected_service_category, ==, mm_sms_part_get_cdma_service_category (part));
+    if (expected_address) {
+        if (expected_address[0])
+            g_assert_cmpstr (expected_address, ==, mm_sms_part_get_number (part));
+        else
+            g_assert (mm_sms_part_get_number (part) == NULL);
+    }
+    if (expected_bearer_reply_option)
+        g_assert_cmpuint (expected_bearer_reply_option, ==, mm_sms_part_get_message_reference (part));
+    if (expected_text)
+        g_assert_cmpstr (expected_text, ==, mm_sms_part_get_text (part));
+
+    mm_sms_part_free (part);
+}
+
+static void
+common_test_part_from_pdu (const guint8 *pdu,
+                           gsize pdu_size,
+                           MMSmsCdmaTeleserviceId expected_teleservice_id,
+                           MMSmsCdmaServiceCategory expected_service_category,
+                           const gchar *expected_address,
+                           guint8 expected_bearer_reply_option,
+                           const gchar *expected_text)
+{
+    gchar *hexpdu;
+
+    hexpdu = mm_utils_bin2hexstr (pdu, pdu_size);
+    common_test_part_from_hexpdu (hexpdu,
+                                  expected_teleservice_id,
+                                  expected_service_category,
+                                  expected_address,
+                                  expected_bearer_reply_option,
+                                  expected_text);
+    g_free (hexpdu);
+}
+
+static void
+common_test_invalid_part_from_hexpdu (const gchar *hexpdu)
+{
+    MMSmsPart *part;
+    GError *error = NULL;
+
+    mm_dbg (" ");
+    part = mm_sms_part_cdma_new_from_pdu (0, hexpdu, &error);
+    g_assert (part == NULL);
+    /* We don't care for the specific error type */
+    g_assert (error != NULL);
+    g_error_free (error);
+}
+
+static void
+common_test_invalid_part_from_pdu (const guint8 *pdu,
+                                   gsize pdu_size)
+{
+    gchar *hexpdu;
+
+    hexpdu = mm_utils_bin2hexstr (pdu, pdu_size);
+    common_test_invalid_part_from_hexpdu (hexpdu);
+    g_free (hexpdu);
+}
+
+static void
+test_pdu1 (void)
+{
+    static const guint8 pdu[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* originating address */
+        0x02, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer reply option */
+        0x06, 0x01,
+        0xFC,
+        /* bearer data */
+        0x08, 0x15,
+        0x00, 0x03, 0x16, 0x8D, 0x30, 0x01, 0x06,
+        0x10, 0x24, 0x18, 0x30, 0x60, 0x80, 0x03,
+        0x06, 0x10, 0x10, 0x04, 0x04, 0x48, 0x47
+    };
+
+    common_test_part_from_pdu (
+        pdu, sizeof (pdu),
+        MM_SMS_CDMA_TELESERVICE_ID_WMT,
+        MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
+        "3305773196",
+        63,
+        "AAAA");
+}
+
+static void
+test_invalid_parameter_length (void)
+{
+    static const guint8 pdu[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* originating address */
+        0x02, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer reply option */
+        0x06, 0x01,
+        0xFC,
+        /* bearer data */
+        0x08, 0x20, /* wrong parameter length! */
+        0x00, 0x03, 0x16, 0x8D, 0x30, 0x01, 0x06,
+        0x10, 0x24, 0x18, 0x30, 0x60, 0x80, 0x03,
+        0x06, 0x10, 0x10, 0x04, 0x04, 0x48, 0x47
+    };
+
+    common_test_invalid_part_from_pdu (pdu, sizeof (pdu));
+}
+
+static void
+test_invalid_address_length (void)
+{
+    static const guint8 pdu[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* originating address (wrong num_fields) */
+        0x02, 0x07,
+        0x03, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer reply option */
+        0x06, 0x01,
+        0xFC,
+        /* bearer data */
+        0x08, 0x15,
+        0x00, 0x03, 0x16, 0x8D, 0x30, 0x01, 0x06,
+        0x10, 0x24, 0x18, 0x30, 0x60, 0x80, 0x03,
+        0x06, 0x10, 0x10, 0x04, 0x04, 0x48, 0x47
+    };
+
+    common_test_part_from_pdu (
+        pdu, sizeof (pdu),
+        MM_SMS_CDMA_TELESERVICE_ID_WMT,
+        MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
+        "",
+        63,
+        NULL);
+}
+
+static void
+test_created_by_us (void)
+{
+    static const guint8 pdu[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* destination address */
+        0x04, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer data */
+        0x08, 0x0D,
+        0x00, 0x03, 0x20, 0x00, 0x00, /* message id */
+        0x01, 0x06, 0x10, 0x24, 0x18, 0x30, 0x60, 0x80 /* user_data */
+    };
+
+    common_test_part_from_pdu (
+        pdu, sizeof (pdu),
+        MM_SMS_CDMA_TELESERVICE_ID_WMT,
+        MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
+        "3305773196",
+        0,
+        "AAAA");
+}
+
+static void
+test_latin_encoding (void)
+{
+    static const guint8 pdu[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* originating address */
+        0x02, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer reply option */
+        0x06, 0x01,
+        0xFC,
+        /* bearer data */
+        0x08, 0x39,
+            /* message id */
+            0x00, 0x03,
+            0x13, 0x8D, 0x20,
+            /* user data */
+            0x01, 0x27,
+            0x41, 0x29, 0x19, 0x22, 0xE1, 0x19, 0x1A, 0xE1,
+            0x1A, 0x01, 0x19, 0xA1, 0x19, 0xA1, 0xA9, 0xB1,
+            0xB9, 0xE9, 0x53, 0x4B, 0x23, 0xAB, 0x53, 0x23,
+            0xAB, 0x23, 0x2B, 0xAB, 0xAB, 0x2B, 0x23, 0xAB,
+            0x53, 0x23, 0x2B, 0xAB, 0x53, 0xAB, 0x20,
+            /* message center timestamp */
+            0x03, 0x06,
+            0x13, 0x10, 0x23, 0x20, 0x06, 0x37,
+            /* priority indicator */
+            0x08, 0x01,
+            0x00
+    };
+
+    common_test_part_from_pdu (
+        pdu, sizeof (pdu),
+        MM_SMS_CDMA_TELESERVICE_ID_WMT,
+        MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
+        "3305773196",
+        63,
+        /* this is ASCII-7 but message uses latin encoding */
+        "#$\\##\\#@#4#4567=*idujdudeuuedujdeujud");
+}
+
+static void
+test_latin_encoding_2 (void)
+{
+    static const guint8 pdu[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* originating address */
+        0x02, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer reply option */
+        0x06, 0x01,
+        0xFC,
+        /* bearer data */
+        0x08, 0x1C,
+            /* message id */
+            0x00, 0x03,
+            0x13, 0x8D, 0x20,
+            /* user data */
+            0x01, 0x0A,
+            0x40, 0x42, 0x1B, 0x0B, 0x6B, 0x83, 0x2F, 0x9B,
+            0x71, 0x08,
+            /* message center timestamp */
+            0x03, 0x06,
+            0x13, 0x10, 0x23, 0x20, 0x06, 0x37,
+            /* priority indicator */
+            0x08, 0x01,
+            0x00
+    };
+
+    common_test_part_from_pdu (
+        pdu, sizeof (pdu),
+        MM_SMS_CDMA_TELESERVICE_ID_WMT,
+        MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
+        "3305773196",
+        63,
+        /* this is latin and message uses latin encoding */
+        "Campeón!");
+}
+
+static void
+test_unicode_encoding (void)
+{
+    static const guint8 pdu[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* originating address */
+        0x02, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer reply option */
+        0x06, 0x01,
+        0xFC,
+        /* bearer data */
+        0x08, 0x28,
+            /* message id */
+            0x00, 0x03,
+            0x1B, 0x73, 0xF0,
+            /* user data */
+            0x01, 0x16,
+            0x20, 0x52, 0x71, 0x6A, 0xB8, 0x5A, 0xA7, 0x92,
+            0xDB, 0xC3, 0x37, 0xC4, 0xB7, 0xDA, 0xDA, 0x82,
+            0x98, 0xB4, 0x50, 0x42, 0x94, 0x18,
+            /* message center timestamp */
+            0x03, 0x06,
+            0x13, 0x10, 0x24, 0x10, 0x45, 0x28,
+            /* priority indicator */
+            0x08, 0x01,
+            0x00
+    };
+
+    common_test_part_from_pdu (
+        pdu, sizeof (pdu),
+        MM_SMS_CDMA_TELESERVICE_ID_WMT,
+        MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
+        "3305773196",
+        63,
+        "中國哲學書電子化計劃");
+}
+
+/********************* PDU CREATOR TESTS *********************/
+
+static void
+common_test_create_pdu (MMSmsCdmaTeleserviceId teleservice_id,
+                        const gchar *number,
+                        const gchar *text,
+                        const guint8 *data,
+                        gsize data_size,
+                        const guint8 *expected,
+                        gsize expected_size)
+{
+    MMSmsPart *part;
+    guint8 *pdu;
+    guint len = 0;
+    GError *error = NULL;
+
+    g_assert (number != NULL);
+
+    part = mm_sms_part_new (0, MM_SMS_PDU_TYPE_CDMA_SUBMIT);
+    mm_sms_part_set_cdma_teleservice_id (part, teleservice_id);
+    mm_sms_part_set_number (part, number);
+    if (text)
+        mm_sms_part_set_text (part, text);
+    else {
+        GByteArray *data_bytearray;
+
+        data_bytearray = g_byte_array_sized_new (data_size);
+        g_byte_array_append (data_bytearray, data, data_size);
+        mm_sms_part_take_data (part, data_bytearray);
+    }
+
+    pdu = mm_sms_part_cdma_get_submit_pdu (part, &len, &error);
+
+    trace_pdu (pdu, len);
+
+    g_assert_no_error (error);
+    g_assert (pdu != NULL);
+    g_assert_cmpuint (len, ==, expected_size);
+    g_assert_cmpint (memcmp (pdu, expected, len), ==, 0);
+
+    g_free (pdu);
+}
+
+static void
+test_create_pdu_text_ascii_encoding (void)
+{
+    static const char *number = "3305773196";
+    static const char *text = "AAAA";
+    static const guint8 expected[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* destination address */
+        0x04, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer data */
+        0x08, 0x0D,
+        0x00, 0x03, 0x20, 0x00, 0x00, /* message id */
+        0x01, 0x06, 0x10, 0x24, 0x18, 0x30, 0x60, 0x80 /* user_data */
+    };
+
+    common_test_create_pdu (MM_SMS_CDMA_TELESERVICE_ID_WMT,
+                            number,
+                            text,
+                            NULL, 0,
+                            expected, sizeof (expected));
+}
+
+static void
+test_create_pdu_text_latin_encoding (void)
+{
+    static const char *number = "3305773196";
+    static const char *text = "Campeón!";
+    static const guint8 expected[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* destination address */
+        0x04, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer data */
+        0x08, 0x11,
+            /* message id */
+            0x00, 0x03,
+            0x20, 0x00, 0x00,
+            /* user data */
+            0x01, 0x0A,
+            0x40, 0x42, 0x1B, 0x0B, 0x6B, 0x83, 0x2F, 0x9B,
+            0x71, 0x08
+    };
+
+    common_test_create_pdu (MM_SMS_CDMA_TELESERVICE_ID_WMT,
+                            number,
+                            text,
+                            NULL, 0,
+                            expected, sizeof (expected));
+}
+
+static void
+test_create_pdu_text_unicode_encoding (void)
+{
+    static const char *number = "3305773196";
+    static const char *text = "中國哲學書電子化計劃";
+    static const guint8 expected[] = {
+        /* message type */
+        0x00,
+        /* teleservice id */
+        0x00, 0x02,
+        0x10, 0x02,
+        /* destination address */
+        0x04, 0x07,
+        0x02, 0x8C, 0xE9, 0x5D, 0xCC, 0x65, 0x80,
+        /* bearer data */
+        0x08, 0x1D,
+            /* message id */
+            0x00, 0x03,
+            0x20, 0x00, 0x00,
+            /* user data */
+            0x01, 0x16,
+            0x20, 0x52, 0x71, 0x6A, 0xB8, 0x5A, 0xA7, 0x92,
+            0xDB, 0xC3, 0x37, 0xC4, 0xB7, 0xDA, 0xDA, 0x82,
+            0x98, 0xB4, 0x50, 0x42, 0x94, 0x18
+    };
+
+    common_test_create_pdu (MM_SMS_CDMA_TELESERVICE_ID_WMT,
+                            number,
+                            text,
+                            NULL, 0,
+                            expected, sizeof (expected));
+}
+
+/************************************************************/
+
+void
+_mm_log (const char *loc,
+         const char *func,
+         guint32 level,
+         const char *fmt,
+         ...)
+{
+#if defined ENABLE_TEST_MESSAGE_TRACES
+    /* Dummy log function */
+    va_list args;
+    gchar *msg;
+
+    va_start (args, fmt);
+    msg = g_strdup_vprintf (fmt, args);
+    va_end (args);
+    g_print ("%s\n", msg);
+    g_free (msg);
+#endif
+}
+
+int main (int argc, char **argv)
+{
+    setlocale (LC_ALL, "");
+
+    g_type_init ();
+    g_test_init (&argc, &argv, NULL);
+
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/pdu1", test_pdu1);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/invalid-parameter-length", test_invalid_parameter_length);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/invalid-address-length", test_invalid_address_length);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/created-by-us", test_created_by_us);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/latin-encoding", test_latin_encoding);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/latin-encoding-2", test_latin_encoding_2);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Parser/unicode-encoding", test_unicode_encoding);
+
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Creator/ascii-encoding", test_create_pdu_text_ascii_encoding);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Creator/latin-encoding", test_create_pdu_text_latin_encoding);
+    g_test_add_func ("/MM/SMS/CDMA/PDU-Creator/unicode-encoding", test_create_pdu_text_unicode_encoding);
+
+    return g_test_run ();
+}