Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions examples/ANCS/ANCS.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Original: https://github.com/mathcampbell/ANCS
#include <Arduino.h>
#include "NimBLEDevice.h"

static NimBLEUUID ancsServiceUUID("7905F431-B5CE-4E99-A40F-4B1E122D00D0");
static NimBLEUUID notificationSourceCharacteristicUUID("9FBF120D-6301-42D9-8C58-25E699A21DBD");
static NimBLEUUID controlPointCharacteristicUUID("69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9");
static NimBLEUUID dataSourceCharacteristicUUID("22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB");

static NimBLEClient* pClient;

uint8_t latestMessageID[4];
boolean pendingNotification = false;
boolean incomingCall = false;
uint8_t acceptCall = 0;

static void dataSourceNotifyCallback(NimBLERemoteCharacteristic* pDataSourceCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
// Serial.print("Notify callback for characteristic ");
// Serial.print(pDataSourceCharacteristic->getUUID().toString().c_str());
// Serial.print(" of data length ");
// Serial.println(length);
for (int i = 0; i < length; i++) {
if (i > 7) {
Serial.write(pData[i]);
} else {
Serial.print(pData[i], HEX);
Serial.print(" ");
}
}
Serial.println();
}

static void NotificationSourceNotifyCallback(NimBLERemoteCharacteristic* pNotificationSourceCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
if (pData[0] == 0) {
Serial.println("New notification!");
latestMessageID[0] = pData[4];
latestMessageID[1] = pData[5];
latestMessageID[2] = pData[6];
latestMessageID[3] = pData[7];

switch (pData[2]) {
Comment on lines +36 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Arrr, guard the notification packet before ye index fixed offsets.

pData[0], pData[2], and pData[4..7] are read without checking length. A short or malformed callback payload turns this into an out-of-bounds read in the BLE path.

🛠️ Patch fer this
 static void NotificationSourceNotifyCallback(NimBLERemoteCharacteristic* pNotificationSourceCharacteristic,
                                              uint8_t*                    pData,
                                              size_t                      length,
                                              bool                        isNotify) {
+    if (length < 8) {
+        Serial.printf("Unexpected Notification Source length: %u\n", static_cast<unsigned>(length));
+        return;
+    }
+
     if (pData[0] == 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ANCS/ANCS.ino` around lines 36 - 47, In
NotificationSourceNotifyCallback, guard accesses into pData by first checking
pData != nullptr and that length is at least the number of bytes you read (use
length >= 8 since you read pData[0], pData[2], and pData[4..7]); if the buffer
is too short, log/ignore and return early. Update the start of
NotificationSourceNotifyCallback to validate these conditions before using pData
or writing into latestMessageID to avoid out-of-bounds reads.

case 0:
Serial.println("Category: Other");
break;
case 1:
incomingCall = true;
Serial.println("Category: Incoming call");
break;
case 2:
Serial.println("Category: Missed call");
break;
case 3:
Serial.println("Category: Voicemail");
break;
case 4:
Serial.println("Category: Social");
break;
case 5:
Serial.println("Category: Schedule");
break;
case 6:
Serial.println("Category: Email");
break;
case 7:
Serial.println("Category: News");
break;
case 8:
Serial.println("Category: Health");
break;
case 9:
Serial.println("Category: Business");
break;
case 10:
Serial.println("Category: Location");
break;
case 11:
Serial.println("Category: Entertainment");
break;
default:
break;
}
} else if (pData[0] == 1) {
Serial.println("Notification Modified!");
if (pData[2] == 1) {
Serial.println("Call Changed!");
}
} else if (pData[0] == 2) {
Serial.println("Notification Removed!");
if (pData[2] == 1) {
Serial.println("Call Gone!");
}
}
pendingNotification = true;
Comment on lines +88 to +99
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Arrr, don’t reuse a stale UID for modified/removed events.

pendingNotification is raised for every event, but latestMessageID is only refreshed in the pData[0] == 0 branch. If a modified/removed event arrives first, Lines 172-180 send control-point requests for a stale or all-zero UID; the removed-call branch also never drops incomingCall.

🛠️ Patch fer this
     } else if (pData[0] == 1) {
+        latestMessageID[0] = pData[4];
+        latestMessageID[1] = pData[5];
+        latestMessageID[2] = pData[6];
+        latestMessageID[3] = pData[7];
         Serial.println("Notification Modified!");
         if (pData[2] == 1) {
             Serial.println("Call Changed!");
         }
     } else if (pData[0] == 2) {
         Serial.println("Notification Removed!");
         if (pData[2] == 1) {
             Serial.println("Call Gone!");
+            incomingCall = false;
         }
+        pendingNotification = false;
+        return;
     }
     pendingNotification = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (pData[0] == 1) {
Serial.println("Notification Modified!");
if (pData[2] == 1) {
Serial.println("Call Changed!");
}
} else if (pData[0] == 2) {
Serial.println("Notification Removed!");
if (pData[2] == 1) {
Serial.println("Call Gone!");
}
}
pendingNotification = true;
} else if (pData[0] == 1) {
latestMessageID[0] = pData[4];
latestMessageID[1] = pData[5];
latestMessageID[2] = pData[6];
latestMessageID[3] = pData[7];
Serial.println("Notification Modified!");
if (pData[2] == 1) {
Serial.println("Call Changed!");
}
} else if (pData[0] == 2) {
Serial.println("Notification Removed!");
if (pData[2] == 1) {
Serial.println("Call Gone!");
incomingCall = false;
}
pendingNotification = false;
return;
}
pendingNotification = true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ANCS/ANCS.ino` around lines 88 - 99, The code raises
pendingNotification for every event but only updates latestMessageID in the "new
notification" branch, causing control-point requests to use a stale/all-zero UID
for modified/removed events and never clearing incomingCall on removal. Fix by
extracting and copying the UID from the incoming pData into latestMessageID in
the pData[0] == 1 (modified) and pData[0] == 2 (removed) branches before setting
pendingNotification or issuing control-point actions; for the removed (pData[0]
== 2) branch also clear incomingCall when pData[2] indicates a call gone;
finally, only set pendingNotification when a valid (non-zero) UID was extracted
to avoid using a stale UID. Ensure you reference and update variables
latestMessageID, pendingNotification, and incomingCall in the modified branches.

}

class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
Serial.printf("Client connected: %s\n", connInfo.getAddress().toString().c_str());
pClient = pServer->getClient(connInfo);
Serial.println("Client connected!");
}

void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
Serial.printf("Client disconnected: %s, reason: %d\n", connInfo.getAddress().toString().c_str(), reason);
}
Comment on lines +109 to +111
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Arrr, this session loop never leaves a dead connection.

while (1) ignores link state, and onDisconnect() only logs. After the peer drops, the sketch stays marooned in the old session instead of returning to the normal reconnect path.

🛠️ Patch fer this
     void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
         Serial.printf("Client disconnected: %s, reason: %d\n", connInfo.getAddress().toString().c_str(), reason);
+        pendingNotification = false;
+        incomingCall        = false;
+        acceptCall          = 0;
+        pClient             = nullptr;
     }
-        while (1) {
+        while (pClient != nullptr && pClient->isConnected()) {
-                while (incomingCall) {
+                while (incomingCall && pClient != nullptr && pClient->isConnected()) {

Also applies to: 166-208

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ANCS/ANCS.ino` around lines 109 - 111, The session loop that uses
while(1) never exits when the peer drops because onDisconnect only logs; update
onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) to
signal the main code (e.g., set a volatile/global flag like deviceConnected =
false or call pServer->removePeer(connInfo.getConnHandle()) if appropriate) and
then change the infinite while(1) session loop to check that signal or the
server connection count (use NimBLEServer::getConnCount() or the deviceConnected
flag) and break/return when the connection is lost so the sketch can clean up
and return to the reconnect path.

} serverCallbacks;

void setup() {
Serial.begin(115200);

NimBLEDevice::init("ANCS");
NimBLEDevice::setSecurityAuth(true, true, true);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO);
NimBLEDevice::setPower(9);

NimBLEServer* pServer = NimBLEDevice::createServer();
pServer->setCallbacks(&serverCallbacks);
pServer->advertiseOnDisconnect(true);

NimBLEAdvertising* pAdvertising = pServer->getAdvertising();
NimBLEAdvertisementData advData{};
advData.setFlags(0x06);
advData.addServiceUUID(ancsServiceUUID);
pAdvertising->setAdvertisementData(advData);
pAdvertising->start();

Serial.println("Advertising started!");
}

void loop() {
if (pClient != nullptr && pClient->isConnected()) {
auto pAncsService = pClient->getService(ancsServiceUUID);
if (pAncsService == nullptr) {
Serial.printf("Failed to find our service UUID: %s\n", ancsServiceUUID.toString().c_str());
return;
}
// Obtain a reference to the characteristic in the service of the remote BLE server.
auto pNotificationSourceCharacteristic = pAncsService->getCharacteristic(notificationSourceCharacteristicUUID);
if (pNotificationSourceCharacteristic == nullptr) {
Serial.printf("Failed to find our characteristic UUID: %s\n",
notificationSourceCharacteristicUUID.toString().c_str());
return;
}
// Obtain a reference to the characteristic in the service of the remote BLE server.
auto pControlPointCharacteristic = pAncsService->getCharacteristic(controlPointCharacteristicUUID);
if (pControlPointCharacteristic == nullptr) {
Serial.printf("Failed to find our characteristic UUID: %s\n",
controlPointCharacteristicUUID.toString().c_str());
return;
}
// Obtain a reference to the characteristic in the service of the remote BLE server.
auto pDataSourceCharacteristic = pAncsService->getCharacteristic(dataSourceCharacteristicUUID);
if (pDataSourceCharacteristic == nullptr) {
Serial.printf("Failed to find our characteristic UUID: %s\n", dataSourceCharacteristicUUID.toString().c_str());
return;
}
pDataSourceCharacteristic->subscribe(true, dataSourceNotifyCallback);
pNotificationSourceCharacteristic->subscribe(true, NotificationSourceNotifyCallback);

while (1) {
if (pendingNotification || incomingCall) {
// CommandID: CommandIDGetNotificationAttributes
// 32bit uid
// AttributeID
Serial.println("Requesting details...");
uint8_t val[8] =
{0x0, latestMessageID[0], latestMessageID[1], latestMessageID[2], latestMessageID[3], 0x0, 0x0, 0x10};
pControlPointCharacteristic->writeValue(val, 6, true); // Identifier
val[5] = 0x1;
pControlPointCharacteristic->writeValue(val, 8, true); // Title
val[5] = 0x3;
pControlPointCharacteristic->writeValue(val, 8, true); // Message
val[5] = 0x5;
pControlPointCharacteristic->writeValue(val, 6, true); // Date

while (incomingCall) {
if (Serial.available() > 0) {
acceptCall = Serial.read();
Serial.println((char)acceptCall);
}

if (acceptCall == 49) { // call accepted , get number 1 from serial
const uint8_t vResponse[] =
{0x02, latestMessageID[0], latestMessageID[1], latestMessageID[2], latestMessageID[3], 0x00};
pControlPointCharacteristic->writeValue((uint8_t*)vResponse, 6, true);

acceptCall = 0;
// incomingCall = false;
} else if (acceptCall == 48) { // call rejected , get number 0 from serial
const uint8_t vResponse[] =
{0x02, latestMessageID[0], latestMessageID[1], latestMessageID[2], latestMessageID[3], 0x01};
pControlPointCharacteristic->writeValue((uint8_t*)vResponse, 6, true);

acceptCall = 0;
incomingCall = false;
}
}

pendingNotification = false;
}
delay(1);
}
}
delay(1);
}
Loading