From ff2d7b76bf4878f47a2df1261219c9d9e14f8c9e Mon Sep 17 00:00:00 2001
From: Dylan Van Assche <me@dylanvanassche.be>
Date: Wed, 13 Apr 2022 19:56:41 +0200
Subject: [PATCH 08/26] bluetooth: support AT+CMEE

AT+CMEE is now supported and extended error messages are returned
when an ERROR occurs. By default, these error codes are disabled,
the HF must enable them explicitely with AT+CMEE=1 (numeric mode).
---
 src/modules/bluetooth/backend-native.c | 71 +++++++++++++++++++++-----
 src/modules/bluetooth/bluez5-util.h    |  6 +++
 2 files changed, 65 insertions(+), 12 deletions(-)

diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index 77792146a..3110c5ef2 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -57,6 +57,7 @@ struct pa_bluetooth_backend {
   bool enable_hsp_hs;
   bool enable_hfp_hf;
   bool cmer_indicator_reporting_enabled;
+  bool cmee_extended_error_reporting_enabled;
   uint32_t cind_enabled_indicators;
 
   PA_LLIST_HEAD(pa_dbus_pending, pending);
@@ -125,7 +126,7 @@ typedef enum pa_bluetooth_ag_to_hf_indicators {
 /* gateway features we support, which is as little as we can get away with */
 static uint32_t hfp_features =
     /* HFP 1.6 requires this */
-    (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS);
+    (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS) | (1 << HFP_AG_INDICATORS) | (1 << HFP_AG_EERR);
 
 #define HSP_AG_PROFILE "/Profile/HSPAGProfile"
 #define HFP_AG_PROFILE "/Profile/HFPAGProfile"
@@ -256,6 +257,14 @@ static void rfcomm_write_response(int fd, const char *fmt, ...)
     va_end(ap);
 }
 
+/* Send Extended Error messages if enabled */
+static void rfcomm_write_error(pa_bluetooth_backend *b, int fd, unsigned int error) {
+    if (b->cmee_extended_error_reporting_enabled)
+        rfcomm_write_response(fd, "+CME ERROR: %d", error);
+    else
+        rfcomm_write_response(fd, "ERROR");
+}
+
 static int sco_setsockopt_enable_bt_voice(pa_bluetooth_transport *t, int fd) {
     /* the mSBC codec requires a special transparent eSCO connection */
     struct bt_voice voice;
@@ -621,6 +630,30 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
     size_t len;
     const char *state;
 
+    /* Extended error reporting. Described in Bluetooth HFP 1.8 spec. */
+    if (sscanf(buf, "AT+CMEE=%d", &val) == 1) {
+        switch(val) {
+            /* Disable extended error reporting */
+            case 0:
+                discovery->native_backend->cmee_extended_error_reporting_enabled = false;
+                return true;
+            /* Enable extend error reporting in numeric mode */
+            case 1:
+                discovery->native_backend->cmee_extended_error_reporting_enabled = true;
+                return true;
+            /* Enable extend error reporting in verbose mode */
+            case 2:
+                pa_log_warn("Extended Error Reporting verbose mode is unsupported by Bluetooth HFP 1.8");
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_SUPPORTED);
+                return false;
+            default:
+                pa_log_warn("Unknown AT+CMEE value: %d", val);
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
+                return false;
+	}
+        pa_assert_not_reached();
+    }
+
     /* first-time initialize selected codec to CVSD */
     if (c->selected_codec == 0)
         c->selected_codec = 1;
@@ -655,7 +688,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
                 discovery->native_backend->cind_enabled_indicators &= ~(1 << indicator);
 	    else {
                 pa_log_error("Unable to parse indicator of AT+BIA command: %s", buf);
-                rfcomm_write_response(fd, "ERROR");
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
 	        return false;
             }
 
@@ -741,7 +774,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         }
         else {
             pa_log_error("Unable to parse AT+CMER command: %s", buf);
-            rfcomm_write_response(fd, "ERROR");
+            rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
 	    return false;
         }
 
@@ -767,7 +800,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
             pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, sco_setsockopt_enable_bt_voice);
         } else {
             pa_assert_fp(val != 1 && val != 2);
-            rfcomm_write_response(fd, "ERROR");
+            rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
             return false;
         }
 
@@ -798,14 +831,14 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
                 pa_log_notice("Battery Level: %d%%", val);
                 if (val < 0 || val > 100) {
                     pa_log_error("Battery HF indicator %d out of [0, 100] range", val);
-                    rfcomm_write_response(fd, "ERROR");
+                    rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
                     return false;
                 }
                 pa_bluetooth_device_report_battery_level(t->device, val, "HFP 1.7 HF indicator");
                 break;
             default:
                 pa_log_error("Unknown HF indicator %u", indicator);
-                rfcomm_write_response(fd, "ERROR");
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_SUPPORTED);
                 return false;
         }
         return true;
@@ -819,12 +852,23 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
     if (c->state != 5) {
         pa_log_error("HFP negotiation failed in state %d with inbound %s\n",
                      c->state, buf);
-        rfcomm_write_response(fd, "ERROR");
+        rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
         return false;
     }
 
-    /* Unsupported commands return ERROR */
-    return false
+    /* 
+     * Unsupported commands return ERROR.
+     * Unsupported AT commands from HFP 1.8:
+     *  - AT+BINP
+     *  - AT+BLDN
+     *  - ATD>
+     *  - AT+BVRA
+     *  - AT+BTRH
+     *  - AT+CHLD
+     *  - AT+CCWA
+     */
+    rfcomm_write_error(discovery->native_backend, fd, CMEE_OPERATION_NOT_SUPPORTED);
+    return false;
 }
 
 static int get_rfcomm_fd (pa_bluetooth_discovery *discovery) {
@@ -996,12 +1040,13 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
                         break;
                 }
             }
-            if (!do_reply)
-                rfcomm_write_response(fd, "ERROR");
+            if (!do_reply) {
+                rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
+            }
         } else if (t->config) { /* t->config is only non-null for hfp profile */
             do_reply = hfp_rfcomm_handle(fd, t, buf);
         } else {
-            rfcomm_write_response(fd, "ERROR");
+            rfcomm_write_error(discovery->native_backend, fd, CMEE_AG_FAILURE);
             do_reply = false;
         }
 
@@ -1391,6 +1436,8 @@ pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_d
     /* While all CIND indicators are enabled, event reporting is not enabled by default */
     backend->cmer_indicator_reporting_enabled = false;
 
+    /* CMEE is disabled by default */
+    backend->cmee_extended_error_reporting_enabled = false;
 
     return backend;
 }
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index f69176425..4cc5c2882 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -211,6 +211,12 @@ struct pa_bluetooth_adapter {
     bool battery_provider_registered;
 };
 
+/* extended error reporting values, described in Bluetooth HFP 1.8 spec */
+typedef enum pa_bluetooth_cmee {
+    CMEE_AG_FAILURE = 0,
+    CMEE_OPERATION_NOT_SUPPORTED = 4,
+} pa_bluetooth_cmee_t;
+
 #ifdef HAVE_BLUEZ_5_OFONO_HEADSET
 pa_bluetooth_backend *pa_bluetooth_ofono_backend_new(pa_core *c, pa_bluetooth_discovery *y);
 void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b);
-- 
2.35.1

