Key Terms in This Guide
After Inquiry tells you a device exists (giving you its BD_ADDR and Class of Device), you may want to get its human-readable name. That is what Name Discovery does.
Inquiry only returns the BD_ADDR and CoD. The friendly name — like “John’s Car Kit” or “Sony WH-1000XM5” — requires a separate step: a Remote Name Request. This is sent directly to the target device using its BD_ADDR obtained from inquiry.
The official UI label for this procedure defined by GAP is: “Bluetooth Device Name Discovery”
Inquiry vs Name Discovery — What Each Returns:
| Information | Returned by Inquiry? | Returned by Name Discovery? |
|---|---|---|
| BD_ADDR | ✓ Yes | ✗ No |
| Class of Device | ✓ Yes | ✗ No |
| Clock offset | ✓ Yes | ✗ No |
| Human-readable Device Name | ~ Only if EIR supported | ✓ Yes — always |
BlueZ — Name Discovery:
# Remote Name Request — get human-readable name from BD_ADDR
hcitool name 00:1A:7D:DA:71:13
# Output: MyHeadset
# Name discovery via bluetoothctl (modern method)
bluetoothctl info 00:1A:7D:DA:71:13 | grep "Name"
# Output: Name: MyHeadset
# Scan and show names automatically (bluetoothctl resolves names)
bluetoothctl scan on
# [NEW] Device 00:1A:7D:DA:71:13 MyHeadset ← name included
# If you only have the address, trigger a name lookup
hcitool -i hci0 name 00:1A:7D:DA:71:13
Device Discovery combines Inquiry and Name Discovery into one complete procedure. It gives you the full picture of a remote device — everything that Inquiry provides, plus the human-readable device name.
The official UI label defined by GAP is: “Bluetooth Device Discovery”
| Information Provided by Device Discovery | Source |
|---|---|
| BD_ADDR | From Inquiry |
| Clock information | From Inquiry |
| Class of Device | From Inquiry |
| Page Scan mode | From Inquiry |
| Extended Inquiry Response (EIR) | From Inquiry (if device supports EIR) |
| Bluetooth Device Name ← extra step | From Name Discovery (Remote Name Request) — this is what makes Device Discovery different from plain Inquiry |
Device Discovery Flow:
| Discovering Device | ↕ | Remote Device |
|---|---|---|
| Step 1: Send Inquiry (GIAC) | → | Reply with FHS: BD_ADDR, CoD, clock |
| Step 2: Send Remote Name Request to BD_ADDR | → | Reply with device name string |
| Result: Full device profile — BD_ADDR + CoD + Clock + Name | ||
Bonding creates a mutual trust relationship between two Bluetooth devices. The result of bonding is a link key — a shared secret that both devices store permanently. Every time the two devices reconnect in the future, this link key is used to authenticate each other automatically without any user interaction.
Think of it like this: the first time you shake hands and exchange phone numbers is bonding. After that, you recognise each other instantly — that is the link key at work.
The official UI label defined by GAP is: “Bluetooth Bonding”
| Bonding Stage | What Happens |
|---|---|
| 1. Pairing | User enters passkey or approves numeric comparison on both devices |
| 2. Link Key Generation | A link key is created using the passkey (or SSP method) and exchanged between the two devices |
| 3. Link Key Storage | Both devices store the link key in persistent memory — the bond is saved |
| 4. Future Reconnection | When the devices meet again, the stored link key is used for authentication — no passkey needed from user |
BlueZ — Bonding and Pairing:
# Start pairing / bonding with a remote device
bluetoothctl pair 00:1A:7D:DA:71:13
# If the device needs a passkey, bluetoothctl will prompt you
# Trust the device after bonding (auto-connect on future encounters)
bluetoothctl trust 00:1A:7D:DA:71:13
# List all bonded devices (stored link keys)
bluetoothctl paired-devices
# Check bond status for a specific device
bluetoothctl info 00:1A:7D:DA:71:13 | grep -E "Paired|Trusted|Bonded"
# Remove a bond (delete link key from both sides if possible)
bluetoothctl remove 00:1A:7D:DA:71:13
# View link keys stored in BlueZ (requires root)
cat /var/lib/bluetooth/<adapter-address>/<device-address>/info
# Shows: [LinkKey] Key=... Type=... PINLength=...
Once you have discovered a device and decided to connect to it, there are three steps that happen in sequence to build the full connection from the radio layer up to the application layer. These are called the Establishment Procedures.
Device Discovery or Inquiry always happens first to get the BD_ADDR and other information. Then the three establishment procedures follow in order.
The Three Establishment Steps — Built Layer by Layer:
| Device A (Initiator) | ↕ | Device B (Acceptor) |
|---|---|---|
|
Step 1 — LMP Layer
Link Manager (LMP)
|
Link Establishment
↔
ACL link created
|
Step 1 — LMP Layer
Link Manager (LMP)
|
|
Step 2 — L2CAP Layer
L2CAP
LMP (below)
|
Channel Establishment
↔
L2CAP channel opened
|
Step 2 — L2CAP Layer
L2CAP
LMP (below)
|
|
Step 3 — Application Layer
Application
L2CAP (below)
LMP (below)
|
Connection Establishment
↔
Apps can communicate
|
Step 3 — Application Layer
Application
L2CAP (below)
LMP (below)
|
Each step builds on the one below it. You cannot have Step 2 without Step 1, or Step 3 without Step 2.
| Step | Procedure | Layer | UI Name (per GAP) | What It Creates |
|---|---|---|---|---|
| 1 | Link Establishment | LMP | “Bluetooth link establishment” | An ACL (Asynchronous Connection-Less) baseband link between the two devices |
| 2 | Channel Establishment | L2CAP | “Bluetooth channel establishment” | An L2CAP channel — a logical connection that higher protocols (RFCOMM, AVDTP) can use |
| 3 | Connection Establishment | Application | “Bluetooth connection establishment” | An end-to-end connection between the applications on both devices — data can now flow |
BlueZ — Connection Establishment:
# Step 1: Link Establishment — create an ACL link
# BlueZ does this automatically when you connect. To verify:
hcitool con
# Output: Connections:
# < ACL 00:1A:7D:DA:71:13 handle 11 state 1 lm MASTER
# Step 2: Channel Establishment — open an L2CAP channel
# Test raw L2CAP connectivity (ping)
l2ping 00:1A:7D:DA:71:13
# Create a raw L2CAP connection to a PSM (Protocol/Service Multiplexer)
# Example: connect to PSM 3 (RFCOMM)
l2test -r -P 3 00:1A:7D:DA:71:13
# Step 3: Connection Establishment — full application connection
# Using bluetoothctl (handles all 3 steps transparently)
bluetoothctl connect 00:1A:7D:DA:71:13
# Check ACL link quality
hcitool lq 00:1A:7D:DA:71:13 # link quality (0-255)
hcitool rssi 00:1A:7D:DA:71:13 # signal strength (dBm)
# Disconnect
bluetoothctl disconnect 00:1A:7D:DA:71:13
Authentication is the process of verifying who is at the other end of the link. It answers the question: “Is this device really who it says it is?”
Authentication happens automatically when two devices initiate connection establishment. The process checks for a stored link key:
| Scenario | What Happens |
|---|---|
| Link key already exists (previously bonded) | Authentication uses the stored link key. No user interaction needed. Connection proceeds immediately. |
| Link key does not exist (first time) | The pairing procedure is triggered to create a link key. User may need to enter a passkey or confirm a number. |
| Link key exists but does not match | Authentication fails. The connection is rejected. The device must be un-paired and re-paired. |
BlueZ — Checking Authentication:
# Check if a device is authenticated (paired with link key)
bluetoothctl info 00:1A:7D:DA:71:13 | grep "Paired"
# Paired: yes means link key is stored
# View the stored link key (requires root access)
sudo cat /var/lib/bluetooth/<local-addr>/<remote-addr>/info
# [LinkKey]
# Key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Type=4
# PINLength=0
# Require authentication for connections to this adapter
hciconfig hci0 auth # enable authentication requirement
hciconfig hci0 noauth # disable authentication requirement
# Enable encryption after authentication
hciconfig hci0 encrypt # require encryption
hciconfig hci0 noencrypt # disable encryption requirement
GAP defines four security modes. They fall into two families: Legacy (for older devices) and Secure Simple Pairing / SSP (for Bluetooth 2.1+ devices). The mode determines when security is enforced and what kind of link key is required.
Legacy Security Modes (Bluetooth 2.0 and earlier):
| Mode | Name | When Security is Enforced | Use Case |
|---|---|---|---|
| Mode 1 | Non-secured | Never — no security at all | Open connections where no authentication or encryption is needed. Not recommended for sensitive data. |
| Mode 2 | Service-level enforced | After connection is established — only if the specific service/channel requires it | Some services on the device need security, others do not. Security is per-service, not per-link. |
| Mode 3 | Link-level enforced | During connection setup — before anything else happens | All connections to this device require authentication. Strongest legacy option. |
SSP Security Mode (Bluetooth 2.1 + EDR and later):
| Mode | Name | Sub-type | MITM Protection? | How Link Key is Generated |
|---|---|---|---|---|
| Mode 4 | Service-level enforced (SSP) | Authenticated link key required | ✓ Yes | Numeric Comparison, Out-of-Band (OOB), or Passkey Entry |
| Unauthenticated link key required | ✗ No | Just Works method — no user confirmation | ||
| Security optional | N/A | Used only for SDP transactions — lowest security requirement |
Can a device support two modes at once? Yes. A device connecting to a legacy Bluetooth 2.0 device uses Security Mode 2, while the same device connecting to a modern SSP-capable device uses Security Mode 4. BlueZ handles this automatically based on the remote device’s capabilities.
Security Mode Timeline:
| Bluetooth Version | Security Modes Available | Pairing Type |
|---|---|---|
| Bluetooth 1.x / 2.0 | Mode 1, 2, 3 | Legacy Pairing (PIN entry) |
| Bluetooth 2.1 + EDR and later | Mode 1, 2, 3, 4 | Secure Simple Pairing (SSP) — Numeric Comparison, Just Works, Passkey Entry, OOB |
BlueZ — Security Configuration:
# Check SSP (Secure Simple Pairing) status on adapter
hciconfig hci0 | grep "Simple pairing"
# Enable SSP on adapter (Security Mode 4)
hciconfig hci0 sspmode 1
# Disable SSP (fall back to legacy modes 1-3)
hciconfig hci0 sspmode 0
# Set I/O capability for SSP pairing method:
# 0 = DisplayOnly (Just Works or Passkey display)
# 1 = DisplayYesNo (Numeric Comparison — MITM protection)
# 2 = KeyboardOnly (Passkey Entry — MITM protection)
# 3 = NoInputNoOutput (Just Works — no MITM protection)
hcitool cmd 0x08 0x0024 1 # set DisplayYesNo (numeric comparison)
# Using bluetoothctl to handle SSP pairing interactively
bluetoothctl agent on # enable pairing agent
bluetoothctl default-agent # set as default
bluetoothctl pair 00:1A:7D:DA:71:13
# For Numeric Comparison: confirm the 6-digit code matches on both devices
# For Just Works: confirms automatically, no user input
# Check security level of an active connection
hcitool con # shows handle
hcitool auth <handle> # request authentication on the link
The Serial Port Profile (SPP) defines how to create a wireless RS-232 serial connection between two Bluetooth devices. It was one of the very first Bluetooth profiles ever defined — because the original goal of Bluetooth was to replace cables, and serial cables were everywhere.
The key power of SPP: legacy applications that were written for wired RS-232 serial ports can use SPP with zero code changes. From the application’s point of view, it is just talking to a COM port. The fact that the COM port is actually a virtual port running over Bluetooth RFCOMM is completely transparent.
SPP depends on GAP (reusing all its discovery and connection procedures) and is in turn used as a foundation by other profiles — specifically Hands-Free Profile (HFP) and the Generic Object Exchange Profile (GOEP).
The Two SPP Device Roles:
| Role | What It Does | Analogy |
|---|---|---|
| Dev A | Initiates the Bluetooth connection — the active side that starts the session | Like the person who picks up the phone and dials |
| Dev B | Waits for an incoming connection and accepts it when Dev A connects | Like the person who waits for the phone to ring and answers it |
The SPP stack has two types of applications running side by side. A Bluetooth management application handles discovery and connection setup. Once connected, a legacy serial application uses the emulated port as if it were a normal RS-232 cable — it never knows Bluetooth is involved.
SPP Stack Diagram — Dev A and Dev B (Figure 4.16):
| Dev A (Initiator) | ↔ | Dev B (Acceptor) |
|---|---|---|
|
Legacy Serial App
BT Connection App
Serial Port Emulation (/dev/rfcomm0)
SDP | RFCOMM
L2CAP
Lower Layers (Baseband + Radio)
|
RFCOMM
virtual link ↔
|
Legacy Serial App
BT Connection App
Serial Port Emulation (/dev/rfcomm1)
SDP | RFCOMM
L2CAP
Lower Layers (Baseband + Radio)
|
The legacy serial app uses the emulated port exactly like a physical RS-232 cable. It has no knowledge of Bluetooth underneath.
How SPP Works — Step by Step:
| Step | Action | Who Does It |
|---|---|---|
| 1 | Bluetooth management app on Dev A scans and discovers Dev B (GAP inquiry) | BT Connection App |
| 2 | Dev A uses SDP to find the RFCOMM channel number of Dev B’s SPP service | BT Connection App + SDP |
| 3 | Dev A establishes an RFCOMM connection to Dev B on that channel | BT Connection App + RFCOMM |
| 4 | A virtual serial port device appears on Dev A (e.g. /dev/rfcomm0 on Linux) |
Serial Port Emulation layer (OS driver) |
| 5 | Legacy serial application opens /dev/rfcomm0 and sends/receives data — no code change needed |
Legacy Serial App |
BlueZ — SPP on Linux:
# === DEV B SIDE: Register an SPP service (server) ===
# Add an SPP service record to SDP (makes device discoverable as SPP server)
sdptool add SP
# Open an RFCOMM listener on channel 1 (Dev B waiting for connection)
rfcomm listen /dev/rfcomm0 1
# Now Dev B waits. Dev A can connect to this channel.
# === DEV A SIDE: Connect to Dev B's SPP service ===
# Step 1: Find Dev B's SPP RFCOMM channel via SDP
sdptool browse 00:1A:7D:DA:71:13 | grep -A2 "Serial Port"
# Output: Channel: 1
# Step 2: Bind the RFCOMM device
rfcomm bind /dev/rfcomm0 00:1A:7D:DA:71:13 1
# Step 3: Connect
rfcomm connect /dev/rfcomm0 00:1A:7D:DA:71:13 1
# Output: Connected /dev/rfcomm0 to 00:1A:7D:DA:71:13 on channel 1
# === LEGACY APP USAGE (after connection) ===
# Use the virtual serial port just like RS-232:
screen /dev/rfcomm0 115200 # terminal emulator
minicom -D /dev/rfcomm0 # minicom serial monitor
cat /dev/rfcomm0 # read data
echo "Hello" > /dev/rfcomm0 # send data
# Using pyserial in Python (legacy app example):
# import serial
# ser = serial.Serial('/dev/rfcomm0', 115200)
# ser.write(b'Hello\n')
# data = ser.readline()
# Check RFCOMM status
rfcomm show /dev/rfcomm0
# Release the binding
rfcomm release /dev/rfcomm0
The Headset Profile (HSP) and Hands-Free Profile (HFP) define how a Bluetooth connection handles phone calls between a mobile phone and a hands-free device — such as a mono Bluetooth headset or a car kit.
HSP was one of the very first Bluetooth profiles ever created. Over time, HFP replaced it because HFP is a superset of HSP — everything HSP can do, HFP can also do, plus much more. Modern devices implement HFP.
| Profile | Status | Key Point |
|---|---|---|
| HSP — Headset Profile | Superseded / Legacy | Original profile for mono headsets. Basic call control only — answer, reject, volume. |
| HFP — Hands-Free Profile | Current standard | Full superset of HSP. Adds call waiting, voice dialing, battery indicator, signal strength display, and more. |
Functions Defined by Headset / HFP Profiles:
| Function Category | Specific Functions |
|---|---|
| Connection | Route audio from mobile phone to the hands-free device when a call is active or incoming |
| Call Control | Accept an incoming call from the headset button Reject an incoming call Terminate (hang up) an active call |
HFP Roles — AG and HF:
| Role | Short Name | Device | Responsibility |
|---|---|---|---|
| Audio Gateway | AG | Mobile phone or device connected to cellular network | Handles the actual call, manages audio routing to the HF device |
| Hands-Free | HF | Bluetooth headset, car kit, speakerphone | Provides microphone and speaker, sends call control commands to the AG |
HFP Protocol Stack:
| Audio Gateway (AG) — Phone | ↔ | Hands-Free (HF) — Headset / Car Kit |
|---|---|---|
|
Phone App (call handling)
HFP / AT Commands over RFCOMM
SDP | RFCOMM
L2CAP
Baseband + Radio
|
AT Commands →
↔
← Responses + Audio
|
HF App (mic + speaker)
HFP / AT Commands over RFCOMM
SDP | RFCOMM
L2CAP
Baseband + Radio
|
HFP uses AT commands (the same commands modems use) sent over an RFCOMM channel to control calls
BlueZ — HFP on Linux:
# Check if a device supports HFP via SDP
sdptool browse 00:1A:7D:DA:71:13 | grep -A5 "Handsfree"
# Shows: Handsfree Audio Gateway or Handsfree
# Connect to a paired HFP headset
bluetoothctl connect 00:1A:7D:DA:71:13
# HFP in BlueZ uses oFono (telephony) or PulseAudio/PipeWire for audio
# Check HFP profile in bluetoothctl
bluetoothctl info 00:1A:7D:DA:71:13 | grep "UUID"
# Look for: Handsfree Audio Gateway (HFP AG) UUID
# PulseAudio: list Bluetooth HFP cards
pactl list cards | grep -A10 "bluez_card"
# Switch between A2DP (music) and HFP (call) profiles
pactl set-card-profile bluez_card.00_1A_7D_DA_71_13 headset_head_unit
pactl set-card-profile bluez_card.00_1A_7D_DA_71_13 a2dp_sink
# With PipeWire (modern replacement for PulseAudio)
wpctl status # show Bluetooth audio devices
# Switch profile:
pactl set-card-profile bluez_card.00_1A_7D_DA_71_13 handsfree_head_unit
# AT Commands used by HFP (sent over RFCOMM channel under the hood):
# ATA - Answer call
# AT+CHUP - Terminate call
# AT+CHLD=1 - Hold call / accept waiting call
# AT+CLCC - List current calls
# AT+VGS=N - Set speaker volume (0-15)
# AT+VGM=N - Set microphone gain (0-15)
Inquiry → Name Discovery → Device Discovery. Each adds more information. Device Discovery is the complete procedure.
Link (LMP/ACL) → Channel (L2CAP) → Connection (Application). Each layer builds on the one below it.
The link key is stored permanently on both devices. Future reconnections use it for authentication automatically.
Mode 1 = none. Mode 2 = after connection (per service). Mode 3 = during connection. Mode 4 = SSP (BT 2.1+).
Legacy apps see /dev/rfcomm0 as a normal serial port. RFCOMM + L2CAP carry the data wirelessly underneath.
HFP is a superset of HSP. It handles everything from answering calls to volume control using AT commands over RFCOMM.
