* [PATCH v1 01/18] obexd/pbap: Fix crash counting vCards in dummy backend
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 16:55 ` obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 bluez.test.bot
2026-11-04 8:34 ` [PATCH v1 02/18] obexd/pbap: Fix Tracker contact grouping across requests Jinwang Li
` (16 subsequent siblings)
17 siblings, 1 reply; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Skip the vCard callback when none is given. Counting the vCards of the
missed calls folder passes none and crashed.
Fixes: f34bc5d924ec ("obexd: Add PBAP PullPhoneBook function for dummy back-end")
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/phonebook-dummy.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index 0a0ae24ff..14075b502 100644
--- a/obexd/plugins/phonebook-dummy.c
+++ b/obexd/plugins/phonebook-dummy.c
@@ -175,7 +175,9 @@ static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset,
v = Parse_MIME_FromFile(fp);
if (v != NULL) {
- func(filename, v, user_data);
+ if (func)
+ func(filename, v, user_data);
+
deleteVObject(v);
n++;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* RE: obexd/pbap: Update PSE support from PBAP 1.1 to 1.2
2026-11-04 8:34 ` [PATCH v1 01/18] obexd/pbap: Fix crash counting vCards in dummy backend Jinwang Li
@ 2026-11-04 16:55 ` bluez.test.bot
2026-11-05 15:37 ` Luiz Augusto von Dentz
0 siblings, 1 reply; 25+ messages in thread
From: bluez.test.bot @ 2026-11-04 16:55 UTC (permalink / raw)
To: linux-bluetooth, jinwang.li
[-- Attachment #1: Type: text/plain, Size: 1020 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1201470/
---Test result---
Test Summary:
CheckPatch PASS 8.55 seconds
GitLint PASS 4.70 seconds
BuildEll PASS 23.33 seconds
BluezMake PASS 197.66 seconds
MakeCheck PASS 13.22 seconds
MakeDistcheck PASS 105.32 seconds
CheckValgrind PASS 164.16 seconds
CheckSmatch PASS 173.53 seconds
bluezmakeextell PASS 69.20 seconds
TestFunctional PASS 885.71 seconds
IncrementalBuild PASS 250.73 seconds
ScanBuild PASS 687.43 seconds
https://github.com/bluez/bluez/pull/2760
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: obexd/pbap: Update PSE support from PBAP 1.1 to 1.2
2026-11-04 16:55 ` obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 bluez.test.bot
@ 2026-11-05 15:37 ` Luiz Augusto von Dentz
2026-11-09 9:34 ` Jinwang Li
0 siblings, 1 reply; 25+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-05 15:37 UTC (permalink / raw)
To: linux-bluetooth; +Cc: jinwang.li
Hi Jinwang,
On Wed, Nov 4, 2026 at 12:14 PM <bluez.test.bot@gmail.com> wrote:
>
> This is automated email and please do not reply to this email!
>
> Dear submitter,
>
> Thank you for submitting the patches to the linux bluetooth mailing list.
> This is a CI test results with your patch series:
> PW Link:https://patchwork.kernel.org/series/1201470/
>
> ---Test result---
>
> Test Summary:
> CheckPatch PASS 8.55 seconds
> GitLint PASS 4.70 seconds
> BuildEll PASS 23.33 seconds
> BluezMake PASS 197.66 seconds
> MakeCheck PASS 13.22 seconds
> MakeDistcheck PASS 105.32 seconds
> CheckValgrind PASS 164.16 seconds
> CheckSmatch PASS 173.53 seconds
> bluezmakeextell PASS 69.20 seconds
> TestFunctional PASS 885.71 seconds
> IncrementalBuild PASS 250.73 seconds
> ScanBuild PASS 687.43 seconds
>
>
>
> https://github.com/bluez/bluez/pull/2760
>
> ---
> Regards,
> Linux Bluetooth
Copilot flagged a couple of problems:
https://github.com/bluez/bluez/pull/2760#pullrequestreview-5775992507
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: obexd/pbap: Update PSE support from PBAP 1.1 to 1.2
2026-11-05 15:37 ` Luiz Augusto von Dentz
@ 2026-11-09 9:34 ` Jinwang Li
2026-11-09 14:31 ` Luiz Augusto von Dentz
0 siblings, 1 reply; 25+ messages in thread
From: Jinwang Li @ 2026-11-09 9:34 UTC (permalink / raw)
To: Luiz Augusto von Dentz, linux-bluetooth; +Cc: cheng.jiang
On 11/5/2026 11:37 PM, Luiz Augusto von Dentz wrote:
> Hi Jinwang,
>
> On Wed, Nov 4, 2026 at 12:14 PM <bluez.test.bot@gmail.com> wrote:
>>
>> This is automated email and please do not reply to this email!
>>
>> Dear submitter,
>>
>> Thank you for submitting the patches to the linux bluetooth mailing list.
>> This is a CI test results with your patch series:
>> PW Link:https://patchwork.kernel.org/series/1201470/
>>
>> ---Test result---
>>
>> Test Summary:
>> CheckPatch PASS 8.55 seconds
>> GitLint PASS 4.70 seconds
>> BuildEll PASS 23.33 seconds
>> BluezMake PASS 197.66 seconds
>> MakeCheck PASS 13.22 seconds
>> MakeDistcheck PASS 105.32 seconds
>> CheckValgrind PASS 164.16 seconds
>> CheckSmatch PASS 173.53 seconds
>> bluezmakeextell PASS 69.20 seconds
>> TestFunctional PASS 885.71 seconds
>> IncrementalBuild PASS 250.73 seconds
>> ScanBuild PASS 687.43 seconds
>>
>>
>>
>> https://github.com/bluez/bluez/pull/2760
>>
>> ---
>> Regards,
>> Linux Bluetooth
>
> Copilot flagged a couple of problems:
> https://github.com/bluez/bluez/pull/2760#pullrequestreview-5775992507
Hi Luiz,
The first three comments are valid. v2 will fix all three.
I would also like to discuss comment 4, "Clear JPEG image capability
until photos are normalized."
The Default Contact Image Format feature is mandatory for PBAP 1.2 and
requires PHOTO to contain a base64-encoded JPEG of at most 300x300
pixels and 50 kB.
The three backends currently handle photos differently: Tracker uses the
photo URL directly, ebook passes through inline data or a URI, and dummy
forwards the PHOTO value from the vCard. This series only advertises the
feature bit and does not change these behaviors.
I don't think obexd should perform remote network access, image
resizing, or transcoding, as that would add unrelated networking and
image-processing dependencies.
Without those, I see three options:
Keep the existing behavior and only advertise bit 9.
Validate inline PHOTO data and omit non-conformant or URI-based images.
Read local photo URIs, base64-encode JPEGs that meet the requirements,
and omit unsupported or non-conformant images.
Do you have a preference for how this should be handled?
Regards,
Jinwang>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: obexd/pbap: Update PSE support from PBAP 1.1 to 1.2
2026-11-09 9:34 ` Jinwang Li
@ 2026-11-09 14:31 ` Luiz Augusto von Dentz
2026-11-10 2:00 ` Jinwang Li
0 siblings, 1 reply; 25+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-09 14:31 UTC (permalink / raw)
To: Jinwang Li; +Cc: linux-bluetooth, cheng.jiang
Hi Jinwang,
On Mon, Nov 9, 2026 at 4:34 AM Jinwang Li <jinwang.li@oss.qualcomm.com> wrote:
>
>
>
> On 11/5/2026 11:37 PM, Luiz Augusto von Dentz wrote:
> > Hi Jinwang,
> >
> > On Wed, Nov 4, 2026 at 12:14 PM <bluez.test.bot@gmail.com> wrote:
> >>
> >> This is automated email and please do not reply to this email!
> >>
> >> Dear submitter,
> >>
> >> Thank you for submitting the patches to the linux bluetooth mailing list.
> >> This is a CI test results with your patch series:
> >> PW Link:https://patchwork.kernel.org/series/1201470/
> >>
> >> ---Test result---
> >>
> >> Test Summary:
> >> CheckPatch PASS 8.55 seconds
> >> GitLint PASS 4.70 seconds
> >> BuildEll PASS 23.33 seconds
> >> BluezMake PASS 197.66 seconds
> >> MakeCheck PASS 13.22 seconds
> >> MakeDistcheck PASS 105.32 seconds
> >> CheckValgrind PASS 164.16 seconds
> >> CheckSmatch PASS 173.53 seconds
> >> bluezmakeextell PASS 69.20 seconds
> >> TestFunctional PASS 885.71 seconds
> >> IncrementalBuild PASS 250.73 seconds
> >> ScanBuild PASS 687.43 seconds
> >>
> >>
> >>
> >> https://github.com/bluez/bluez/pull/2760
> >>
> >> ---
> >> Regards,
> >> Linux Bluetooth
> >
> > Copilot flagged a couple of problems:
> > https://github.com/bluez/bluez/pull/2760#pullrequestreview-5775992507
> Hi Luiz,
>
> The first three comments are valid. v2 will fix all three.
>
> I would also like to discuss comment 4, "Clear JPEG image capability
> until photos are normalized."
>
> The Default Contact Image Format feature is mandatory for PBAP 1.2 and
> requires PHOTO to contain a base64-encoded JPEG of at most 300x300
> pixels and 50 kB.
>
> The three backends currently handle photos differently: Tracker uses the
> photo URL directly, ebook passes through inline data or a URI, and dummy
> forwards the PHOTO value from the vCard. This series only advertises the
> feature bit and does not change these behaviors.
>
> I don't think obexd should perform remote network access, image
> resizing, or transcoding, as that would add unrelated networking and
> image-processing dependencies.
>
> Without those, I see three options:
>
> Keep the existing behavior and only advertise bit 9.
> Validate inline PHOTO data and omit non-conformant or URI-based images.
> Read local photo URIs, base64-encode JPEGs that meet the requirements,
> and omit unsupported or non-conformant images.
>
> Do you have a preference for how this should be handled?
I don't have a particular preference, it just needs to be spec
compliant and pass the PTS testing. If each backend uses a different
format or method, perhaps we need to converge on one. That said, I
don't think the Linux desktop cares too much about PBAP so perhaps
this is just to exercise whether PBAP could be qualified? In that case
I would just use the dummy format, or read it locally and then check
if it meets the requirements.
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: obexd/pbap: Update PSE support from PBAP 1.1 to 1.2
2026-11-09 14:31 ` Luiz Augusto von Dentz
@ 2026-11-10 2:00 ` Jinwang Li
0 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-10 2:00 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth, cheng.jiang
Hi Luiz,
On 11/9/2026 10:31 PM, Luiz Augusto von Dentz wrote:
> Hi Jinwang,
>
> On Mon, Nov 9, 2026 at 4:34 AM Jinwang Li <jinwang.li@oss.qualcomm.com> wrote:
>>
>>
>>
>> On 11/5/2026 11:37 PM, Luiz Augusto von Dentz wrote:
>>> Hi Jinwang,
>>>
>>> On Wed, Nov 4, 2026 at 12:14 PM <bluez.test.bot@gmail.com> wrote:
>>>>
>>>> This is automated email and please do not reply to this email!
>>>>
>>>> Dear submitter,
>>>>
>>>> Thank you for submitting the patches to the linux bluetooth mailing list.
>>>> This is a CI test results with your patch series:
>>>> PW Link:https://patchwork.kernel.org/series/1201470/
>>>>
>>>> ---Test result---
>>>>
>>>> Test Summary:
>>>> CheckPatch PASS 8.55 seconds
>>>> GitLint PASS 4.70 seconds
>>>> BuildEll PASS 23.33 seconds
>>>> BluezMake PASS 197.66 seconds
>>>> MakeCheck PASS 13.22 seconds
>>>> MakeDistcheck PASS 105.32 seconds
>>>> CheckValgrind PASS 164.16 seconds
>>>> CheckSmatch PASS 173.53 seconds
>>>> bluezmakeextell PASS 69.20 seconds
>>>> TestFunctional PASS 885.71 seconds
>>>> IncrementalBuild PASS 250.73 seconds
>>>> ScanBuild PASS 687.43 seconds
>>>>
>>>>
>>>>
>>>> https://github.com/bluez/bluez/pull/2760
>>>>
>>>> ---
>>>> Regards,
>>>> Linux Bluetooth
>>>
>>> Copilot flagged a couple of problems:
>>> https://github.com/bluez/bluez/pull/2760#pullrequestreview-5775992507
>> Hi Luiz,
>>
>> The first three comments are valid. v2 will fix all three.
>>
>> I would also like to discuss comment 4, "Clear JPEG image capability
>> until photos are normalized."
>>
>> The Default Contact Image Format feature is mandatory for PBAP 1.2 and
>> requires PHOTO to contain a base64-encoded JPEG of at most 300x300
>> pixels and 50 kB.
>>
>> The three backends currently handle photos differently: Tracker uses the
>> photo URL directly, ebook passes through inline data or a URI, and dummy
>> forwards the PHOTO value from the vCard. This series only advertises the
>> feature bit and does not change these behaviors.
>>
>> I don't think obexd should perform remote network access, image
>> resizing, or transcoding, as that would add unrelated networking and
>> image-processing dependencies.
>>
>> Without those, I see three options:
>>
>> Keep the existing behavior and only advertise bit 9.
>> Validate inline PHOTO data and omit non-conformant or URI-based images.
>> Read local photo URIs, base64-encode JPEGs that meet the requirements,
>> and omit unsupported or non-conformant images.
>>
>> Do you have a preference for how this should be handled?
>
> I don't have a particular preference, it just needs to be spec
> compliant and pass the PTS testing. If each backend uses a different
> format or method, perhaps we need to converge on one. That said, I
> don't think the Linux desktop cares too much about PBAP so perhaps
> this is just to exercise whether PBAP could be qualified? In that case
> I would just use the dummy format, or read it locally and then check
> if it meets the requirements.
The main purpose of these changes is to pass the PTS tests, as PBAP
1.1.1 is scheduled to be deprecated on 2028-02-01. I'll update the
implementation accordingly and send a v2.
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v1 02/18] obexd/pbap: Fix Tracker contact grouping across requests
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
2026-11-04 8:34 ` [PATCH v1 01/18] obexd/pbap: Fix crash counting vCards in dummy backend Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 03/18] obexd/pbap: Include directly bound Tracker telephone numbers Jinwang Li
` (15 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Keep the contact grouping state in the request instead of a static
variable, so it does not carry over to the next request.
Fixes: a8eb063b1db4 ("obexd: Fix pull phonebook with non-zero offset parameter")
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/phonebook-tracker.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/obexd/plugins/phonebook-tracker.c b/obexd/plugins/phonebook-tracker.c
index ba0940c40..5adb52a11 100644
--- a/obexd/plugins/phonebook-tracker.c
+++ b/obexd/plugins/phonebook-tracker.c
@@ -491,6 +491,7 @@ struct phonebook_data {
uint8_t newmissedcalls;
GCancellable *query_canc;
char *req_name;
+ char *last_id;
int vcard_part_count;
int tracker_index;
};
@@ -1250,7 +1251,6 @@ static int pull_contacts(const char **reply, int num_fields, void *user_data)
struct contact_data *contact_data;
int last_index, i;
gboolean cdata_present = FALSE, part_sent = FALSE;
- static char *temp_id = NULL;
if (num_fields < 0) {
data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
@@ -1288,10 +1288,10 @@ static int pull_contacts(const char **reply, int num_fields, void *user_data)
TRACKER_DEFAULT_CONTACT_ME))
return 0;
- if (g_strcmp0(temp_id, reply[CONTACTS_ID_COL])) {
+ if (g_strcmp0(data->last_id, reply[CONTACTS_ID_COL])) {
data->index++;
- g_free(temp_id);
- temp_id = g_strdup(reply[CONTACTS_ID_COL]);
+ g_free(data->last_id);
+ data->last_id = g_strdup(reply[CONTACTS_ID_COL]);
/* Incrementing counter for vcards in current part of data,
* but only if liststartoffset has been already reached */
@@ -1359,9 +1359,6 @@ done:
send_pull_part(data, params, TRUE);
fail:
- g_free(temp_id);
- temp_id = NULL;
-
return -EINTR;
/*
* phonebook_data is freed in phonebook_req_finalize. Useful in
@@ -1568,6 +1565,7 @@ void phonebook_req_finalize(void *request)
free_data_contacts(data);
g_free(data->req_name);
+ g_free(data->last_id);
g_free(data);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 03/18] obexd/pbap: Include directly bound Tracker telephone numbers
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
2026-11-04 8:34 ` [PATCH v1 01/18] obexd/pbap: Fix crash counting vCards in dummy backend Jinwang Li
2026-11-04 8:34 ` [PATCH v1 02/18] obexd/pbap: Fix Tracker contact grouping across requests Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 04/18] obexd/pbap: Reject PullvCardListing without Name in telecom folder Jinwang Li
` (14 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Add the telephone numbers bound directly to a contact to the Tracker
contact queries, in addition to the ones bound to its affiliations.
Fixes: 41f45780b971 ("obexd: Fix for handling fax and cell phone numbers in PBAP")
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/phonebook-tracker.c | 39 +++++++++++++++++--------------
1 file changed, 21 insertions(+), 18 deletions(-)
diff --git a/obexd/plugins/phonebook-tracker.c b/obexd/plugins/phonebook-tracker.c
index 5adb52a11..326bf8892 100644
--- a/obexd/plugins/phonebook-tracker.c
+++ b/obexd/plugins/phonebook-tracker.c
@@ -76,11 +76,14 @@
#define CONTACTS_QUERY_ALL \
"SELECT " \
-"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number)," \
-"\"\31\", nco:phoneNumber(?aff_number)), \"\30\")" \
-"WHERE {" \
-" ?_role nco:hasPhoneNumber ?aff_number" \
-"}) " \
+"fn:concat(tracker:coalesce((SELECT GROUP_CONCAT(" \
+"fn:concat(rdf:type(?aff_number), \"\31\"," \
+"nco:phoneNumber(?aff_number), \"\31A\30\"), \"\") " \
+"WHERE { ?_role nco:hasPhoneNumber ?aff_number }), \"\"), " \
+"tracker:coalesce((SELECT GROUP_CONCAT(" \
+"fn:concat(rdf:type(?con_number), \"\31\"," \
+"nco:phoneNumber(?con_number), \"\31D\30\"), \"\") " \
+"WHERE { ?_contact nco:hasPhoneNumber ?con_number }), \"\")) " \
"nco:fullname(?_contact) " \
"nco:nameFamily(?_contact) " \
"nco:nameGiven(?_contact) " \
@@ -325,11 +328,14 @@ COMBINED_CONSTRAINT \
#define CONTACTS_QUERY_FROM_URI \
"SELECT " \
-"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number)," \
-"\"\31\", nco:phoneNumber(?aff_number)), \"\30\")" \
-"WHERE {" \
-" ?_role nco:hasPhoneNumber ?aff_number" \
-"}) " \
+"fn:concat(tracker:coalesce((SELECT GROUP_CONCAT(" \
+"fn:concat(rdf:type(?aff_number), \"\31\"," \
+"nco:phoneNumber(?aff_number), \"\31A\30\"), \"\") " \
+"WHERE { ?_role nco:hasPhoneNumber ?aff_number }), \"\"), " \
+"tracker:coalesce((SELECT GROUP_CONCAT(" \
+"fn:concat(rdf:type(?con_number), \"\31\"," \
+"nco:phoneNumber(?con_number), \"\31D\30\"), \"\") " \
+"WHERE { <%s> nco:hasPhoneNumber ?con_number }), \"\")) " \
"nco:fullname(<%s>) " \
"nco:nameFamily(<%s>) " \
"nco:nameGiven(<%s>) " \
@@ -1063,10 +1069,8 @@ static void add_aff_number(struct phonebook_contact *contact,
char **num_parts;
char *type, *number;
- /* For phone taken directly from contacts data, phone number string
- * is represented as number type and number string - those strings are
- * separated by SUB_DELIM string */
- num_parts = g_strsplit(pnumber, SUB_DELIM, 2);
+ /* Type and number may be followed by A (affiliation) or D (direct). */
+ num_parts = g_strsplit(pnumber, SUB_DELIM, 3);
if (!num_parts)
return;
@@ -1085,10 +1089,9 @@ static void add_aff_number(struct phonebook_contact *contact,
add_phone_number(contact, number, TEL_TYPE_FAX);
else if (g_strrstr(type, MOBILE_NUM_TYPE))
add_phone_number(contact, number, TEL_TYPE_MOBILE);
+ else if (g_strcmp0(num_parts[2], "D") == 0)
+ add_phone_number(contact, number, TEL_TYPE_OTHER);
else
- /* if this is no fax/mobile phone, then adding phone number
- * type based on type of the affiliation field
- */
add_phone_number(contact, number, get_phone_type(aff_type));
failed:
@@ -1658,7 +1661,7 @@ void *phonebook_get_entry(const char *folder, const char *id,
if (g_str_has_prefix(id, CONTACT_ID_PREFIX) == TRUE ||
g_strcmp0(id, TRACKER_DEFAULT_CONTACT_ME) == 0)
query = g_strdup_printf(CONTACTS_QUERY_FROM_URI, id, id, id, id,
- id, id, id, id, id, id, id, id, id);
+ id, id, id, id, id, id, id, id, id, id);
else if (g_str_has_prefix(id, CALL_ID_PREFIX) == TRUE)
query = g_strdup_printf(CONTACT_FROM_CALL_QUERY, id);
else
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 04/18] obexd/pbap: Reject PullvCardListing without Name in telecom folder
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (2 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 03/18] obexd/pbap: Include directly bound Tracker telephone numbers Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 05/18] obexd/pbap: Fix unselected PHOTO in dummy responses Jinwang Li
` (13 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Return an error for a PullvCardListing request with an empty Name when
the current folder is telecom, which has no vCard listing.
Fixes: 2e2c48dfc234 ("obexd: Initial support for mimetype drivers on PBAP")
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index 3ee54f1fe..436981a4c 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -544,6 +544,10 @@ static int pbap_get(struct obex_session *os, void *user_data)
} else if (g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) {
/* Always relative */
if (!name || strlen(name) == 0) {
+ /* telecom has no vCard listing. */
+ if (g_strcmp0(pbap->folder, PB_TELECOM_FOLDER) == 0)
+ return -ENOENT;
+
/* Current folder */
path = g_strdup(pbap->folder);
} else {
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 05/18] obexd/pbap: Fix unselected PHOTO in dummy responses
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (3 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 04/18] obexd/pbap: Reject PullvCardListing without Name in telecom folder Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 06/18] obexd/pbap: Refresh cache for each vCard listing Jinwang Li
` (12 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Fix the dummy backend returning PHOTO when a nonzero PropertySelector
does not select it in PullPhoneBook and PullvCardEntry responses.
Fixes: f34bc5d924ec ("obexd: Add PBAP PullPhoneBook function for dummy back-end")
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/phonebook-dummy.c | 84 ++++++++++++++++++++++++++++-----
1 file changed, 72 insertions(+), 12 deletions(-)
diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index 14075b502..7c11ac975 100644
--- a/obexd/plugins/phonebook-dummy.c
+++ b/obexd/plugins/phonebook-dummy.c
@@ -193,10 +193,64 @@ static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset,
return 0;
}
+struct concat_data {
+ GString *buffer;
+ const struct apparam_field *params;
+};
+
+static char *filter_vcard_photo(const char *buffer, size_t length,
+ const struct apparam_field *params,
+ size_t *filtered_length)
+{
+ GString *result;
+ char *copy;
+ char **lines;
+ gboolean include = TRUE;
+ unsigned int i;
+
+ /* PHOTO is bit 3 of the PropertySelector. */
+ if (!params->filter || (params->filter & (1ULL << 3))) {
+ *filtered_length = length;
+ return g_strndup(buffer, length);
+ }
+
+ copy = g_strndup(buffer, length);
+ lines = g_strsplit_set(copy, "\r\n", -1);
+ result = g_string_new(NULL);
+
+ for (i = 0; lines[i]; i++) {
+ const char *line = lines[i];
+ const char *end;
+ const char *name;
+
+ if (!line[0])
+ continue;
+
+ if (line[0] != ' ' && line[0] != '\t') {
+ end = strpbrk(line, ";:");
+ name = end ? memrchr(line, '.', end - line) : NULL;
+ name = name ? name + 1 : line;
+ include = !end || end - name != 5 ||
+ g_ascii_strncasecmp(name, "PHOTO", 5) != 0;
+ }
+
+ if (include)
+ g_string_append_printf(result, "%s\r\n", line);
+ }
+
+ g_strfreev(lines);
+ g_free(copy);
+
+ *filtered_length = result->len;
+ return g_string_free(result, FALSE);
+}
+
static void entry_concat(const char *filename, VObject *v, void *user_data)
{
- GString *buffer = user_data;
+ struct concat_data *data = user_data;
+ char *filtered;
char tmp[1024];
+ size_t filtered_len;
int len;
/*
@@ -207,19 +261,22 @@ static void entry_concat(const char *filename, VObject *v, void *user_data)
memset(tmp, 0, len);
writeMemVObject(tmp, &len, v);
+ filtered = filter_vcard_photo(tmp, len, data->params,
+ &filtered_len);
- /* FIXME: only the requested fields must be added */
- g_string_append_len(buffer, tmp, len);
+ g_string_append_len(data->buffer, filtered, filtered_len);
+ g_free(filtered);
}
static gboolean read_dir(void *user_data)
{
struct dummy_data *dummy = user_data;
- GString *buffer;
+ struct concat_data data;
DIR *dp;
uint16_t count = 0, max, offset;
- buffer = g_string_new("");
+ data.buffer = g_string_new("");
+ data.params = dummy->apparams;
dp = opendir(dummy->folder);
if (dp == NULL) {
@@ -242,15 +299,15 @@ static gboolean read_dir(void *user_data)
offset = dummy->apparams->liststartoffset;
}
- foreach_vcard(dp, entry_concat, offset, max, buffer, &count);
+ foreach_vcard(dp, entry_concat, offset, max, &data, &count);
closedir(dp);
done:
/* FIXME: Missing vCards fields filtering */
- dummy->cb(buffer->str, buffer->len, count, dummy->newmissedcalls,
- TRUE, dummy->user_data);
+ dummy->cb(data.buffer->str, data.buffer->len, count,
+ dummy->newmissedcalls, TRUE, dummy->user_data);
- g_string_free(buffer, TRUE);
+ g_string_free(data.buffer, TRUE);
return FALSE;
}
@@ -332,7 +389,9 @@ static gboolean create_cache(void *user_data)
static gboolean read_entry(void *user_data)
{
struct dummy_data *dummy = user_data;
+ char *filtered;
char buffer[1024];
+ size_t filtered_len;
ssize_t count;
memset(buffer, 0, sizeof(buffer));
@@ -344,9 +403,10 @@ static gboolean read_entry(void *user_data)
count = 0;
}
- /* FIXME: Missing vCards fields filtering */
-
- dummy->cb(buffer, count, 1, 0, TRUE, dummy->user_data);
+ filtered = filter_vcard_photo(buffer, count, dummy->apparams,
+ &filtered_len);
+ dummy->cb(filtered, filtered_len, 1, 0, TRUE, dummy->user_data);
+ g_free(filtered);
return FALSE;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 06/18] obexd/pbap: Refresh cache for each vCard listing
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (4 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 05/18] obexd/pbap: Fix unselected PHOTO in dummy responses Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 07/18] obexd/pbap: Limit NewMissedCalls to mch responses Jinwang Li
` (11 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Rebuild the cache for each PullvCardListing request. Return current
folder entries on repeated requests within a connection.
Fixes: 581b305d17b5 ("obexd: Changes the interface between the PBAP core and backends")
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 20 ++++++--------------
1 file changed, 6 insertions(+), 14 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index 436981a4c..9176fc92a 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -735,7 +735,6 @@ static void *vobject_list_open(const char *name, int oflag, mode_t mode,
void *context, size_t *size, int *err)
{
struct pbap_session *pbap = context;
- struct pbap_object *obj = NULL;
int ret;
void *request;
@@ -751,29 +750,22 @@ static void *vobject_list_open(const char *name, int oflag, mode_t mode,
goto fail;
}
- /* PullvCardListing always get the contacts from the cache */
+ /* The cache is rebuilt for each listing request. */
+ pbap->cache.valid = FALSE;
+ pbap->cache.index = 0;
+ cache_clear(&pbap->cache);
- if (pbap->cache.valid) {
- obj = vobject_create(pbap, NULL);
- ret = generate_response(pbap);
- } else {
- request = phonebook_create_cache(name, cache_entry_notify,
+ request = phonebook_create_cache(name, cache_entry_notify,
cache_ready_notify, pbap, &ret);
- if (ret == 0)
- obj = vobject_create(pbap, request);
- }
if (ret < 0)
goto fail;
if (err)
*err = 0;
- return obj;
+ return vobject_create(pbap, request);
fail:
- if (obj)
- vobject_close(obj);
-
if (err)
*err = ret;
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 07/18] obexd/pbap: Limit NewMissedCalls to mch responses
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (5 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 06/18] obexd/pbap: Refresh cache for each vCard listing Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 08/18] obexd/pbap: Add NewMissedCalls to vCard listing responses Jinwang Li
` (10 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Include NewMissedCalls only in mch responses and send it in the
first response packet.
Fixes: f82b81e01502 ("obexd: New NewMissedCalls added for phonebook_pull.")
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 56 +++++++++++++++++++++++++++++---------------
1 file changed, 37 insertions(+), 19 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index 9176fc92a..e0d0cf1ab 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -77,6 +77,7 @@ struct pbap_session {
struct pbap_object {
GString *buffer;
GObexApparam *apparam;
+ char *folder;
gboolean firstpacket;
gboolean lastpart;
struct pbap_session *session;
@@ -158,6 +159,22 @@ static void cache_clear(struct cache *cache)
cache->entries = NULL;
}
+static void response_add_missed_calls(struct pbap_session *pbap, uint8_t missed)
+{
+ const char *folder = pbap->obj->folder;
+ gboolean include;
+
+ include = folder && g_str_has_suffix(folder, PB_CALLS_MISSED_FOLDER);
+
+ if (!include)
+ return;
+
+ DBG("missed %u", missed);
+
+ pbap->obj->apparam = g_obex_apparam_set_uint8(pbap->obj->apparam,
+ NEWMISSEDCALLS_TAG, missed);
+}
+
static void phonebook_size_result(const char *buffer, size_t bufsize,
int vcards, uint8_t missed,
gboolean lastpart, void *user_data)
@@ -182,13 +199,7 @@ static void phonebook_size_result(const char *buffer, size_t bufsize,
pbap->obj->firstpacket = TRUE;
- if (missed > 0) {
- DBG("missed %d", missed);
- }
-
- pbap->obj->apparam = g_obex_apparam_set_uint8(pbap->obj->apparam,
- NEWMISSEDCALLS_TAG,
- missed);
+ response_add_missed_calls(pbap, missed);
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
@@ -197,6 +208,7 @@ static void query_result(const char *buffer, size_t bufsize, int vcards,
uint8_t missed, gboolean lastpart, void *user_data)
{
struct pbap_session *pbap = user_data;
+ gboolean first;
DBG("");
@@ -212,22 +224,22 @@ static void query_result(const char *buffer, size_t bufsize, int vcards,
return;
}
- if (!pbap->obj->buffer)
+ first = !pbap->obj->buffer;
+
+ if (first)
pbap->obj->buffer = g_string_new_len(buffer, bufsize);
else
pbap->obj->buffer = g_string_append_len(pbap->obj->buffer,
buffer, bufsize);
- if (missed > 0) {
- DBG("missed %d", missed);
+ /* Application parameters go in the first response packet only */
+ if (first) {
+ response_add_missed_calls(pbap, missed);
- pbap->obj->firstpacket = TRUE;
+ if (pbap->obj->apparam)
+ pbap->obj->firstpacket = TRUE;
}
- pbap->obj->apparam = g_obex_apparam_set_uint8(pbap->obj->apparam,
- NEWMISSEDCALLS_TAG,
- missed);
-
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
@@ -648,7 +660,7 @@ static const struct obex_service_driver pbap = {
};
static struct pbap_object *vobject_create(struct pbap_session *pbap,
- void *request)
+ void *request, const char *path)
{
struct pbap_object *obj;
@@ -657,6 +669,11 @@ static struct pbap_object *vobject_create(struct pbap_session *pbap,
pbap->obj = obj;
obj->request = request;
+ if (g_str_has_suffix(path, ".vcf"))
+ obj->folder = g_strndup(path, strlen(path) - 4);
+ else
+ obj->folder = g_strdup(path);
+
return obj;
}
@@ -699,7 +716,7 @@ static void *vobject_pull_open(const char *name, int oflag, mode_t mode,
if (err)
*err = 0;
- return vobject_create(pbap, request);
+ return vobject_create(pbap, request, name);
fail:
if (err)
@@ -726,6 +743,7 @@ static int vobject_close(void *object)
if (obj->request)
phonebook_req_finalize(obj->request);
+ g_free(obj->folder);
g_free(obj);
return 0;
@@ -763,7 +781,7 @@ static void *vobject_list_open(const char *name, int oflag, mode_t mode,
if (err)
*err = 0;
- return vobject_create(pbap, request);
+ return vobject_create(pbap, request, name);
fail:
if (err)
@@ -816,7 +834,7 @@ done:
if (err)
*err = 0;
- return vobject_create(pbap, request);
+ return vobject_create(pbap, request, pbap->folder);
fail:
if (err)
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 08/18] obexd/pbap: Add NewMissedCalls to vCard listing responses
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (6 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 07/18] obexd/pbap: Limit NewMissedCalls to mch responses Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 09/18] obexd/pbap: Separate vCard entry response handling Jinwang Li
` (9 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Pass the missed call count from the backends to the PBAP core and
include NewMissedCalls in vCard listing responses.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 14 ++++++----
obexd/plugins/phonebook-dummy.c | 10 ++++++-
obexd/plugins/phonebook-ebook.c | 2 +-
obexd/plugins/phonebook-tracker.c | 44 +++++++++++++++++++++++++------
obexd/plugins/phonebook.h | 7 ++---
5 files changed, 57 insertions(+), 20 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index e0d0cf1ab..02664e538 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -353,15 +353,16 @@ static GSList *sort_entries(GSList *l, uint8_t order, uint8_t search_attrib,
return sorted;
}
-static int generate_response(void *user_data)
+static int generate_response(struct pbap_session *pbap, uint8_t missed)
{
- struct pbap_session *pbap = user_data;
GSList *sorted;
GSList *l;
uint16_t max = pbap->params->maxlistcount;
DBG("");
+ response_add_missed_calls(pbap, missed);
+
if (max == 0) {
/* Ignore all other parameter and return PhoneBookSize */
uint16_t size = g_slist_length(pbap->cache.entries);
@@ -401,10 +402,13 @@ static int generate_response(void *user_data)
VCARD_LISTING_END);
g_slist_free(sorted);
+ if (pbap->obj->apparam)
+ pbap->obj->firstpacket = TRUE;
+
return 0;
}
-static void cache_ready_notify(void *user_data)
+static void cache_ready_notify(uint8_t missed, void *user_data)
{
struct pbap_session *pbap = user_data;
@@ -415,11 +419,11 @@ static void cache_ready_notify(void *user_data)
pbap->cache.valid = TRUE;
- generate_response(pbap);
+ generate_response(pbap, missed);
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
-static void cache_entry_done(void *user_data)
+static void cache_entry_done(uint8_t missed, void *user_data)
{
struct pbap_session *pbap = user_data;
const char *id;
diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index 7c11ac975..237349bef 100644
--- a/obexd/plugins/phonebook-dummy.c
+++ b/obexd/plugins/phonebook-dummy.c
@@ -49,6 +49,7 @@ struct cache_query {
phonebook_cache_ready_cb ready_cb;
void *user_data;
DIR *dp;
+ uint8_t newmissedcalls;
};
static char *root_folder = NULL;
@@ -381,7 +382,7 @@ static gboolean create_cache(void *user_data)
*/
foreach_vcard(query->dp, entry_notify, 0, 0xffff, query, NULL);
- query->ready_cb(query->user_data);
+ query->ready_cb(query->newmissedcalls, query->user_data);
return FALSE;
}
@@ -653,6 +654,13 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
query->user_data = user_data;
query->dp = dp;
+ if (g_str_equal(name, PB_CALLS_MISSED_FOLDER)) {
+ foldername = g_build_filename(root_folder,
+ PB_CALLS_MISSED_FOLDER, NULL);
+ query->newmissedcalls = count_vcards(foldername);
+ g_free(foldername);
+ }
+
dummy = g_new0(struct dummy_data, 1);
dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache,
diff --git a/obexd/plugins/phonebook-ebook.c b/obexd/plugins/phonebook-ebook.c
index beb72b22b..0df8f2d0e 100644
--- a/obexd/plugins/phonebook-ebook.c
+++ b/obexd/plugins/phonebook-ebook.c
@@ -540,7 +540,7 @@ static void phonebook_create_cache_ready(GObject *source_object,
done:
if (data->queued_calls == 0)
- data->ready_cb(data->user_data);
+ data->ready_cb(0, data->user_data);
return;
diff --git a/obexd/plugins/phonebook-tracker.c b/obexd/plugins/phonebook-tracker.c
index 326bf8892..07d8dd34b 100644
--- a/obexd/plugins/phonebook-tracker.c
+++ b/obexd/plugins/phonebook-tracker.c
@@ -480,6 +480,12 @@ struct pending_reply {
int num_fields;
};
+enum phonebook_request {
+ PHONEBOOK_REQUEST_PULL,
+ PHONEBOOK_REQUEST_ENTRY,
+ PHONEBOOK_REQUEST_CACHE,
+};
+
struct contact_data {
char *id;
struct phonebook_contact *contact;
@@ -489,7 +495,7 @@ struct phonebook_data {
phonebook_cb cb;
void *user_data;
int index;
- gboolean vcardentry;
+ enum phonebook_request request;
const struct apparam_field *params;
GSList *contacts;
phonebook_cache_ready_cb ready_cb;
@@ -1278,7 +1284,7 @@ static int pull_contacts(const char **reply, int num_fields, void *user_data)
}
/* We are doing a PullvCardEntry, no need for those checks */
- if (data->vcardentry)
+ if (data->request == PHONEBOOK_REQUEST_ENTRY)
goto add_entry;
/* Last four fields are always present, ignoring them */
@@ -1411,7 +1417,8 @@ static int add_to_cache(const char **reply, int num_fields, void *user_data)
done:
if (num_fields <= 0)
- data->ready_cb(data->user_data);
+ data->ready_cb(num_fields < 0 ? 0 : data->newmissedcalls,
+ data->user_data);
return -EINTR;
/*
@@ -1517,7 +1524,11 @@ static int pull_newmissedcalls(const char **reply, int num_fields,
int nmissed;
if (num_fields < 0) {
- data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
+ if (data->request == PHONEBOOK_REQUEST_CACHE)
+ data->ready_cb(0, data->user_data);
+ else
+ data->cb(NULL, 0, num_fields, 0, TRUE,
+ data->user_data);
return -EINTR;
}
@@ -1531,7 +1542,12 @@ static int pull_newmissedcalls(const char **reply, int num_fields,
return 0;
}
- if (data->params->maxlistcount == 0) {
+ if (data->request == PHONEBOOK_REQUEST_CACHE) {
+ query = folder2query(data->req_name);
+ col_amount = 8;
+ pull_cb = add_to_cache;
+ } else if (data->request == PHONEBOOK_REQUEST_PULL &&
+ data->params->maxlistcount == 0) {
query = name2count_query(PB_CALLS_MISSED);
col_amount = COUNT_QUERY_COL_AMOUNT;
pull_cb = pull_contacts_size;
@@ -1543,7 +1559,10 @@ static int pull_newmissedcalls(const char **reply, int num_fields,
err = query_tracker(query, col_amount, pull_cb, data);
if (err < 0) {
- data->cb(NULL, 0, err, 0, TRUE, data->user_data);
+ if (data->request == PHONEBOOK_REQUEST_CACHE)
+ data->ready_cb(0, data->user_data);
+ else
+ data->cb(NULL, 0, err, 0, TRUE, data->user_data);
return -EINTR;
}
@@ -1580,6 +1599,7 @@ void *phonebook_pull(const char *name, const struct apparam_field *params,
DBG("name %s", name);
data = g_new0(struct phonebook_data, 1);
+ data->request = PHONEBOOK_REQUEST_PULL;
data->params = params;
data->user_data = user_data;
data->cb = cb;
@@ -1656,7 +1676,7 @@ void *phonebook_get_entry(const char *folder, const char *id,
data->user_data = user_data;
data->params = params;
data->cb = cb;
- data->vcardentry = TRUE;
+ data->request = PHONEBOOK_REQUEST_ENTRY;
if (g_str_has_prefix(id, CONTACT_ID_PREFIX) == TRUE ||
g_strcmp0(id, TRACKER_DEFAULT_CONTACT_ME) == 0)
@@ -1694,11 +1714,19 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
}
data = g_new0(struct phonebook_data, 1);
+ data->request = PHONEBOOK_REQUEST_CACHE;
data->entry_cb = entry_cb;
data->ready_cb = ready_cb;
data->user_data = user_data;
+ data->req_name = g_strdup(name);
+
+ if (g_str_equal(name, PB_CALLS_MISSED_FOLDER))
+ ret = query_tracker(NEW_MISSED_CALLS_COUNT_QUERY,
+ COUNT_QUERY_COL_AMOUNT,
+ pull_newmissedcalls, data);
+ else
+ ret = query_tracker(query, 8, add_to_cache, data);
- ret = query_tracker(query, 8, add_to_cache, data);
if (err)
*err = ret;
diff --git a/obexd/plugins/phonebook.h b/obexd/plugins/phonebook.h
index 7e99de6e7..9ab5b72f6 100644
--- a/obexd/plugins/phonebook.h
+++ b/obexd/plugins/phonebook.h
@@ -72,11 +72,8 @@ typedef void (*phonebook_entry_cb) (const char *id, uint32_t handle,
const char *name, const char *sound,
const char *tel, void *user_data);
-/*
- * After notify all entries to PBAP core, the backend
- * needs to notify that the operation has finished.
- */
-typedef void (*phonebook_cache_ready_cb) (void *user_data);
+/* Notifies cache completion and the number of undismissed missed calls. */
+typedef void (*phonebook_cache_ready_cb) (uint8_t missed, void *user_data);
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 09/18] obexd/pbap: Separate vCard entry response handling
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (7 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 08/18] obexd/pbap: Add NewMissedCalls to vCard listing responses Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 10/18] obexd: Parse application parameters in CONNECT request Jinwang Li
` (8 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Separate the vCard entry response callback from phonebook responses
and share buffer handling between them.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 42 ++++++++++++++++++++++++++++++++++--------
1 file changed, 34 insertions(+), 8 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index 02664e538..83256c98c 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -204,14 +204,12 @@ static void phonebook_size_result(const char *buffer, size_t bufsize,
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
-static void query_result(const char *buffer, size_t bufsize, int vcards,
- uint8_t missed, gboolean lastpart, void *user_data)
+/* Returns -1 on error, otherwise whether this is the first packet. */
+static int vobject_append(struct pbap_session *pbap, const char *buffer,
+ size_t bufsize, int vcards, gboolean lastpart)
{
- struct pbap_session *pbap = user_data;
gboolean first;
- DBG("");
-
if (pbap->obj->request && lastpart) {
phonebook_req_finalize(pbap->obj->request);
pbap->obj->request = NULL;
@@ -221,7 +219,7 @@ static void query_result(const char *buffer, size_t bufsize, int vcards,
if (vcards < 0) {
obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT);
- return;
+ return -1;
}
first = !pbap->obj->buffer;
@@ -232,6 +230,21 @@ static void query_result(const char *buffer, size_t bufsize, int vcards,
pbap->obj->buffer = g_string_append_len(pbap->obj->buffer,
buffer, bufsize);
+ return first;
+}
+
+static void query_result(const char *buffer, size_t bufsize, int vcards,
+ uint8_t missed, gboolean lastpart, void *user_data)
+{
+ struct pbap_session *pbap = user_data;
+ int first;
+
+ DBG("");
+
+ first = vobject_append(pbap, buffer, bufsize, vcards, lastpart);
+ if (first < 0)
+ return;
+
/* Application parameters go in the first response packet only */
if (first) {
response_add_missed_calls(pbap, missed);
@@ -243,6 +256,19 @@ static void query_result(const char *buffer, size_t bufsize, int vcards,
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
+static void vcard_result(const char *buffer, size_t bufsize, int vcards,
+ uint8_t missed, gboolean lastpart, void *user_data)
+{
+ struct pbap_session *pbap = user_data;
+ int first;
+
+ first = vobject_append(pbap, buffer, bufsize, vcards, lastpart);
+ if (first < 0)
+ return;
+
+ obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
+}
+
static void cache_entry_notify(const char *id, uint32_t handle,
const char *name, const char *sound,
const char *tel, void *user_data)
@@ -442,7 +468,7 @@ static void cache_entry_done(uint8_t missed, void *user_data)
phonebook_req_finalize(pbap->obj->request);
pbap->obj->request = phonebook_get_entry(pbap->folder, id,
- pbap->params, query_result, pbap, &ret);
+ pbap->params, vcard_result, pbap, &ret);
if (ret < 0)
obex_object_set_io_flags(pbap->obj, G_IO_ERR, ret);
}
@@ -829,7 +855,7 @@ static void *vobject_vcard_open(const char *name, int oflag, mode_t mode,
}
request = phonebook_get_entry(pbap->folder, id, pbap->params,
- query_result, pbap, &ret);
+ vcard_result, pbap, &ret);
done:
if (ret < 0)
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 10/18] obexd: Parse application parameters in CONNECT request
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (8 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 09/18] obexd/pbap: Separate vCard entry response handling Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 11/18] obexd/pbap: Parse PbapSupportedFeatures in connect request Jinwang Li
` (7 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Make the application parameters of a CONNECT request available to the
service plugins while the connection is being set up.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/src/obex.c | 42 ++++++++++++++++++++++++------------------
1 file changed, 24 insertions(+), 18 deletions(-)
diff --git a/obexd/src/obex.c b/obexd/src/obex.c
index 370bfac9e..4d2c86b6a 100644
--- a/obexd/src/obex.c
+++ b/obexd/src/obex.c
@@ -254,6 +254,24 @@ probe:
who, who_size);
}
+static void parse_apparam(struct obex_session *os, GObexPacket *req)
+{
+ GObexHeader *hdr;
+ const guint8 *apparam;
+ gsize len;
+
+ hdr = g_obex_packet_get_header(req, G_OBEX_HDR_APPARAM);
+ if (hdr == NULL)
+ return;
+
+ if (!g_obex_header_get_bytes(hdr, &apparam, &len))
+ return;
+
+ os->apparam = util_memdup(apparam, len);
+ os->apparam_len = len;
+ DBG("APPARAM");
+}
+
static void cmd_connect(GObex *obex, GObexPacket *req, void *user_data)
{
struct obex_session *os = user_data;
@@ -275,7 +293,13 @@ static void cmd_connect(GObex *obex, GObexPacket *req, void *user_data)
DBG("Selected driver: %s", os->service->name);
+ parse_apparam(os, req);
+
os->service_data = os->service->connect(os, &err);
+ free(os->apparam);
+ os->apparam = NULL;
+ os->apparam_len = 0;
+
if (err < 0) {
os_set_response(os, err);
return;
@@ -582,24 +606,6 @@ static void parse_name(struct obex_session *os, GObexPacket *req)
DBG("NAME: %s", os->name);
}
-static void parse_apparam(struct obex_session *os, GObexPacket *req)
-{
- GObexHeader *hdr;
- const guint8 *apparam;
- gsize len;
-
- hdr = g_obex_packet_get_header(req, G_OBEX_HDR_APPARAM);
- if (hdr == NULL)
- return;
-
- if (!g_obex_header_get_bytes(hdr, &apparam, &len))
- return;
-
- os->apparam = util_memdup(apparam, len);
- os->apparam_len = len;
- DBG("APPARAM");
-}
-
static void cmd_get(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct obex_session *os = user_data;
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 11/18] obexd/pbap: Parse PbapSupportedFeatures in connect request
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (9 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 10/18] obexd: Parse application parameters in CONNECT request Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 12/18] obexd/pbap: Add persistent database identifier Jinwang Li
` (6 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Parse the PbapSupportedFeatures tag of the CONNECT request, combine it
with the features supported by the PSE and store the result in the
session.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 47 +++++++++++++++++++++++++++++++++++---------
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index 83256c98c..92794b968 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -42,15 +42,22 @@
#define VCARDLISTING_TYPE "x-bt/vcard-listing"
#define VCARDENTRY_TYPE "x-bt/vcard"
-#define ORDER_TAG 0x01
-#define SEARCHVALUE_TAG 0x02
-#define SEARCHATTRIB_TAG 0x03
-#define MAXLISTCOUNT_TAG 0x04
-#define LISTSTARTOFFSET_TAG 0x05
-#define FILTER_TAG 0x06
-#define FORMAT_TAG 0X07
-#define PHONEBOOKSIZE_TAG 0X08
-#define NEWMISSEDCALLS_TAG 0X09
+#define ORDER_TAG 0x01
+#define SEARCHVALUE_TAG 0x02
+#define SEARCHATTRIB_TAG 0x03
+#define MAXLISTCOUNT_TAG 0x04
+#define LISTSTARTOFFSET_TAG 0x05
+#define FILTER_TAG 0x06
+#define FORMAT_TAG 0x07
+#define PHONEBOOKSIZE_TAG 0x08
+#define NEWMISSEDCALLS_TAG 0x09
+#define SUPPORTED_FEATURES_TAG 0x10
+
+#define DOWNLOAD_FEATURE 0x00000001
+#define BROWSE_FEATURE 0x00000002
+
+#define PSE_SUPPORTED_FEATURES \
+ (DOWNLOAD_FEATURE | BROWSE_FEATURE)
struct cache {
gboolean valid;
@@ -72,6 +79,7 @@ struct pbap_session {
uint32_t find_handle;
struct cache cache;
struct pbap_object *obj;
+ uint32_t negotiated_features;
};
struct pbap_object {
@@ -513,6 +521,10 @@ static struct apparam_field *parse_aparam(const uint8_t *buffer, uint32_t hlen)
static void *pbap_connect(struct obex_session *os, int *err)
{
struct pbap_session *pbap;
+ const uint8_t *buffer;
+ GObexApparam *apparam;
+ uint32_t pce_features;
+ ssize_t rsize;
manager_register_session(os);
@@ -520,6 +532,23 @@ static void *pbap_connect(struct obex_session *os, int *err)
pbap->folder = g_strdup("/");
pbap->find_handle = PHONEBOOK_INVALID_HANDLE;
+ rsize = obex_get_apparam(os, &buffer);
+ pce_features = DOWNLOAD_FEATURE | BROWSE_FEATURE;
+
+ if (rsize > 0) {
+ apparam = g_obex_apparam_decode(buffer, rsize);
+ if (apparam) {
+ g_obex_apparam_get_uint32(apparam,
+ SUPPORTED_FEATURES_TAG,
+ &pce_features);
+ g_obex_apparam_free(apparam);
+ }
+ }
+
+ pbap->negotiated_features = PSE_SUPPORTED_FEATURES & pce_features;
+
+ DBG("negotiated features 0x%08x", pbap->negotiated_features);
+
if (err)
*err = 0;
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 12/18] obexd/pbap: Add persistent database identifier
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (10 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 11/18] obexd/pbap: Parse PbapSupportedFeatures in connect request Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 13/18] obexd/pbap: Include database identifier in responses Jinwang Li
` (5 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Add a 128-bit Database Identifier for the PSE. The dummy backend
generates it when it is missing and stores it in a metadata file. The
ebook and tracker backends do not persist it and report it as zero.
Assisted-by: Claude:claude-sonnet-5-5
---
Makefile.obexd | 2 +
obexd/plugins/phonebook-dummy.c | 23 ++++
obexd/plugins/phonebook-metadata.c | 194 +++++++++++++++++++++++++++++
obexd/plugins/phonebook-metadata.h | 45 +++++++
obexd/plugins/phonebook.c | 7 ++
obexd/plugins/phonebook.h | 11 ++
6 files changed, 282 insertions(+)
create mode 100644 obexd/plugins/phonebook-metadata.c
create mode 100644 obexd/plugins/phonebook-metadata.h
diff --git a/Makefile.obexd b/Makefile.obexd
index 9daa5aac2..4b3d08f01 100644
--- a/Makefile.obexd
+++ b/Makefile.obexd
@@ -49,6 +49,8 @@ obexd_builtin_sources += obexd/plugins/pbap.c \
obexd/plugins/vcard.h obexd/plugins/vcard.c \
obexd/plugins/phonebook.h \
obexd/plugins/phonebook.c \
+ obexd/plugins/phonebook-metadata.h \
+ obexd/plugins/phonebook-metadata.c \
obexd/plugins/phonebook-@PLUGIN_PHONEBOOK@.c
EXTRA_DIST += obexd/plugins/phonebook-dummy.c obexd/plugins/phonebook-ebook.c \
obexd/plugins/phonebook-tracker.c
diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index 237349bef..698510f6f 100644
--- a/obexd/plugins/phonebook-dummy.c
+++ b/obexd/plugins/phonebook-dummy.c
@@ -31,6 +31,7 @@
#include "obexd/src/log.h"
#include "phonebook.h"
+#include "phonebook-metadata.h"
typedef void (*vcard_func_t) (const char *file, VObject *vo, void *user_data);
@@ -75,14 +76,36 @@ static void query_free(void *user_data)
g_free(query);
}
+static void dummy_metadata_init(void)
+{
+ metadata_identifier_update();
+ metadata_save();
+}
+
int phonebook_driver_init(void)
{
+ char *metadata_path;
+ uint8_t id[16];
+
if (root_folder)
return 0;
/* FIXME: It should NOT be hard-coded */
root_folder = g_build_filename(getenv("HOME"), "phonebook", NULL);
+ metadata_path = g_build_filename(root_folder,
+ "phonebook-metadata", NULL);
+
+ if (!metadata_file_open(metadata_path)) {
+ g_free(metadata_path);
+ return 0;
+ }
+
+ g_free(metadata_path);
+
+ if (!metadata_get("identifier", "value", id))
+ dummy_metadata_init();
+
return 0;
}
diff --git a/obexd/plugins/phonebook-metadata.c b/obexd/plugins/phonebook-metadata.c
new file mode 100644
index 000000000..06cfa2a8a
--- /dev/null
+++ b/obexd/plugins/phonebook-metadata.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * OBEX Server
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "obexd/src/log.h"
+#include "phonebook.h"
+#include "phonebook-metadata.h"
+
+static char *metadata_file;
+static GKeyFile *metadata_kf;
+static gboolean metadata_dirty;
+
+gboolean metadata_get(const char *group,
+ const char *key, uint8_t buf[16])
+{
+ char *str;
+ uint8_t value[16];
+ unsigned int i;
+
+ if (!metadata_kf)
+ return FALSE;
+
+ str = g_key_file_get_string(metadata_kf, group, key, NULL);
+ if (!str || strlen(str) != 32)
+ goto failed;
+
+ for (i = 0; i < 16; i++) {
+ int high = g_ascii_xdigit_value(str[i * 2]);
+ int low = g_ascii_xdigit_value(str[i * 2 + 1]);
+
+ if (high < 0 || low < 0)
+ goto failed;
+
+ value[i] = (high << 4) | low;
+ }
+
+ memcpy(buf, value, sizeof(value));
+ g_free(str);
+ return TRUE;
+
+failed:
+ g_free(str);
+ return FALSE;
+}
+
+void metadata_set(const char *group, const char *key,
+ const uint8_t buf[16])
+{
+ char hex[33];
+ unsigned int i;
+
+ if (!metadata_file || !metadata_kf)
+ return;
+
+ for (i = 0; i < 16; i++)
+ sprintf(hex + (i * 2), "%02X", buf[i]);
+
+ g_key_file_set_string(metadata_kf, group, key, hex);
+ metadata_dirty = TRUE;
+}
+
+void metadata_identifier_update(void)
+{
+ uint8_t identifier[16];
+ unsigned int i;
+
+ if (!metadata_file || !metadata_kf)
+ return;
+
+ for (i = 0; i < 16; i += sizeof(uint32_t)) {
+ uint32_t value = g_random_int();
+
+ identifier[i] = (value >> 24) & 0xff;
+ identifier[i + 1] = (value >> 16) & 0xff;
+ identifier[i + 2] = (value >> 8) & 0xff;
+ identifier[i + 3] = value & 0xff;
+ }
+
+ metadata_set("identifier", "value", identifier);
+}
+
+gboolean metadata_save(void)
+{
+ char *data;
+ gsize len;
+ GError *gerr = NULL;
+
+ if (!metadata_file || !metadata_kf)
+ return FALSE;
+
+ if (!metadata_dirty)
+ return TRUE;
+
+ data = g_key_file_to_data(metadata_kf, &len, NULL);
+
+ if (len > 0) {
+ if (!g_file_set_contents(metadata_file, data, len, &gerr)) {
+ error("Unable to save metadata file: %s",
+ gerr->message);
+ g_error_free(gerr);
+ g_free(data);
+ return FALSE;
+ }
+ }
+
+ g_free(data);
+ metadata_dirty = FALSE;
+
+ return TRUE;
+}
+
+gboolean metadata_file_open(const char *file)
+{
+ GError *gerr = NULL;
+
+ if (!file)
+ return FALSE;
+
+ metadata_file = g_strdup(file);
+ if (!metadata_file)
+ return FALSE;
+
+ if (!g_file_test(metadata_file, G_FILE_TEST_EXISTS)) {
+ if (!g_file_set_contents(metadata_file, "", 0, &gerr)) {
+ error("Unable to create metadata file: %s",
+ gerr->message);
+ g_error_free(gerr);
+ gerr = NULL;
+ }
+ }
+
+ metadata_kf = g_key_file_new();
+
+ if (metadata_kf && !g_key_file_load_from_file(metadata_kf,
+ metadata_file, 0, &gerr)) {
+ error("Unable to load metadata file: %s", gerr->message);
+ g_error_free(gerr);
+ g_key_file_free(metadata_kf);
+ metadata_kf = NULL;
+ g_free(metadata_file);
+ metadata_file = NULL;
+ return FALSE;
+ }
+
+ metadata_dirty = FALSE;
+
+ return TRUE;
+}
+
+gboolean metadata_read(struct phonebook_metadata *metadata)
+{
+ memset(metadata, 0, sizeof(*metadata));
+
+ if (!metadata_file || !metadata_kf)
+ return FALSE;
+
+ /* Values which are not saved are reported as not persistent. */
+ if (metadata_dirty)
+ return FALSE;
+
+ if (!metadata_get("identifier", "value", metadata->database_identifier))
+ DBG("identifier not found in metadata");
+
+ return TRUE;
+}
+
+void metadata_file_close(void)
+{
+ if (metadata_kf) {
+ g_key_file_free(metadata_kf);
+ metadata_kf = NULL;
+ }
+
+ if (metadata_file) {
+ g_free(metadata_file);
+ metadata_file = NULL;
+ }
+}
diff --git a/obexd/plugins/phonebook-metadata.h b/obexd/plugins/phonebook-metadata.h
new file mode 100644
index 000000000..c70c20ecd
--- /dev/null
+++ b/obexd/plugins/phonebook-metadata.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ * OBEX Server
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *
+ */
+
+#include <stdint.h>
+
+#include <glib.h>
+
+struct phonebook_metadata;
+
+/*
+ * Opens and loads the metadata file, creating it when it doesn't exist.
+ * The key file is cached internally. Returns FALSE on error.
+ */
+gboolean metadata_file_open(const char *file);
+
+/* Closes the metadata file and releases cached resources. */
+void metadata_file_close(void);
+
+/* Reads metadata and zeroes invalid or unsaved values. */
+gboolean metadata_read(struct phonebook_metadata *metadata);
+
+/* Stores a hex encoded byte array. */
+void metadata_set(const char *group, const char *key,
+ const uint8_t buf[16]);
+
+/*
+ * Reads a hex encoded byte array. Returns FALSE when the value is
+ * missing or invalid.
+ */
+gboolean metadata_get(const char *group, const char *key, uint8_t buf[16]);
+
+/* Generates and stores a new database identifier. */
+void metadata_identifier_update(void);
+
+/*
+ * Persists the metadata file. Returns TRUE on success.
+ */
+gboolean metadata_save(void);
diff --git a/obexd/plugins/phonebook.c b/obexd/plugins/phonebook.c
index 6afdb7abb..059414446 100644
--- a/obexd/plugins/phonebook.c
+++ b/obexd/plugins/phonebook.c
@@ -17,6 +17,7 @@
#include <glib.h>
#include "phonebook.h"
+#include "phonebook-metadata.h"
static unsigned int refcount = 0;
@@ -44,4 +45,10 @@ void phonebook_exit(void)
return;
phonebook_driver_exit();
+ metadata_file_close();
+}
+
+int phonebook_get_metadata(struct phonebook_metadata *metadata)
+{
+ return metadata_read(metadata);
}
diff --git a/obexd/plugins/phonebook.h b/obexd/plugins/phonebook.h
index 9ab5b72f6..6fb91f7e0 100644
--- a/obexd/plugins/phonebook.h
+++ b/obexd/plugins/phonebook.h
@@ -55,6 +55,11 @@ struct apparam_field {
char *searchval;
};
+/* Metadata identifying the current database contents. */
+struct phonebook_metadata {
+ uint8_t database_identifier[16];
+};
+
/*
* Interface between the PBAP core and backends to retrieve
* all contacts that match the application parameters rules.
@@ -99,6 +104,12 @@ void phonebook_driver_exit(void);
char *phonebook_set_folder(const char *current_folder,
const char *new_folder, uint8_t flags, int *err);
+/*
+ * Returns the metadata identifying the current database contents. A zero
+ * database identifier means the values are not persistent.
+ */
+int phonebook_get_metadata(struct phonebook_metadata *metadata);
+
/*
* phonebook_pull should be used only to prepare pull request - prepared
* request data is returned by this function. Start of fetching data from
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 13/18] obexd/pbap: Include database identifier in responses
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (11 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 12/18] obexd/pbap: Add persistent database identifier Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 14/18] obexd/pbap: Add folder version counters Jinwang Li
` (4 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Add the Database Identifier parameter to the phonebook, vCard listing
and vCard entry responses when the feature is negotiated, and enable the
feature in PSE_SUPPORTED_FEATURES.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 40 +++++++++++++++++++++++++++++++++++++---
1 file changed, 37 insertions(+), 3 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index 92794b968..f271547a3 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -51,13 +51,16 @@
#define FORMAT_TAG 0x07
#define PHONEBOOKSIZE_TAG 0x08
#define NEWMISSEDCALLS_TAG 0x09
+#define DATABASEID_TAG 0x0D
#define SUPPORTED_FEATURES_TAG 0x10
#define DOWNLOAD_FEATURE 0x00000001
#define BROWSE_FEATURE 0x00000002
+#define DATABASEID_FEATURE 0x00000004
#define PSE_SUPPORTED_FEATURES \
- (DOWNLOAD_FEATURE | BROWSE_FEATURE)
+ (DOWNLOAD_FEATURE | BROWSE_FEATURE | \
+ DATABASEID_FEATURE)
struct cache {
gboolean valid;
@@ -167,6 +170,25 @@ static void cache_clear(struct cache *cache)
cache->entries = NULL;
}
+/* Adds the metadata application parameters to the response. */
+static void response_add_metadata(struct pbap_session *pbap)
+{
+ struct phonebook_metadata metadata;
+
+ if (!(pbap->negotiated_features & DATABASEID_FEATURE))
+ return;
+
+ memset(&metadata, 0, sizeof(metadata));
+
+ if (!phonebook_get_metadata(&metadata))
+ DBG("Metadata unavailable");
+
+ pbap->obj->apparam = g_obex_apparam_set_bytes(pbap->obj->apparam,
+ DATABASEID_TAG,
+ metadata.database_identifier,
+ sizeof(metadata.database_identifier));
+}
+
static void response_add_missed_calls(struct pbap_session *pbap, uint8_t missed)
{
const char *folder = pbap->obj->folder;
@@ -208,6 +230,7 @@ static void phonebook_size_result(const char *buffer, size_t bufsize,
pbap->obj->firstpacket = TRUE;
response_add_missed_calls(pbap, missed);
+ response_add_metadata(pbap);
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
@@ -256,6 +279,7 @@ static void query_result(const char *buffer, size_t bufsize, int vcards,
/* Application parameters go in the first response packet only */
if (first) {
response_add_missed_calls(pbap, missed);
+ response_add_metadata(pbap);
if (pbap->obj->apparam)
pbap->obj->firstpacket = TRUE;
@@ -274,6 +298,14 @@ static void vcard_result(const char *buffer, size_t bufsize, int vcards,
if (first < 0)
return;
+ /* Application parameters go in the first response packet only */
+ if (first) {
+ response_add_metadata(pbap);
+
+ if (pbap->obj->apparam)
+ pbap->obj->firstpacket = TRUE;
+ }
+
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
@@ -396,6 +428,7 @@ static int generate_response(struct pbap_session *pbap, uint8_t missed)
DBG("");
response_add_missed_calls(pbap, missed);
+ response_add_metadata(pbap);
if (max == 0) {
/* Ignore all other parameter and return PhoneBookSize */
@@ -902,7 +935,7 @@ fail:
return NULL;
}
-static ssize_t vobject_pull_get_next_header(void *object, void *buf, size_t mtu,
+static ssize_t vobject_get_next_header(void *object, void *buf, size_t mtu,
uint8_t *hi)
{
struct pbap_object *obj = object;
@@ -1006,7 +1039,7 @@ static const struct obex_mime_type_driver mime_pull = {
.open = vobject_pull_open,
.close = vobject_close,
.read = vobject_pull_read,
- .get_next_header = vobject_pull_get_next_header,
+ .get_next_header = vobject_get_next_header,
};
static const struct obex_mime_type_driver mime_list = {
@@ -1026,6 +1059,7 @@ static const struct obex_mime_type_driver mime_vcard = {
.open = vobject_vcard_open,
.close = vobject_close,
.read = vobject_vcard_read,
+ .get_next_header = vobject_get_next_header,
};
static int pbap_init(void)
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 14/18] obexd/pbap: Add folder version counters
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (12 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 13/18] obexd/pbap: Include database identifier in responses Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 15/18] obexd/pbap: Include folder version counters in responses Jinwang Li
` (3 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Add primary and secondary folder version counters to the dummy backend.
The counters are incremented when the content of a folder changes, and a
new Database Identifier is generated when they restart. The ebook and
tracker backends keep no counters and report a zero Database Identifier.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/phonebook-dummy.c | 248 +++++++++++++++++++++++++++++
obexd/plugins/phonebook-metadata.c | 54 +++++++
obexd/plugins/phonebook-metadata.h | 8 +-
obexd/plugins/phonebook.h | 2 +
4 files changed, 311 insertions(+), 1 deletion(-)
diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index 698510f6f..ca731c092 100644
--- a/obexd/plugins/phonebook-dummy.c
+++ b/obexd/plugins/phonebook-dummy.c
@@ -53,6 +53,12 @@ struct cache_query {
uint8_t newmissedcalls;
};
+struct digest_data {
+ GChecksum *primary;
+ GChecksum *secondary;
+ char *path;
+};
+
static char *root_folder = NULL;
static void dummy_free(void *user_data)
@@ -76,9 +82,54 @@ static void query_free(void *user_data)
g_free(query);
}
+static gboolean phonebook_compute_folder_digest(const char *folder,
+ uint8_t primary_digest[16],
+ uint8_t secondary_digest[16]);
+
+static const char * const version_folders[] = {
+ PB_CONTACTS_FOLDER,
+ PB_CALLS_COMBINED_FOLDER,
+ PB_CALLS_INCOMING_FOLDER,
+ PB_CALLS_MISSED_FOLDER,
+ PB_CALLS_OUTGOING_FOLDER,
+ PB_CALLS_SPEEDDIAL_FOLDER,
+ PB_CALLS_FAVORITE_FOLDER,
+ NULL,
+};
+
+static gboolean folder_has_version(const char *folder)
+{
+ unsigned int i;
+
+ for (i = 0; version_folders[i]; i++) {
+ if (g_str_equal(folder, version_folders[i]))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static void dummy_metadata_init(void)
{
+ static const uint8_t zeros[16] = {0};
+ uint8_t primary[16], secondary[16];
+ int i;
+
metadata_identifier_update();
+
+ for (i = 0; version_folders[i]; i++) {
+ const char *folder = version_folders[i];
+
+ if (phonebook_compute_folder_digest(folder, primary,
+ secondary)) {
+ metadata_set(folder, "primary_digest", primary);
+ metadata_set(folder, "secondary_digest", secondary);
+ }
+
+ metadata_set(folder, PRIMARY_COUNTER, zeros);
+ metadata_set(folder, SECONDARY_COUNTER, zeros);
+ }
+
metadata_save();
}
@@ -217,6 +268,194 @@ static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset,
return 0;
}
+static char *vobject_get_string(VObject *prop)
+{
+ switch (vObjectValueType(prop)) {
+ case VCVT_STRINGZ:
+ return g_strdup(vObjectStringZValue(prop));
+ case VCVT_USTRINGZ:
+ return fakeCString(vObjectUStringZValue(prop));
+ default:
+ return NULL;
+ }
+}
+
+static void digest_entry(const char *filename, VObject *v, void *user_data)
+{
+ struct digest_data *data = user_data;
+ VObjectIterator iterator, sub_iterator;
+ VObject *prop, *subprop;
+ char *entry;
+ char *contents;
+ char *str;
+ gsize len;
+
+ /* The primary digest covers the file name and the whole contents. */
+ g_checksum_update(data->primary, (const guchar *) filename,
+ strlen(filename));
+ g_checksum_update(data->primary, (const guchar *) "\0", 1);
+
+ entry = g_build_filename(data->path, filename, NULL);
+
+ if (g_file_get_contents(entry, &contents, &len, NULL)) {
+ g_checksum_update(data->primary,
+ (const guchar *) contents, len);
+ g_free(contents);
+ }
+
+ g_free(entry);
+
+ g_checksum_update(data->primary, (const guchar *) "\0", 1);
+
+ /*
+ * The secondary digest covers the file name and the properties
+ * tracked by the secondary counter.
+ */
+ g_checksum_update(data->secondary, (const guchar *) filename,
+ strlen(filename));
+ g_checksum_update(data->secondary, (const guchar *) "\0", 1);
+
+ initPropIterator(&iterator, v);
+
+ while (moreIteration(&iterator)) {
+ prop = nextVObject(&iterator);
+
+ if (g_ascii_strcasecmp(vObjectName(prop), VCNameProp) &&
+ g_ascii_strcasecmp(vObjectName(prop),
+ VCFullNameProp) &&
+ g_ascii_strcasecmp(vObjectName(prop),
+ VCTelephoneProp) &&
+ g_ascii_strcasecmp(vObjectName(prop),
+ VCEmailAddressProp) &&
+ g_ascii_strcasecmp(vObjectName(prop),
+ VCMailerProp) &&
+ g_ascii_strcasecmp(vObjectName(prop),
+ VCAdrProp) &&
+ g_ascii_strcasecmp(vObjectName(prop),
+ "X-BT-UCI"))
+ continue;
+
+ g_checksum_update(data->secondary,
+ (const guchar *) vObjectName(prop),
+ strlen(vObjectName(prop)));
+ g_checksum_update(data->secondary, (const guchar *) "\0", 1);
+
+ str = vobject_get_string(prop);
+ if (str) {
+ g_checksum_update(data->secondary,
+ (const guchar *) str, strlen(str));
+ g_free(str);
+ g_checksum_update(data->secondary,
+ (const guchar *) "\0", 1);
+ }
+
+ /* Property components and parameters are included. */
+ initPropIterator(&sub_iterator, prop);
+
+ while (moreIteration(&sub_iterator)) {
+ subprop = nextVObject(&sub_iterator);
+
+ g_checksum_update(data->secondary,
+ (const guchar *) vObjectName(subprop),
+ strlen(vObjectName(subprop)));
+ g_checksum_update(data->secondary,
+ (const guchar *) "\0", 1);
+
+ str = vobject_get_string(subprop);
+ if (str) {
+ g_checksum_update(data->secondary,
+ (const guchar *) str,
+ strlen(str));
+ g_free(str);
+ g_checksum_update(data->secondary,
+ (const guchar *) "\0", 1);
+ }
+ }
+ }
+}
+
+/* Computes the digests used to detect changes of a folder's contents. */
+static gboolean phonebook_compute_folder_digest(const char *folder,
+ uint8_t primary_digest[16],
+ uint8_t secondary_digest[16])
+{
+ struct digest_data digest;
+ DIR *dp;
+ char *path;
+ gsize len = g_checksum_type_get_length(G_CHECKSUM_MD5);
+
+ path = g_build_filename(root_folder, folder, NULL);
+
+ dp = opendir(path);
+ if (!dp) {
+ g_free(path);
+ return FALSE;
+ }
+
+ digest.primary = g_checksum_new(G_CHECKSUM_MD5);
+ digest.secondary = g_checksum_new(G_CHECKSUM_MD5);
+ digest.path = path;
+
+ /* Entries beyond 65535 are not covered by the digest */
+ foreach_vcard(dp, digest_entry, 0, 0xffff, &digest, NULL);
+
+ closedir(dp);
+ g_free(path);
+
+ g_checksum_get_digest(digest.primary, primary_digest, &len);
+ g_checksum_get_digest(digest.secondary, secondary_digest, &len);
+
+ g_checksum_free(digest.primary);
+ g_checksum_free(digest.secondary);
+
+ return TRUE;
+}
+
+static void dummy_update_folder_version(const char *folder)
+{
+ uint8_t primary[16], secondary[16];
+ uint8_t stored_primary[16], stored_secondary[16];
+ uint8_t counter[16];
+ gboolean primary_changed, secondary_changed;
+
+ /* Retries a previously failed save. */
+ metadata_save();
+
+ if (!folder_has_version(folder))
+ return;
+
+ if (!phonebook_compute_folder_digest(folder, primary, secondary))
+ return;
+
+ /* Missing counters restart with a new Database Identifier. */
+ if (!metadata_get(folder, PRIMARY_COUNTER, counter) ||
+ !metadata_get(folder, SECONDARY_COUNTER, counter)) {
+ dummy_metadata_init();
+ return;
+ }
+
+ /* A missing or unreadable digest counts as a change. */
+ primary_changed = !metadata_get(folder, "primary_digest",
+ stored_primary) ||
+ memcmp(primary, stored_primary, 16) != 0;
+ secondary_changed = !metadata_get(folder, "secondary_digest",
+ stored_secondary) ||
+ memcmp(secondary, stored_secondary, 16) != 0;
+
+ if (!primary_changed && !secondary_changed)
+ return;
+
+ if (!metadata_folder_version_counter_incr(folder, primary_changed,
+ secondary_changed)) {
+ dummy_metadata_init();
+ return;
+ }
+
+ metadata_set(folder, "primary_digest", primary);
+ metadata_set(folder, "secondary_digest", secondary);
+ metadata_save();
+}
+
struct concat_data {
GString *buffer;
const struct apparam_field *params;
@@ -578,6 +817,11 @@ void *phonebook_pull(const char *name, const struct apparam_field *params,
return NULL;
}
+ folder = g_strndup(name, strlen(name) - 4);
+
+ dummy_update_folder_version(folder);
+ g_free(folder);
+
folder = g_strndup(filename, strlen(filename) - 4);
g_free(filename);
if (!is_dir(folder)) {
@@ -624,6 +868,8 @@ void *phonebook_get_entry(const char *folder, const char *id,
char *filename;
int fd;
+ dummy_update_folder_version(folder);
+
filename = g_build_filename(root_folder, folder, id, NULL);
fd = open(filename, O_RDONLY);
@@ -660,6 +906,8 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
DIR *dp;
struct dummy_data *dummy;
+ dummy_update_folder_version(name);
+
foldername = g_build_filename(root_folder, name, NULL);
dp = opendir(foldername);
g_free(foldername);
diff --git a/obexd/plugins/phonebook-metadata.c b/obexd/plugins/phonebook-metadata.c
index 06cfa2a8a..dffdcda91 100644
--- a/obexd/plugins/phonebook-metadata.c
+++ b/obexd/plugins/phonebook-metadata.c
@@ -192,3 +192,57 @@ void metadata_file_close(void)
metadata_file = NULL;
}
}
+
+/* Increments a 128-bit big-endian counter. Returns FALSE on rollover. */
+static gboolean counter_incr(uint8_t counter[16])
+{
+ int i;
+
+ for (i = 15; i >= 0; i--) {
+ counter[i]++;
+
+ if (counter[i])
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * Increments the primary and secondary folder version counters.
+ * Returns FALSE on missing counters or rollover.
+ */
+gboolean metadata_folder_version_counter_incr(const char *folder,
+ gboolean primary_changed,
+ gboolean secondary_changed)
+{
+ uint8_t primary[16];
+ uint8_t secondary[16];
+
+ if (!metadata_file || !metadata_kf)
+ return FALSE;
+
+ if (primary_changed) {
+ if (!metadata_get(folder, PRIMARY_COUNTER, primary))
+ return FALSE;
+
+ if (!counter_incr(primary))
+ return FALSE;
+
+ metadata_set(folder, PRIMARY_COUNTER, primary);
+ }
+
+ if (secondary_changed && (g_str_equal(folder, PB_CONTACTS_FOLDER) ||
+ g_str_equal(folder, PB_CALLS_SPEEDDIAL_FOLDER) ||
+ g_str_equal(folder, PB_CALLS_FAVORITE_FOLDER))) {
+ if (!metadata_get(folder, SECONDARY_COUNTER, secondary))
+ return FALSE;
+
+ if (!counter_incr(secondary))
+ return FALSE;
+
+ metadata_set(folder, SECONDARY_COUNTER, secondary);
+ }
+
+ return TRUE;
+}
diff --git a/obexd/plugins/phonebook-metadata.h b/obexd/plugins/phonebook-metadata.h
index c70c20ecd..a2f921f13 100644
--- a/obexd/plugins/phonebook-metadata.h
+++ b/obexd/plugins/phonebook-metadata.h
@@ -40,6 +40,12 @@ gboolean metadata_get(const char *group, const char *key, uint8_t buf[16]);
void metadata_identifier_update(void);
/*
- * Persists the metadata file. Returns TRUE on success.
+ * Increments the primary and/or secondary folder version counters.
+ * Returns FALSE on missing counters or rollover.
*/
+gboolean metadata_folder_version_counter_incr(const char *folder,
+ gboolean primary_changed,
+ gboolean secondary_changed);
+
+/* Saves the metadata file when it has changes. Returns TRUE on success. */
gboolean metadata_save(void);
diff --git a/obexd/plugins/phonebook.h b/obexd/plugins/phonebook.h
index 6fb91f7e0..1bd6ff229 100644
--- a/obexd/plugins/phonebook.h
+++ b/obexd/plugins/phonebook.h
@@ -39,6 +39,8 @@
#define PB_INFO_LOG "/telecom/pb/info.log"
#define PB_CC_LOG "/telecom/pb/luid/cc.log"
+#define PRIMARY_COUNTER "primary_counter"
+#define SECONDARY_COUNTER "secondary_counter"
struct apparam_field {
/* list and pull attributes */
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 15/18] obexd/pbap: Include folder version counters in responses
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (13 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 14/18] obexd/pbap: Add folder version counters Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 16/18] obexd/pbap: Add vCardSelector support Jinwang Li
` (2 subsequent siblings)
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Add the primary and secondary folder version counters to the phonebook
and vCard listing responses when the feature is negotiated. The Database
Identifier is reported as zero when the folder has no counters.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 47 ++++++++++++++++++++++--------
obexd/plugins/phonebook-metadata.c | 15 ++++++++--
obexd/plugins/phonebook-metadata.h | 3 +-
obexd/plugins/phonebook.c | 5 ++--
obexd/plugins/phonebook.h | 5 +++-
5 files changed, 56 insertions(+), 19 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index f271547a3..c47be81ab 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -51,16 +51,19 @@
#define FORMAT_TAG 0x07
#define PHONEBOOKSIZE_TAG 0x08
#define NEWMISSEDCALLS_TAG 0x09
+#define PRIMARY_COUNTER_TAG 0x0A
+#define SECONDARY_COUNTER_TAG 0x0B
#define DATABASEID_TAG 0x0D
#define SUPPORTED_FEATURES_TAG 0x10
#define DOWNLOAD_FEATURE 0x00000001
#define BROWSE_FEATURE 0x00000002
#define DATABASEID_FEATURE 0x00000004
+#define FOLDER_VERSION_FEATURE 0x00000008
#define PSE_SUPPORTED_FEATURES \
(DOWNLOAD_FEATURE | BROWSE_FEATURE | \
- DATABASEID_FEATURE)
+ DATABASEID_FEATURE | FOLDER_VERSION_FEATURE)
struct cache {
gboolean valid;
@@ -171,22 +174,42 @@ static void cache_clear(struct cache *cache)
}
/* Adds the metadata application parameters to the response. */
-static void response_add_metadata(struct pbap_session *pbap)
+static void response_add_metadata(struct pbap_session *pbap,
+ gboolean include_counters)
{
struct phonebook_metadata metadata;
+ gboolean database_id, folder_version;
+ const char *folder = pbap->obj->folder;
- if (!(pbap->negotiated_features & DATABASEID_FEATURE))
- return;
+ database_id = pbap->negotiated_features & DATABASEID_FEATURE;
+ folder_version = include_counters &&
+ (pbap->negotiated_features & FOLDER_VERSION_FEATURE);
- memset(&metadata, 0, sizeof(metadata));
+ if (!database_id && !folder_version)
+ return;
- if (!phonebook_get_metadata(&metadata))
+ if (!phonebook_get_metadata(folder, &metadata))
DBG("Metadata unavailable");
- pbap->obj->apparam = g_obex_apparam_set_bytes(pbap->obj->apparam,
- DATABASEID_TAG,
+ if (database_id)
+ pbap->obj->apparam = g_obex_apparam_set_bytes(
+ pbap->obj->apparam, DATABASEID_TAG,
metadata.database_identifier,
sizeof(metadata.database_identifier));
+
+ if (folder_version) {
+ pbap->obj->apparam = g_obex_apparam_set_bytes(
+ pbap->obj->apparam,
+ PRIMARY_COUNTER_TAG,
+ metadata.primary_counter,
+ sizeof(metadata.primary_counter));
+
+ pbap->obj->apparam = g_obex_apparam_set_bytes(
+ pbap->obj->apparam,
+ SECONDARY_COUNTER_TAG,
+ metadata.secondary_counter,
+ sizeof(metadata.secondary_counter));
+ }
}
static void response_add_missed_calls(struct pbap_session *pbap, uint8_t missed)
@@ -230,7 +253,7 @@ static void phonebook_size_result(const char *buffer, size_t bufsize,
pbap->obj->firstpacket = TRUE;
response_add_missed_calls(pbap, missed);
- response_add_metadata(pbap);
+ response_add_metadata(pbap, TRUE);
obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
}
@@ -279,7 +302,7 @@ static void query_result(const char *buffer, size_t bufsize, int vcards,
/* Application parameters go in the first response packet only */
if (first) {
response_add_missed_calls(pbap, missed);
- response_add_metadata(pbap);
+ response_add_metadata(pbap, TRUE);
if (pbap->obj->apparam)
pbap->obj->firstpacket = TRUE;
@@ -300,7 +323,7 @@ static void vcard_result(const char *buffer, size_t bufsize, int vcards,
/* Application parameters go in the first response packet only */
if (first) {
- response_add_metadata(pbap);
+ response_add_metadata(pbap, FALSE);
if (pbap->obj->apparam)
pbap->obj->firstpacket = TRUE;
@@ -428,7 +451,7 @@ static int generate_response(struct pbap_session *pbap, uint8_t missed)
DBG("");
response_add_missed_calls(pbap, missed);
- response_add_metadata(pbap);
+ response_add_metadata(pbap, TRUE);
if (max == 0) {
/* Ignore all other parameter and return PhoneBookSize */
diff --git a/obexd/plugins/phonebook-metadata.c b/obexd/plugins/phonebook-metadata.c
index dffdcda91..5a1acf7af 100644
--- a/obexd/plugins/phonebook-metadata.c
+++ b/obexd/plugins/phonebook-metadata.c
@@ -163,7 +163,7 @@ gboolean metadata_file_open(const char *file)
return TRUE;
}
-gboolean metadata_read(struct phonebook_metadata *metadata)
+gboolean metadata_read(const char *folder, struct phonebook_metadata *metadata)
{
memset(metadata, 0, sizeof(*metadata));
@@ -174,8 +174,17 @@ gboolean metadata_read(struct phonebook_metadata *metadata)
if (metadata_dirty)
return FALSE;
- if (!metadata_get("identifier", "value", metadata->database_identifier))
- DBG("identifier not found in metadata");
+ /* Report incomplete metadata as zero values. */
+ if (!metadata_get("identifier", "value",
+ metadata->database_identifier) ||
+ !metadata_get(folder, PRIMARY_COUNTER,
+ metadata->primary_counter) ||
+ !metadata_get(folder, SECONDARY_COUNTER,
+ metadata->secondary_counter)) {
+ DBG("Metadata of %s is incomplete", folder);
+ memset(metadata, 0, sizeof(*metadata));
+ return FALSE;
+ }
return TRUE;
}
diff --git a/obexd/plugins/phonebook-metadata.h b/obexd/plugins/phonebook-metadata.h
index a2f921f13..cc87c7ce4 100644
--- a/obexd/plugins/phonebook-metadata.h
+++ b/obexd/plugins/phonebook-metadata.h
@@ -24,7 +24,8 @@ gboolean metadata_file_open(const char *file);
void metadata_file_close(void);
/* Reads metadata and zeroes invalid or unsaved values. */
-gboolean metadata_read(struct phonebook_metadata *metadata);
+gboolean metadata_read(const char *folder,
+ struct phonebook_metadata *metadata);
/* Stores a hex encoded byte array. */
void metadata_set(const char *group, const char *key,
diff --git a/obexd/plugins/phonebook.c b/obexd/plugins/phonebook.c
index 059414446..45e383d92 100644
--- a/obexd/plugins/phonebook.c
+++ b/obexd/plugins/phonebook.c
@@ -48,7 +48,8 @@ void phonebook_exit(void)
metadata_file_close();
}
-int phonebook_get_metadata(struct phonebook_metadata *metadata)
+int phonebook_get_metadata(const char *folder,
+ struct phonebook_metadata *metadata)
{
- return metadata_read(metadata);
+ return metadata_read(folder, metadata);
}
diff --git a/obexd/plugins/phonebook.h b/obexd/plugins/phonebook.h
index 1bd6ff229..1b5ced180 100644
--- a/obexd/plugins/phonebook.h
+++ b/obexd/plugins/phonebook.h
@@ -60,6 +60,8 @@ struct apparam_field {
/* Metadata identifying the current database contents. */
struct phonebook_metadata {
uint8_t database_identifier[16];
+ uint8_t primary_counter[16];
+ uint8_t secondary_counter[16];
};
/*
@@ -110,7 +112,8 @@ char *phonebook_set_folder(const char *current_folder,
* Returns the metadata identifying the current database contents. A zero
* database identifier means the values are not persistent.
*/
-int phonebook_get_metadata(struct phonebook_metadata *metadata);
+int phonebook_get_metadata(const char *folder,
+ struct phonebook_metadata *metadata);
/*
* phonebook_pull should be used only to prepare pull request - prepared
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 16/18] obexd/pbap: Add vCardSelector support
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (14 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 15/18] obexd/pbap: Include folder version counters in responses Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 17/18] obexd/pbap: Advertise Contact Image Default Format Jinwang Li
2026-11-04 8:34 ` [PATCH v1 18/18] obexd/pbap: Update SDP and PBAP version Jinwang Li
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Apply vCardSelector and vCardSelectorOperator to phonebook and vCard
listing requests when vCard Selecting is negotiated. Count and paginate
matching entries.
Add property presence masks to the dummy, ebook and tracker backends.
Use the full name, nickname or telephone number when structured names
are empty.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 50 +++++-
obexd/plugins/phonebook-dummy.c | 168 +++++++++++++++---
obexd/plugins/phonebook-ebook.c | 84 +++++++--
obexd/plugins/phonebook-tracker.c | 284 +++++++++++++++++-------------
obexd/plugins/phonebook.h | 65 ++++++-
obexd/plugins/vcard.c | 69 ++++++++
obexd/plugins/vcard.h | 2 +
7 files changed, 555 insertions(+), 167 deletions(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index c47be81ab..dba05b038 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -53,17 +53,21 @@
#define NEWMISSEDCALLS_TAG 0x09
#define PRIMARY_COUNTER_TAG 0x0A
#define SECONDARY_COUNTER_TAG 0x0B
+#define VCARD_SELECTOR_TAG 0x0C
#define DATABASEID_TAG 0x0D
+#define VCARD_SELECTOR_OPERATOR_TAG 0x0E
#define SUPPORTED_FEATURES_TAG 0x10
#define DOWNLOAD_FEATURE 0x00000001
#define BROWSE_FEATURE 0x00000002
#define DATABASEID_FEATURE 0x00000004
#define FOLDER_VERSION_FEATURE 0x00000008
+#define VCARD_SELECTING_FEATURE 0x00000010
#define PSE_SUPPORTED_FEATURES \
(DOWNLOAD_FEATURE | BROWSE_FEATURE | \
- DATABASEID_FEATURE | FOLDER_VERSION_FEATURE)
+ DATABASEID_FEATURE | FOLDER_VERSION_FEATURE | \
+ VCARD_SELECTING_FEATURE)
struct cache {
gboolean valid;
@@ -77,6 +81,7 @@ struct cache_entry {
char *name;
char *sound;
char *tel;
+ uint64_t mask;
};
struct pbap_session {
@@ -334,7 +339,8 @@ static void vcard_result(const char *buffer, size_t bufsize, int vcards,
static void cache_entry_notify(const char *id, uint32_t handle,
const char *name, const char *sound,
- const char *tel, void *user_data)
+ const char *tel, uint64_t mask,
+ void *user_data)
{
struct pbap_session *pbap = user_data;
struct cache_entry *entry = g_new0(struct cache_entry, 1);
@@ -349,6 +355,7 @@ static void cache_entry_notify(const char *id, uint32_t handle,
entry->name = g_strdup(name);
entry->sound = g_strdup(sound);
entry->tel = g_strdup(tel);
+ entry->mask = mask;
cache->entries = g_slist_append(cache->entries, entry);
}
@@ -444,6 +451,7 @@ static GSList *sort_entries(GSList *l, uint8_t order, uint8_t search_attrib,
static int generate_response(struct pbap_session *pbap, uint8_t missed)
{
+ GSList *filtered = NULL;
GSList *sorted;
GSList *l;
uint16_t max = pbap->params->maxlistcount;
@@ -453,9 +461,21 @@ static int generate_response(struct pbap_session *pbap, uint8_t missed)
response_add_missed_calls(pbap, missed);
response_add_metadata(pbap, TRUE);
+ /* Select matching entries from the full cache. */
+ for (l = pbap->cache.entries; l; l = l->next) {
+ struct cache_entry *entry = l->data;
+
+ if (phonebook_selector_match(entry->mask,
+ pbap->params->selector,
+ pbap->params->selector_operator))
+ filtered = g_slist_prepend(filtered, entry);
+ }
+
+ filtered = g_slist_reverse(filtered);
+
if (max == 0) {
- /* Ignore all other parameter and return PhoneBookSize */
- uint16_t size = g_slist_length(pbap->cache.entries);
+ /* Return the number of matching entries. */
+ uint16_t size = g_slist_length(filtered);
pbap->obj->firstpacket = TRUE;
pbap->obj->apparam = g_obex_apparam_set_uint16(
@@ -463,6 +483,8 @@ static int generate_response(struct pbap_session *pbap, uint8_t missed)
PHONEBOOKSIZE_TAG,
size);
+ g_slist_free(filtered);
+
return 0;
}
@@ -470,9 +492,10 @@ static int generate_response(struct pbap_session *pbap, uint8_t missed)
* Don't free the sorted list content: this list contains
* only the reference for the "real" cache entry.
*/
- sorted = sort_entries(pbap->cache.entries, pbap->params->order,
+ sorted = sort_entries(filtered, pbap->params->order,
pbap->params->searchattrib,
(const char *) pbap->params->searchval);
+ g_slist_free(filtered);
/* Computing offset considering first entry of the phonebook */
l = g_slist_nth(sorted, pbap->params->liststartoffset);
@@ -560,16 +583,23 @@ static struct apparam_field *parse_aparam(const uint8_t *buffer, uint32_t hlen)
g_obex_apparam_get_uint16(apparam, LISTSTARTOFFSET_TAG,
¶m->liststartoffset);
g_obex_apparam_get_uint64(apparam, FILTER_TAG, ¶m->filter);
+ g_obex_apparam_get_uint64(apparam, VCARD_SELECTOR_TAG,
+ ¶m->selector);
+ g_obex_apparam_get_uint8(apparam,
+ VCARD_SELECTOR_OPERATOR_TAG,
+ ¶m->selector_operator);
param->searchval = g_obex_apparam_get_string(apparam,
SEARCHVALUE_TAG);
g_obex_apparam_free(apparam);
}
- DBG("o %x sa %x sv %s fil %" G_GINT64_MODIFIER "x for %x max %x off %x",
+ DBG("o %x sa %x sv %s fil %" G_GINT64_MODIFIER "x for %x max %x off %x "
+ "sel %" G_GINT64_MODIFIER "x op %x",
param->order, param->searchattrib, param->searchval,
param->filter, param->format, param->maxlistcount,
- param->liststartoffset);
+ param->liststartoffset, param->selector,
+ param->selector_operator);
return param;
}
@@ -654,6 +684,12 @@ static int pbap_get(struct obex_session *os, void *user_data)
if (params == NULL)
return -EBADR;
+ /* Ignore selectors unless vCard Selecting is negotiated. */
+ if (!(pbap->negotiated_features & VCARD_SELECTING_FEATURE)) {
+ params->selector = 0;
+ params->selector_operator = 0;
+ }
+
if (pbap->params) {
g_free(pbap->params->searchval);
g_free(pbap->params);
diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index ca731c092..4b71d0d7d 100644
--- a/obexd/plugins/phonebook-dummy.c
+++ b/obexd/plugins/phonebook-dummy.c
@@ -61,6 +61,109 @@ struct digest_data {
static char *root_folder = NULL;
+static gboolean vobject_value_present(VObject *prop)
+{
+ const char *str;
+ const wchar_t *ustr;
+ VObject *size;
+
+ switch (vObjectValueType(prop)) {
+ case VCVT_STRINGZ:
+ str = vObjectStringZValue(prop);
+ return str && str[0];
+ case VCVT_USTRINGZ:
+ ustr = vObjectUStringZValue(prop);
+ return ustr && uStrLen(ustr) > 0;
+ case VCVT_UINT:
+ case VCVT_ULONG:
+ return TRUE;
+ case VCVT_RAW:
+ if (!vObjectAnyValue(prop))
+ return FALSE;
+
+ size = isAPropertyOf(prop, VCDataSizeProp);
+ if (!size)
+ return FALSE;
+
+ switch (vObjectValueType(size)) {
+ case VCVT_UINT:
+ return vObjectIntegerValue(size) > 0;
+ case VCVT_ULONG:
+ return vObjectLongValue(size) > 0;
+ default:
+ return FALSE;
+ }
+ case VCVT_VOBJECT:
+ return vObjectVObjectValue(prop) != NULL;
+ default:
+ return FALSE;
+ }
+}
+
+static gboolean vcard_property_present(VObject *v, const char *name)
+{
+ static const char * const name_fields[] = {
+ VCFamilyNameProp, VCGivenNameProp, VCAdditionalNamesProp,
+ VCNamePrefixesProp, VCNameSuffixesProp, NULL
+ };
+ static const char * const address_fields[] = {
+ VCPostalBoxProp, VCExtAddressProp, VCStreetAddressProp,
+ VCCityProp, VCRegionProp, VCPostalCodeProp, VCCountryNameProp,
+ NULL
+ };
+ static const char * const org_fields[] = {
+ VCOrgNameProp, VCOrgUnitProp, VCOrgUnit2Prop, VCOrgUnit3Prop,
+ VCOrgUnit4Prop, NULL
+ };
+ const char * const *fields = NULL;
+ VObjectIterator iterator;
+ VObject *prop;
+ unsigned int i;
+
+ if (!g_ascii_strcasecmp(name, VCNameProp))
+ fields = name_fields;
+ else if (!g_ascii_strcasecmp(name, VCAdrProp))
+ fields = address_fields;
+ else if (!g_ascii_strcasecmp(name, VCOrgProp))
+ fields = org_fields;
+
+ initPropIterator(&iterator, v);
+
+ while (moreIteration(&iterator)) {
+ prop = nextVObject(&iterator);
+
+ if (g_ascii_strcasecmp(vObjectName(prop), name))
+ continue;
+
+ if (vobject_value_present(prop))
+ return TRUE;
+
+ for (i = 0; fields && fields[i]; i++) {
+ VObject *field = isAPropertyOf(prop, fields[i]);
+
+ if (field && vobject_value_present(field))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static uint64_t vcard_presence_mask(VObject *v)
+{
+ uint64_t mask = 0;
+ unsigned int i;
+
+ for (i = 0; i < G_N_ELEMENTS(phonebook_selector_properties); i++) {
+ const char *property = phonebook_selector_properties[i];
+
+ if (property && vcard_property_present(v, property))
+ mask |= (1ULL << i);
+ }
+
+ return mask;
+}
+
static void dummy_free(void *user_data)
{
struct dummy_data *dummy = user_data;
@@ -459,6 +562,9 @@ static void dummy_update_folder_version(const char *folder)
struct concat_data {
GString *buffer;
const struct apparam_field *params;
+ uint16_t offset;
+ uint16_t max;
+ uint16_t count;
};
static char *filter_vcard_photo(const char *buffer, size_t length,
@@ -516,6 +622,23 @@ static void entry_concat(const char *filename, VObject *v, void *user_data)
size_t filtered_len;
int len;
+ if (!phonebook_selector_match(vcard_presence_mask(v),
+ data->params->selector,
+ data->params->selector_operator))
+ return;
+
+ data->count++;
+
+ /*
+ * Offset and MaxListCount apply to the vCards matching the
+ * vCardSelector.
+ */
+ if (data->count <= data->offset)
+ return;
+
+ if (data->count - data->offset > data->max)
+ return;
+
/*
* VObject API uses len for IN and OUT
* Written bytes is also returned in the len variable
@@ -534,41 +657,43 @@ static void entry_concat(const char *filename, VObject *v, void *user_data)
static gboolean read_dir(void *user_data)
{
struct dummy_data *dummy = user_data;
- struct concat_data data;
+ struct concat_data data = { 0 };
DIR *dp;
- uint16_t count = 0, max, offset;
data.buffer = g_string_new("");
data.params = dummy->apparams;
- dp = opendir(dummy->folder);
- if (dp == NULL) {
- int err = errno;
- DBG("opendir(): %s(%d)", strerror(err), err);
- goto done;
- }
-
/*
* For PullPhoneBook function, the decision of returning the size
* or contacts is made in the PBAP core. When MaxListCount is ZERO,
* PCE wants to know the size of a given folder, PSE shall ignore all
- * other applicattion parameters that may be present in the request.
+ * other application parameters that may be present in the request,
+ * except the vCardSelector and vCardSelectorOperator, which still
+ * apply to the returned size.
*/
- if (dummy->apparams->maxlistcount == 0) {
- max = 0xffff;
- offset = 0;
- } else {
- max = dummy->apparams->maxlistcount;
- offset = dummy->apparams->liststartoffset;
+ if (dummy->apparams->maxlistcount == 0)
+ data.max = 0xffff;
+ else {
+ data.max = dummy->apparams->maxlistcount;
+ data.offset = dummy->apparams->liststartoffset;
+ }
+
+ dp = opendir(dummy->folder);
+ if (dp == NULL) {
+ int err = errno;
+
+ DBG("opendir(): %s(%d)", strerror(err), err);
+
+ goto done;
}
- foreach_vcard(dp, entry_concat, offset, max, &data, &count);
+ foreach_vcard(dp, entry_concat, 0, 0xffff, &data, NULL);
closedir(dp);
done:
/* FIXME: Missing vCards fields filtering */
- dummy->cb(data.buffer->str, data.buffer->len, count,
- dummy->newmissedcalls, TRUE, dummy->user_data);
+ dummy->cb(data.buffer->str, data.buffer->len, data.count,
+ dummy->newmissedcalls, TRUE, dummy->user_data);
g_string_free(data.buffer, TRUE);
@@ -581,6 +706,7 @@ static void entry_notify(const char *filename, VObject *v, void *user_data)
VObject *property, *subproperty;
GString *name;
const char *tel;
+ uint64_t mask;
long unsigned int handle;
property = isAPropertyOf(v, VCNameProp);
@@ -593,6 +719,8 @@ static void entry_notify(const char *filename, VObject *v, void *user_data)
if (handle > UINT32_MAX)
return;
+ mask = vcard_presence_mask(v);
+
/* LastName; FirstName; MiddleName; Prefix; Suffix */
name = g_string_new("");
@@ -627,7 +755,7 @@ static void entry_notify(const char *filename, VObject *v, void *user_data)
tel = property ? fakeCString(vObjectUStringZValue(property)) : NULL;
- query->entry_cb(filename, handle, name->str, NULL, tel,
+ query->entry_cb(filename, handle, name->str, NULL, tel, mask,
query->user_data);
g_string_free(name, TRUE);
}
diff --git a/obexd/plugins/phonebook-ebook.c b/obexd/plugins/phonebook-ebook.c
index 0df8f2d0e..30a3976ad 100644
--- a/obexd/plugins/phonebook-ebook.c
+++ b/obexd/plugins/phonebook-ebook.c
@@ -101,6 +101,42 @@ static void free_query_context(struct query_context *data)
g_free(data);
}
+static uint64_t evcard_presence_mask(EVCard *evcard)
+{
+ GList *l;
+ /* VERSION is generated when serializing the vCard. */
+ uint64_t mask = 1ULL << 0;
+
+ for (l = e_vcard_get_attributes(evcard); l; l = l->next) {
+ EVCardAttribute *attrib = l->data;
+ const char *name = e_vcard_attribute_get_name(attrib);
+ GList *values;
+ unsigned int i;
+
+ for (values = e_vcard_attribute_get_values(attrib); values;
+ values = values->next) {
+ const char *value = values->data;
+
+ if (value && value[0])
+ break;
+ }
+
+ if (!values)
+ continue;
+
+ for (i = 0; i < G_N_ELEMENTS(phonebook_selector_properties);
+ i++) {
+ if (!g_ascii_strcasecmp(name,
+ phonebook_selector_properties[i])) {
+ mask |= 1ULL << i;
+ break;
+ }
+ }
+ }
+
+ return mask;
+}
+
static char *evcard_to_string(EVCard *evcard, unsigned int format,
uint64_t filter)
{
@@ -275,7 +311,7 @@ static void phonebook_pull_read_ready(GObject *source_object,
GSList *l = NULL;
GSList *contacts = NULL;
GError *gerr = NULL;
- unsigned int count, maxcount;
+ unsigned int count, maxcount, offset;
/* Finish async call to retrieve contacts */
data->queued_calls--;
@@ -292,28 +328,35 @@ static void phonebook_pull_read_ready(GObject *source_object,
goto done;
}
- /*
- * When MaxListCount is zero, PCE wants to know the number of used
- * indexes in the phonebook of interest. All other parameters that
- * may be present in the request shall be ignored.
- */
maxcount = data->params->maxlistcount;
- if (maxcount == 0) {
- data->count += g_slist_length(contacts);
- goto done;
- }
+ offset = data->params->liststartoffset;
+ count = 0;
- /*
- * Convert each contact to a vCard and append the card to
- * the buffer string.
- */
- l = g_slist_nth(contacts, data->params->liststartoffset);
- for (count = 0; l && count + data->count < maxcount;
- l = g_slist_next(l), count++) {
+ for (l = contacts; l; l = l->next) {
EContact *contact = E_CONTACT(l->data);
EVCard *evcard = E_VCARD(contact);
char *vcard;
+ if (data->params->selector &&
+ !phonebook_selector_match(
+ evcard_presence_mask(evcard),
+ data->params->selector,
+ data->params->selector_operator))
+ continue;
+
+ if (maxcount == 0) {
+ count++;
+ continue;
+ }
+
+ if (offset) {
+ offset--;
+ continue;
+ }
+
+ if (count + data->count >= maxcount)
+ break;
+
if (data->params->format == PB_FORMAT_VCARD30)
vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30,
data->params->filter);
@@ -326,6 +369,7 @@ static void phonebook_pull_read_ready(GObject *source_object,
data->buf = g_string_append(data->buf, vcard);
data->buf = g_string_append(data->buf, "\r\n");
g_free(vcard);
+ count++;
}
DBG("collected %d contacts", count);
@@ -527,7 +571,8 @@ static void phonebook_create_cache_ready(GObject *source_object,
tel = g_strdup("");
data->entry_cb(uid, PHONEBOOK_INVALID_HANDLE, name, NULL,
- tel, data->user_data);
+ tel, evcard_presence_mask(evcard),
+ data->user_data);
g_free(name);
g_free(uid);
@@ -605,7 +650,8 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
else
tel = g_strdup("");
- data->entry_cb(uid, 0, cname, NULL, tel, data->user_data);
+ data->entry_cb(uid, 0, cname, NULL, tel, evcard_presence_mask(evcard),
+ data->user_data);
data->count++;
g_free(cname);
diff --git a/obexd/plugins/phonebook-tracker.c b/obexd/plugins/phonebook-tracker.c
index 07d8dd34b..9db62ab8e 100644
--- a/obexd/plugins/phonebook-tracker.c
+++ b/obexd/plugins/phonebook-tracker.c
@@ -144,32 +144,6 @@
"}" \
"ORDER BY tracker:id(?_contact)"
-#define CONTACTS_QUERY_ALL_LIST \
- "SELECT ?c nco:nameFamily(?c) " \
- "nco:nameGiven(?c) nco:nameAdditional(?c) " \
- "nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) " \
- "(SELECT " \
- "?nick " \
- "WHERE { " \
- "{ " \
- "?c nco:nickname ?nick " \
- "} UNION { " \
- "?c nco:hasAffiliation ?role . " \
- "?role nco:hasIMAddress ?im . " \
- "?im nco:imNickname ?nick " \
- "} " \
- "} " \
- ") " \
- "nco:phoneNumber(?h) " \
- "WHERE { " \
- "?c a nco:PersonContact . " \
- "OPTIONAL { ?c nco:hasPhoneNumber ?h . } " \
- "OPTIONAL { " \
- "?c nco:hasAffiliation ?a . " \
- "?a nco:hasPhoneNumber ?h . " \
- "} " \
- "} GROUP BY ?c"
-
#define CALLS_CONSTRAINTS(CONSTRAINT) \
" WHERE { " \
"?_call a nmo:Call . " \
@@ -194,27 +168,6 @@ CONSTRAINT \
"} " \
"} "
-#define CALLS_LIST(CONSTRAINT) \
-"SELECT ?_call nco:nameFamily(?_contact) " \
- "nco:nameGiven(?_contact) nco:nameAdditional(?_contact) " \
- "nco:nameHonorificPrefix(?_contact) " \
- "nco:nameHonorificSuffix(?_contact) " \
- "(SELECT " \
- "?nick " \
- "WHERE { " \
- "{ " \
- "?_contact nco:nickname ?nick " \
- "} UNION { " \
- "?_contact nco:hasAffiliation ?role . " \
- "?role nco:hasIMAddress ?im . " \
- "?im nco:imNickname ?nick " \
- "} " \
- "} " \
- ") " \
- "nco:phoneNumber(?_cpn) " \
-CALLS_CONSTRAINTS(CONSTRAINT) \
-"ORDER BY DESC(nmo:sentDate(?_call)) "
-
#define CALLS_QUERY(CONSTRAINT) \
"SELECT " \
"(SELECT fn:concat(rdf:type(?role_number)," \
@@ -317,13 +270,9 @@ COMBINED_CONSTRAINT \
"FILTER (?_call = <%s>) "
#define MISSED_CALLS_QUERY CALLS_QUERY(MISSED_CONSTRAINT)
-#define MISSED_CALLS_LIST CALLS_LIST(MISSED_CONSTRAINT)
#define INCOMING_CALLS_QUERY CALLS_QUERY(INCOMING_CONSTRAINT)
-#define INCOMING_CALLS_LIST CALLS_LIST(INCOMING_CONSTRAINT)
#define OUTGOING_CALLS_QUERY CALLS_QUERY(OUTGOING_CONSTRAINT)
-#define OUTGOING_CALLS_LIST CALLS_LIST(OUTGOING_CONSTRAINT)
#define COMBINED_CALLS_QUERY CALLS_QUERY(COMBINED_CONSTRAINT)
-#define COMBINED_CALLS_LIST CALLS_LIST(COMBINED_CONSTRAINT)
#define CONTACT_FROM_CALL_QUERY CALLS_QUERY(CALL_URI_CONSTRAINT)
#define CONTACTS_QUERY_FROM_URI \
@@ -482,11 +431,13 @@ struct pending_reply {
enum phonebook_request {
PHONEBOOK_REQUEST_PULL,
+ PHONEBOOK_REQUEST_SELECT,
PHONEBOOK_REQUEST_ENTRY,
PHONEBOOK_REQUEST_CACHE,
};
struct contact_data {
+ gboolean name_present;
char *id;
struct phonebook_contact *contact;
};
@@ -498,6 +449,7 @@ struct phonebook_data {
enum phonebook_request request;
const struct apparam_field *params;
GSList *contacts;
+ GHashTable *contact_index;
phonebook_cache_ready_cb ready_cb;
phonebook_entry_cb entry_cb;
uint8_t newmissedcalls;
@@ -573,15 +525,15 @@ static gboolean folder_is_valid(const char *folder)
static const char *folder2query(const char *folder)
{
if (g_str_equal(folder, PB_CONTACTS_FOLDER))
- return CONTACTS_QUERY_ALL_LIST;
+ return CONTACTS_QUERY_ALL;
else if (g_str_equal(folder, PB_CALLS_INCOMING_FOLDER))
- return INCOMING_CALLS_LIST;
+ return INCOMING_CALLS_QUERY;
else if (g_str_equal(folder, PB_CALLS_OUTGOING_FOLDER))
- return OUTGOING_CALLS_LIST;
+ return OUTGOING_CALLS_QUERY;
else if (g_str_equal(folder, PB_CALLS_MISSED_FOLDER))
- return MISSED_CALLS_LIST;
+ return MISSED_CALLS_QUERY;
else if (g_str_equal(folder, PB_CALLS_COMBINED_FOLDER))
- return COMBINED_CALLS_LIST;
+ return COMBINED_CALLS_QUERY;
return NULL;
}
@@ -810,12 +762,22 @@ static gboolean contact_matches(struct contact_data *c_data, const char *id,
return (cmp_ret == 0) ? TRUE : FALSE;
}
-static struct phonebook_contact *find_contact(GSList *contacts, const char *id,
- const char *datetime)
+static struct phonebook_contact *find_contact(struct phonebook_data *data,
+ const char *id, const char *datetime)
{
GSList *l;
- for (l = contacts; l; l = l->next) {
+ if (data->contact_index) {
+ struct phonebook_contact *contact;
+ char *key = g_strconcat(id, "\n", datetime, NULL);
+
+ contact = g_hash_table_lookup(data->contact_index, key);
+ g_free(key);
+
+ return contact;
+ }
+
+ for (l = data->contacts; l; l = l->next) {
struct contact_data *c_data = l->data;
if (contact_matches(c_data, id, datetime))
@@ -1235,6 +1197,7 @@ static void free_data_contacts(struct phonebook_data *data)
g_slist_free(data->contacts);
data->contacts = NULL;
+ g_clear_pointer(&data->contact_index, g_hash_table_destroy);
}
static void send_pull_part(struct phonebook_data *data,
@@ -1252,6 +1215,87 @@ static void send_pull_part(struct phonebook_data *data,
g_string_free(vcards, TRUE);
}
+static void send_cache(struct phonebook_data *data)
+{
+ GSList *l;
+
+ for (l = data->contacts; l; l = l->next) {
+ struct contact_data *entry = l->data;
+ struct phonebook_contact *contact = entry->contact;
+ struct phonebook_field *number;
+ uint32_t handle;
+ char *name;
+ const char *tel;
+
+ number = contact->numbers ? contact->numbers->data : NULL;
+ tel = number ? number->text : "";
+
+ if (entry->name_present)
+ name = g_strdup_printf("%s;%s;%s;%s;%s",
+ contact->family ? contact->family : "",
+ contact->given ? contact->given : "",
+ contact->additional ? contact->additional : "",
+ contact->prefix ? contact->prefix : "",
+ contact->suffix ? contact->suffix : "");
+ else if (contact->fullname[0])
+ name = g_strdup(contact->fullname);
+ else if (contact->nickname[0])
+ name = g_strdup(contact->nickname);
+ else
+ name = g_strdup(tel);
+
+ handle = g_str_equal(entry->id, TRACKER_DEFAULT_CONTACT_ME) ?
+ 0 : PHONEBOOK_INVALID_HANDLE;
+ data->entry_cb(entry->id, handle, name, "", tel,
+ phonebook_contact_get_mask(contact),
+ data->user_data);
+ g_free(name);
+ }
+
+ data->ready_cb(data->newmissedcalls, data->user_data);
+}
+
+static void send_selected_contacts(struct phonebook_data *data)
+{
+ const struct apparam_field *params = data->params;
+ unsigned int count = 0;
+ unsigned int offset = params->liststartoffset;
+ GSList *l;
+ GString *vcards;
+
+ vcards = g_string_new(NULL);
+
+ for (l = data->contacts; l; l = l->next) {
+ struct contact_data *entry = l->data;
+ uint64_t mask = phonebook_contact_get_mask(entry->contact);
+
+ if (!phonebook_selector_match(mask, params->selector,
+ params->selector_operator))
+ continue;
+
+ if (params->maxlistcount == 0) {
+ count++;
+ continue;
+ }
+
+ if (offset) {
+ offset--;
+ continue;
+ }
+
+ if (count == params->maxlistcount)
+ break;
+
+ phonebook_add_contact(vcards, entry->contact, params->filter,
+ params->format);
+ count++;
+ }
+
+ data->cb(vcards->str, vcards->len, count, data->newmissedcalls,
+ TRUE, data->user_data);
+ g_string_free(vcards, TRUE);
+}
+
static int pull_contacts(const char **reply, int num_fields, void *user_data)
{
struct phonebook_data *data = user_data;
@@ -1262,7 +1306,12 @@ static int pull_contacts(const char **reply, int num_fields, void *user_data)
gboolean cdata_present = FALSE, part_sent = FALSE;
if (num_fields < 0) {
- data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
+ if (data->request == PHONEBOOK_REQUEST_CACHE)
+ data->ready_cb(0, data->user_data);
+ else
+ data->cb(NULL, 0, num_fields, 0, TRUE,
+ data->user_data);
+
goto fail;
}
@@ -1274,7 +1323,7 @@ static int pull_contacts(const char **reply, int num_fields, void *user_data)
/* Trying to find contact in recently added contacts. It is needed for
* contacts that have more than one telephone number filled */
- contact = find_contact(data->contacts, reply[CONTACTS_ID_COL],
+ contact = find_contact(data, reply[CONTACTS_ID_COL],
reply[COL_DATE]);
/* If contact is already created then adding only new phone numbers */
@@ -1297,6 +1346,11 @@ static int pull_contacts(const char **reply, int num_fields, void *user_data)
TRACKER_DEFAULT_CONTACT_ME))
return 0;
+ /* All rows of a contact are merged before the selection. */
+ if (data->request == PHONEBOOK_REQUEST_CACHE ||
+ data->request == PHONEBOOK_REQUEST_SELECT)
+ goto add_entry;
+
if (g_strcmp0(data->last_id, reply[CONTACTS_ID_COL])) {
data->index++;
g_free(data->last_id);
@@ -1354,7 +1408,25 @@ add_numbers:
contact_data = g_new0(struct contact_data, 1);
contact_data->contact = contact;
contact_data->id = g_strdup(reply[CONTACTS_ID_COL]);
- data->contacts = g_slist_append(data->contacts, contact_data);
+
+ for (i = COL_FAMILY_NAME; i <= COL_NAME_SUFFIX; i++) {
+ if (reply[i][0]) {
+ contact_data->name_present = TRUE;
+ break;
+ }
+ }
+
+ if (data->contact_index) {
+ char *key;
+
+ key = g_strconcat(reply[CONTACTS_ID_COL], "\n",
+ reply[COL_DATE], NULL);
+ g_hash_table_insert(data->contact_index, key, contact);
+ data->contacts = g_slist_prepend(data->contacts,
+ contact_data);
+ } else
+ data->contacts = g_slist_append(data->contacts,
+ contact_data);
}
if (part_sent)
@@ -1363,63 +1435,25 @@ add_numbers:
return 0;
done:
+ if (data->contact_index)
+ data->contacts = g_slist_reverse(data->contacts);
+
/* Processing is end, this is definitely last part of transmission
* (marking lastpart as TRUE) */
- send_pull_part(data, params, TRUE);
-
-fail:
- return -EINTR;
- /*
- * phonebook_data is freed in phonebook_req_finalize. Useful in
- * cases when call is terminated.
- */
-}
-
-static int add_to_cache(const char **reply, int num_fields, void *user_data)
-{
- struct phonebook_data *data = user_data;
- char *formatted;
- int i;
-
- if (reply == NULL || num_fields < 0)
- goto done;
-
- /* the first element is the URI, always not empty */
- for (i = 1; i < num_fields; i++) {
- if (reply[i][0] != '\0')
- break;
+ switch (data->request) {
+ case PHONEBOOK_REQUEST_CACHE:
+ send_cache(data);
+ break;
+ case PHONEBOOK_REQUEST_SELECT:
+ send_selected_contacts(data);
+ break;
+ case PHONEBOOK_REQUEST_PULL:
+ case PHONEBOOK_REQUEST_ENTRY:
+ send_pull_part(data, params, TRUE);
+ break;
}
- if (i == num_fields &&
- !g_str_equal(reply[0], TRACKER_DEFAULT_CONTACT_ME))
- return 0;
-
- if (i == 7)
- formatted = g_strdup(reply[7]);
- else if (i == 6)
- formatted = g_strdup(reply[6]);
- else
- formatted = g_strdup_printf("%s;%s;%s;%s;%s",
- reply[1], reply[2], reply[3], reply[4],
- reply[5]);
-
- /* The owner vCard must have the 0 handle */
- if (strcmp(reply[0], TRACKER_DEFAULT_CONTACT_ME) == 0)
- data->entry_cb(reply[0], 0, formatted, "",
- reply[6], data->user_data);
- else
- data->entry_cb(reply[0], PHONEBOOK_INVALID_HANDLE, formatted,
- "", reply[6], data->user_data);
-
- g_free(formatted);
-
- return 0;
-
-done:
- if (num_fields <= 0)
- data->ready_cb(num_fields < 0 ? 0 : data->newmissedcalls,
- data->user_data);
-
+fail:
return -EINTR;
/*
* phonebook_data is freed in phonebook_req_finalize. Useful in
@@ -1544,8 +1578,8 @@ static int pull_newmissedcalls(const char **reply, int num_fields,
if (data->request == PHONEBOOK_REQUEST_CACHE) {
query = folder2query(data->req_name);
- col_amount = 8;
- pull_cb = add_to_cache;
+ col_amount = PULL_QUERY_COL_AMOUNT;
+ pull_cb = pull_contacts;
} else if (data->request == PHONEBOOK_REQUEST_PULL &&
data->params->maxlistcount == 0) {
query = name2count_query(PB_CALLS_MISSED);
@@ -1599,12 +1633,18 @@ void *phonebook_pull(const char *name, const struct apparam_field *params,
DBG("name %s", name);
data = g_new0(struct phonebook_data, 1);
- data->request = PHONEBOOK_REQUEST_PULL;
data->params = params;
data->user_data = user_data;
data->cb = cb;
data->req_name = g_strdup(name);
+ if (params->selector) {
+ data->request = PHONEBOOK_REQUEST_SELECT;
+ data->contact_index = g_hash_table_new_full(g_str_hash,
+ g_str_equal, g_free, NULL);
+ } else
+ data->request = PHONEBOOK_REQUEST_PULL;
+
if (err)
*err = 0;
@@ -1633,7 +1673,8 @@ int phonebook_pull_read(void *request)
query = NEW_MISSED_CALLS_COUNT_QUERY;
col_amount = COUNT_QUERY_COL_AMOUNT;
pull_cb = pull_newmissedcalls;
- } else if (data->params->maxlistcount == 0) {
+ } else if (data->request == PHONEBOOK_REQUEST_PULL &&
+ data->params->maxlistcount == 0) {
query = name2count_query(data->req_name);
col_amount = COUNT_QUERY_COL_AMOUNT;
pull_cb = pull_contacts_size;
@@ -1715,6 +1756,8 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
data = g_new0(struct phonebook_data, 1);
data->request = PHONEBOOK_REQUEST_CACHE;
+ data->contact_index = g_hash_table_new_full(g_str_hash,
+ g_str_equal, g_free, NULL);
data->entry_cb = entry_cb;
data->ready_cb = ready_cb;
data->user_data = user_data;
@@ -1725,7 +1768,8 @@ void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
COUNT_QUERY_COL_AMOUNT,
pull_newmissedcalls, data);
else
- ret = query_tracker(query, 8, add_to_cache, data);
+ ret = query_tracker(query, PULL_QUERY_COL_AMOUNT,
+ pull_contacts, data);
if (err)
*err = ret;
diff --git a/obexd/plugins/phonebook.h b/obexd/plugins/phonebook.h
index 1b5ced180..df56a720d 100644
--- a/obexd/plugins/phonebook.h
+++ b/obexd/plugins/phonebook.h
@@ -55,6 +55,10 @@ struct apparam_field {
uint8_t order;
uint8_t searchattrib;
char *searchval;
+
+ /* vCard Selecting attributes */
+ uint64_t selector;
+ uint8_t selector_operator;
};
/* Metadata identifying the current database contents. */
@@ -75,15 +79,74 @@ typedef void (*phonebook_cb) (const char *buffer, size_t bufsize,
/*
* Interface between the PBAP core and backends to
* append a new entry in the PBAP folder cache.
+ *
+ * mask is a bitmask of the vCard properties holding a value for this
+ * entry, using the bit assignment of the vCardSelector.
*/
#define PHONEBOOK_INVALID_HANDLE 0xffffffff
typedef void (*phonebook_entry_cb) (const char *id, uint32_t handle,
const char *name, const char *sound,
- const char *tel, void *user_data);
+ const char *tel, uint64_t mask,
+ void *user_data);
/* Notifies cache completion and the number of undismissed missed calls. */
typedef void (*phonebook_cache_ready_cb) (uint8_t missed, void *user_data);
+/* vCardSelector property names, indexed by bit number. */
+static const char * const phonebook_selector_properties[] = {
+/* 0 */ "VERSION",
+ "FN",
+ "N",
+ "PHOTO",
+ "BDAY",
+ "ADR",
+ "LABEL",
+ "TEL",
+/* 8 */ "EMAIL",
+ "MAILER",
+ "TZ",
+ "GEO",
+ "TITLE",
+ "ROLE",
+ "LOGO",
+ "AGENT",
+/* 16 */ "ORG",
+ "NOTE",
+ "REV",
+ "SOUND",
+ "URL",
+ "UID",
+ "KEY",
+ "NICKNAME",
+/* 24 */ "CATEGORIES",
+ "PROID",
+ "CLASS",
+ "SORT-STRING",
+/* 28 */ "X-IRMC-CALL-DATETIME",
+ "X-BT-SPEEDDIALKEY",
+ "X-BT-UCI",
+ "X-BT-UID"
+};
+
+/*
+ * A zero selector matches all entries. Bit 0 of the operator selects
+ * AND (1) or OR (0).
+ */
+static inline gboolean phonebook_selector_match(uint64_t mask,
+ uint64_t selector, uint8_t selector_operator)
+{
+ /* Ignore reserved property bits. */
+ selector &= ~(0x7fULL << 32);
+
+ if (!selector)
+ return TRUE;
+
+ if (selector_operator & 0x01)
+ return (mask & selector) == selector;
+
+ return (mask & selector) != 0;
+}
+
/*
* Set up and tear down the phonebook back-end. The back-end is shared by
diff --git a/obexd/plugins/vcard.c b/obexd/plugins/vcard.c
index 0a727b729..f0b440804 100644
--- a/obexd/plugins/vcard.c
+++ b/obexd/plugins/vcard.c
@@ -387,6 +387,75 @@ static gboolean contact_fields_present(struct phonebook_contact * contact)
return FALSE;
}
+static gboolean field_values_present(GSList *fields)
+{
+ GSList *l;
+
+ for (l = fields; l; l = l->next) {
+ struct phonebook_field *field = l->data;
+
+ if (field->text && field->text[0])
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+uint64_t phonebook_contact_get_mask(struct phonebook_contact *contact)
+{
+ const char * const values[] = {
+ [1] = contact->fullname,
+ [3] = contact->photo,
+ [4] = contact->birthday,
+ [12] = contact->title,
+ [13] = contact->role,
+ [16] = contact->company,
+ [21] = contact->uid,
+ [23] = contact->nickname,
+ [28] = contact->calltype != CALL_TYPE_NOT_A_CALL ?
+ contact->datetime : NULL,
+ };
+ uint64_t mask = FILTER_VERSION;
+ GSList *l;
+ unsigned int i;
+
+ for (i = 0; i < G_N_ELEMENTS(values); i++) {
+ if (values[i] && values[i][0])
+ mask |= 1ULL << i;
+ }
+
+ if (contact_fields_present(contact))
+ mask |= FILTER_N;
+
+ if (contact->department && contact->department[0])
+ mask |= FILTER_ORG;
+
+ if (field_values_present(contact->numbers))
+ mask |= FILTER_TEL;
+
+ if (field_values_present(contact->emails))
+ mask |= FILTER_EMAIL;
+
+ if (field_values_present(contact->urls))
+ mask |= FILTER_URL;
+
+ for (l = contact->addresses; l; l = l->next) {
+ struct phonebook_addr *addr = l->data;
+ GSList *field;
+
+ for (field = addr->fields; field; field = field->next) {
+ const char *value = field->data;
+
+ if (value && value[0]) {
+ mask |= FILTER_ADR;
+ break;
+ }
+ }
+ }
+
+ return mask;
+}
+
static void vcard_printf_name(GString *vcards, uint8_t format,
struct phonebook_contact *contact)
{
diff --git a/obexd/plugins/vcard.h b/obexd/plugins/vcard.h
index 880660ffa..29b82f5f8 100644
--- a/obexd/plugins/vcard.h
+++ b/obexd/plugins/vcard.h
@@ -63,6 +63,8 @@ struct phonebook_contact {
void phonebook_add_contact(GString *vcards, struct phonebook_contact *contact,
uint64_t filter, uint8_t format);
+uint64_t phonebook_contact_get_mask(struct phonebook_contact *contact);
+
void phonebook_contact_free(struct phonebook_contact *contact);
void phonebook_addr_free(gpointer addr);
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 17/18] obexd/pbap: Advertise Contact Image Default Format
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (15 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 16/18] obexd/pbap: Add vCardSelector support Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
2026-11-04 8:34 ` [PATCH v1 18/18] obexd/pbap: Update SDP and PBAP version Jinwang Li
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Add DEFAULT_IMAGE_FEATURE to PSE_SUPPORTED_FEATURES. The feature has no
PCE bit, so it is advertised unconditionally.
Assisted-by: Claude:claude-sonnet-5-5
---
obexd/plugins/pbap.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index dba05b038..fef44ce73 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -63,11 +63,12 @@
#define DATABASEID_FEATURE 0x00000004
#define FOLDER_VERSION_FEATURE 0x00000008
#define VCARD_SELECTING_FEATURE 0x00000010
+#define DEFAULT_IMAGE_FEATURE 0x00000200
#define PSE_SUPPORTED_FEATURES \
(DOWNLOAD_FEATURE | BROWSE_FEATURE | \
DATABASEID_FEATURE | FOLDER_VERSION_FEATURE | \
- VCARD_SELECTING_FEATURE)
+ VCARD_SELECTING_FEATURE | DEFAULT_IMAGE_FEATURE)
struct cache {
gboolean valid;
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread* [PATCH v1 18/18] obexd/pbap: Update SDP and PBAP version
2026-11-04 8:34 [PATCH v1 00/18] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
` (16 preceding siblings ...)
2026-11-04 8:34 ` [PATCH v1 17/18] obexd/pbap: Advertise Contact Image Default Format Jinwang Li
@ 2026-11-04 8:34 ` Jinwang Li
17 siblings, 0 replies; 25+ messages in thread
From: Jinwang Li @ 2026-11-04 8:34 UTC (permalink / raw)
To: linux-bluetooth
Cc: cheng.jiang, quic_chezhou, wei.deng, shuai.zhang, mengshi.wu,
jinwang.li, xiuzhuo.shang
Set PbapSupportedFeatures to 0x0000021f and the PSE profile version to
0x0102.
Assisted-by: Claude:claude-sonnet-5-5
---
src/profile.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/profile.c b/src/profile.c
index e5b1cdfc5..d9dc92be5 100644
--- a/src/profile.c
+++ b/src/profile.c
@@ -469,7 +469,7 @@
<uint8 value=\"0x01\"/> \
</attribute> \
<attribute id=\"0x0317\"> \
- <uint32 value=\"0x00000003\"/> \
+ <uint32 value=\"0x0000021f\"/> \
</attribute> \
<attribute id=\"0x0200\"> \
<uint16 value=\"%u\" name=\"psm\"/> \
@@ -2208,7 +2208,7 @@ static struct default_settings {
.mode = BT_IO_MODE_ERTM,
.authorize = true,
.get_record = get_pse_record,
- .version = 0x0101,
+ .version = 0x0102,
.imtu = BT_RX_MTU,
}, {
.uuid = OBEX_PCE_UUID,
--
2.34.1
^ permalink raw reply related [flat|nested] 25+ messages in thread