Skip to content
Haiku OSDeep Dive Published Updated 4 min readViews unavailable

Haiku Bluetooth DiscoveryAgent: Inquiry Callbacks and Result Deduplication

Trace Haiku's pinned DiscoveryAgent implementation from inquiry submission through listener callbacks, address-based deduplication, and cancellation status.

Haiku’s Bluetooth DiscoveryAgent is easiest to use correctly when an inquiry is treated as a message-driven state transition, not as a synchronous function that returns a finished device list. In the source revision examined here, StartInquiry() submits an inquiry request to the Bluetooth server with a DiscoveryListener as the reply target; later messages delivered to that listener drive InquiryStarted(), DeviceDiscovered(), and InquiryResponse().

This article is scoped to Haiku commit b5307db8ed76386442527c86ec40b52c9f021789. It explains those files, not every Haiku release, adapter, or Bluetooth server; no runtime test is claimed.

Treat StartInquiry() as request submission

The public header exposes two StartInquiry() overloads and CancelInquiry(). The two-argument overload delegates to the explicit-time overload using GetInquiryTime(). In the implementation, the latter first checks whether the Bluetooth messenger exists, then rejects a secs value below 1 or above 61. The parameter is declared as bigtime_t, but the code calls it secs; do not infer a conversion, unit, or promise about the actual scan duration from that name alone.

Before it sends the request, the method stores the supplied listener as the last-used listener and associates it with the local device. The implementation dereferences the listener without a null check, so callers should provide a valid listener object. The request asks the Bluetooth server for inquiry status, several inquiry-result event variants, and inquiry completion. If SendMessage() returns B_OK, StartInquiry() returns B_OK; otherwise it returns B_ERROR.

That return value describes the request-send path in this implementation. It is not evidence that a remote device has already been found, nor does it replace the listener’s later inquiry callbacks. Keep UI state such as “searching,” “found,” “cancelled,” and “failed” tied to callback messages rather than treating the initial status as a final scan result.

Understand when results are cleared and reported

DiscoveryListener::MessageReceived() handles incoming inquiry messages. For a device-result message it first requires a nonzero count, then reads each response’s Bluetooth address and device class. Optional values such as RSSI, scan modes, clock offset, and friendly name have defaults when fields are absent. That makes the result path tolerant of omitted metadata; it does not make every optional field meaningful for every controller or response.

The listener identifies an already-seen device by comparing Bluetooth addresses. A repeated address updates stored response metadata, including RSSI and scan-related fields. A friendly name is copied under a narrower condition: the existing name is not marked complete and the new name is nonempty. The duplicate path then stops processing that item without calling DeviceDiscovered() again. New addresses are appended to the list, initialized from the response fields, and reported through DeviceDiscovered().

The distinction matters for consumers that maintain their own table. DeviceDiscovered() is a new-address notification in this implementation, not a stream of every radio response. If a UI needs refreshed RSSI or other fields for a device already shown, it cannot assume a second callback will arrive; it needs to consult the implementation’s stored result list or use an application-level refresh design supported by the API.

The old list is cleared only when the listener processes BT_MSG_INQUIRY_STARTED and that message contains a status field. The list is not cleared synchronously inside StartInquiry(). Code that starts a scan and immediately reads results should therefore account for message ordering and should not present a previously retained list as results from the new inquiry.

Keep cancellation separate from completion

CancelInquiry() checks the messenger, builds an HCI inquiry-cancel command, and sends a request that expects a command-complete event. It returns the status found in the reply or B_ERROR if the messenger/send/reply path fails. Although the method accepts a DiscoveryListener*, this implementation does not use that argument in its body.

The listener maps completion, termination, and error messages to the corresponding InquiryResponse() values. The source labels termination as cancellation, but cancellation’s command return and the later listener notification are separate observations. Do not display “cancelled and finished” just because the cancel method returned a status; track the listener event as well. Similarly, the base implementations of the listener callbacks are no-op hooks, so an application subclass must implement the behavior it needs.

A practical source-review checklist

When testing code against this revision, verify both sides of the callback contract:

  1. Use a live listener and record the StartInquiry() status separately from callback events.
  2. Send or simulate two result messages with the same address; check that the first creates a DeviceDiscovered() event and the later duplicate updates stored fields without creating another event.
  3. Deliver an inquiry-start message with and without status; verify that only the former clears the list and calls InquiryStarted().
  4. Exercise the cancel request and record its returned command status separately from InquiryResponse(BT_INQUIRY_TERMINATED).

These are test scenarios derived from the pinned source branches, not a claim that a real adapter has been exercised. For an actual application, also check the target Haiku build, Bluetooth server state, adapter behavior, and listener lifetime. The key engineering boundary is simple: request acceptance, accumulated device records, and inquiry termination are distinct states.

Related:

Sources:

Comments