Skip to content

feat(gfps): Added processing to control non-discoverable advertising … #466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 12, 2025

Conversation

hirokada
Copy link
Contributor

@hirokada hirokada commented Jul 5, 2025

GFPS: Expose AccountKey write and NDA generation events for subsequent pairing integration

Description

Summary

This PR introduces internal event exposure mechanisms across the GFPS service layer and the embedded Fast Pair library, enabling application-level control during the subsequent pairing phase.

To support external advertisement orchestration, the GFPS layer now receives structured notifications when:

  • A remote device writes an Account Key
  • A Non-Discoverable Advertisement (NDA) payload is generated

These enhancements enable tight integration with existing application-layer advertisement logic, especially in scenarios where discoverable and non-discoverable flows must be coordinated.

This PR builds upon esp-cpp/nearby#2, now integrated via submodule update.

Changes Overview

GFPS Layer (Service-level integration for application control)

Files:

  • gfps.hpp
  • gfps_service.hpp
  • nearby_ble.cpp

Enhancements:

  • Added two opt-in Config callbacks:
    • account_key_write_callback_t
    • nda_ready_callback_t
  • Removed legacy extern hooks for event propagation
  • Safely nullified stored callbacks in gfps::deinit() to prevent residual behavior
  • Refactored passkey injection logic to target the actual peer initiating pairing, instead of defaulting to the most recently connected device

Embedded Layer (GFPS client callbacks)

Files:

  • nearby_fp_client.h
  • nearby_fp_client.c

Enhancements:

  • Added two client-level callback fields to NearbyFpClientCallbacks:
    • on_account_key_written
    • on_nondiscoverable_advertisement_ready
  • These callbacks run independently of the legacy on_event() dispatch
  • Introduced nearby_fp_client_Deinit() stub for lifecycle management
  • NDA payload is logged for debugging and observability

Compatibility

Area Status
Existing apps Unaffected (new callbacks default to nullptr; opt-in only)
Legacy embedded logic Transitioned from on_event() dispatch to direct callback fields
Discoverable vs NDA Selectively controllable via Config callbacks
Pairing versions Compatible with GFPS v2/v3
Passkey routing Now dynamically targets correct initiator; fallback behavior preserved

Motivation and Context

Prior implementations assumed advertising behavior was controlled solely within the GFPS library. However, in real-world applications—especially during subsequent pairing—advertisements must align with runtime platform logic.

By exposing these internal events:

  • Apps can delay, suppress, or override NDA advertisement scheduling
  • Pairing behavior becomes externally observable and modifiable
  • Control parity between initial and subsequent pairing is achieved

Prior GFPS implementations routed passkey confirmation to the last-connected peer, regardless of pairing initiator. This change ensures the passkey is returned to the correct device that initiated the pairing exchange.

How has this been tested?

  • Verified correct emission of NdaPayloadReady post-generation

  • Confirmed AccountKeyWritten is triggered only after key registration completes

  • Tested suppression and injection of NDA ads using Config callbacks

  • Validated fallback behavior (no callbacks set) matches legacy logic

  • The following actual devices were used for these checks.

    • Pixel 7 Pro (Android 16)

    • Galaxy S24 (Android 15)

Also verified legacy behavior when callbacks are unset, ensuring non-regression.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update
  • Hardware (schematic, board, system design) change
  • Software change

Checklist:

  • My change requires a change to the documentation.
  • I have added / updated the documentation related to this change via either README or WIKI

Software

  • I have added tests to cover my changes.
  • I have updated the .github/workflows/build.yml file to add my new test to the automated cloud build github action.
  • All new and existing tests passed.
  • My code follows the code style of this project.

Copy link

github-actions bot commented Jul 5, 2025

✅Static analysis result - no issues found! ✅

Copilot

This comment was marked as outdated.

Copy link
Contributor

@finger563 finger563 left a comment

Choose a reason for hiding this comment

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

Hey @hirokada-san, Thanks for this PR! I don't think we want to have this be a kconfig-setting and we definitely can't have espp::gfps_service be dependent on an opaque app.hpp and bb::App API.

Instead what I'd suggest is that the gfps_service take in another function that the user can provide which would then be called in the nearby_platform_SetRemotePasskey implementation, if the function is not nullptr. I'd expect the function to be provided in the espp::gfps::Config struct, similar to the notify_callback_t that's provided today. For backwards compatibility for the API, I'd expect the function to default to nullptr in the struct. If no callback is provided, then the original implementation of the SetRemotePasskey function runs (which ends up calling injectConfirmPasskey on the first connected peer). If the callback is provided, then it should be the one that handles all the work, and should simply be provided the passkey from the outer call.

I believe that such a design requires no Kconfig setting, no app.hpp or other opaque API dependencies, and maximizes flexibility for use of the API in the future. Additionally, it continues to remain backwards compatible because existing callers (who are not providing the function pointer today) will have the same behavior as they do today.

@hirokada
Copy link
Contributor Author

hirokada commented Jul 8, 2025

@finger563 -san, thank you for your review and feedback.
I have revised the source code in accordance with your guidelines. I have also updated the PR explanations of these code changes.

Copy link
Contributor

@finger563 finger563 left a comment

Choose a reason for hiding this comment

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

@hirokada-san, Thanks for the updates, this is definitely better.

There are still some improvements I'd like to see, and I'm not sure your code is fully operational - there are some callbacks you use and function implementations you define that I cannot find references to, so I believe they will not actually do anything.

@hirokada
Copy link
Contributor Author

hirokada commented Jul 9, 2025

@finger563 -san, thank you so much for the code review.
I have revised the source code based on your comments. I have also updated the PR description. Could you please review it again?

Comment on lines 53 to 58
typedef std::function<void(uint64_t, const uint8_t *key)> account_key_write_callback_t;

/// Callback triggered when the GFPS layer finishes constructing a Non-Discoverable Advertisement payload
/// @param adv_data Pointer to raw advertisement payload bytes
/// @param len Length of the advertisement data
typedef std::function<void(const uint8_t *adv_data, size_t len)> nda_ready_callback_t;
Copy link
Contributor

@finger563 finger563 Jul 9, 2025

Choose a reason for hiding this comment

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

For these two functions, can we change the use of const uint8_t* and const uint8_t*, size_t to be const std::span<uint8_t>&. Given that the first function's argument is known size of 16 bytes, we can use

const std::span<uint8_t, 16>&

While for the second, we'd still use

const std::span<uint8_t>&

This provides a lightweight accessor which enables the application code to use range-based methods and other accessors, while also including OOB checking and other type/memory safety helpers.

Then in the event handler which calls these callbacks you can simply call:

g_account_key_write_cb(payload->peer_address, std::span<uint8_t, 16>(payload->key_data, 16));

and

g_non_discoverable_advertisement_cb(std::span<uint8_t>(payload->adv_data, payload->length));

Copy link
Contributor

@finger563 finger563 left a comment

Choose a reason for hiding this comment

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

@hirokada There are still a few lingering issues - one is relatively minor (the use of the new typedef), but the other breaks the build (the use of non-existent event enums). Does this require a fork of the google/nearby library to work or where are these events coming from?

I've also suggested an API update to use std::span where possible to do a lightweight type-conversion when passing data out of the low-level gfps library to app c++ code.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR enhances the GFPS service layer by exposing Account Key write and NDA-ready events through opt-in application callbacks, enabling tighter integration during subsequent pairing flows.

  • Added two new callbacks (on_account_key_write_callback, on_non_discoverable_advertisement_ready_callback) in the GFPS Config and hooked them into the embedded Fast Pair client.
  • Updated init/deinit workflows in nearby_ble.cpp to register, invoke, and clear these callbacks safely.
  • Refactored GfpsService::init overloads to accept and merge a user-provided Config, while preserving the internal notification mechanism.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
components/gfps_service/src/nearby_ble.cpp Registered C-compatible handlers for account key writes and NDA payload readiness; stored and cleared callbacks in init/deinit; updated nearby_fp_client_Init.
components/gfps_service/include/gfps_service.hpp Added overloads of init to accept custom Config; merged application callbacks with internal notify logic.
components/gfps_service/include/gfps.hpp Introduced new callback typedefs for passkey injection, account key write, and NDA readiness.
components/gfps_service/detail/nearby Submodule bump to deliver embedded client callback support.

hirokada added 6 commits July 12, 2025 07:35
…for subsequent pairing from the application layer.
…ling

- Added optional callback to notify app when NDA advertisement payload is prepared
- Added callback to notify when Account Key is written
- Passkey response destination changed from fixed (latest peer) to actual sender
- Behavior customizable via espp::gfps::Config (all new callbacks default to nullptr)
- Preserves backward compatibility and introduces no app-layer dependencies
- Enables app-layer control of Non-Discoverable Advertisement dispatch
- Refines callback struct lifetime handling via static allocation
- Consolidates lambda dispatch logic for cleaner event handling
- Applies typedef aliases for callback clarity in Config
- Incorporates reviewer feedback for safer and clearer design
@hirokada hirokada force-pushed the feature/add-app-control-subpair-flow branch from ffa3375 to ffddb56 Compare July 12, 2025 03:06
@hirokada
Copy link
Contributor Author

@finger563 -san, I’ve applied the suggestions from Copilot and confirmed that the modified source behaves correctly.

@finger563 finger563 merged commit 17f243e into esp-cpp:main Jul 12, 2025
85 checks passed
@finger563
Copy link
Contributor

@finger563 -san, I’ve applied the suggestions from Copilot and confirmed that the modified source behaves correctly.

@hirokada -san, thank you for all the work you put into reviewing and testing this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants