Linux bluetooth development
 help / color / mirror / Atom feed
* 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ messages in thread

* [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2
@ 2026-11-11  9:44 Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Jinwang Li
                   ` (19 more replies)
  0 siblings, 20 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, jinwang.li, xiuzhuo.shang

This series updates the obexd PBAP Phone Book Server Equipment (PSE)
from PBAP 1.1 to PBAP 1.2, following the PBAP 1.2.3 specification.

It adds PbapSupportedFeatures negotiation, Database Identifier and
primary and secondary Folder Version Counter response parameters, and
vCardSelector filtering for phonebook and vCard listing requests. It
also advertises Contact Image Default Format and updates the SDP profile
version to 0x0102.

The dummy backend persists the Database Identifier and folder counters.
The ebook and Tracker backends report zero values to indicate that these
values are not persistent. vCardSelector is implemented in all three
backends.

The series also fixes phonebook backend and request handling issues,
refreshes the listing cache for each request, and adds NewMissedCalls to
vCard listing responses. NewMissedCalls is limited to mch responses.

Testing was performed on the OE-Core wrynose branch (Yocto Project 6.0),
together with prerequisite patches that have already been merged
upstream. The dummy, ebook, and Tracker backends were all functionally
tested. The dummy backend was also tested against the PBAP PSE PTS test
cases covered by this series.

Patch overview:

 1. Fix the PRODID property name in the PBAP property tables and mask.
 2. Fix the PRODID property name in the PhonebookAccess documentation.
 3. Skip a missing dummy vCard callback when counting missed calls.
 4. Keep Tracker contact grouping state within each request.
 5. Include Tracker telephone numbers bound directly to contacts.
 6. Reject an empty PullvCardListing Name in the telecom folder.
 7. Omit PHOTO from dummy responses when PropertySelector excludes it.
 8. Rebuild the cache for each vCard listing request.
 9. Restrict NewMissedCalls to the first packet of mch responses.
10. Pass backend missed call counts to vCard listing responses.
11. Separate vCard entry response handling from phonebook responses.
12. Make CONNECT application parameters available to service plugins.
13. Parse and negotiate PbapSupportedFeatures during CONNECT.
14. Generate and persist the dummy Database Identifier.
15. Include Database Identifier in phonebook, listing and entry
    responses.
16. Maintain primary and secondary folder version counters in dummy.
17. Include negotiated folder version counters in phonebook and listing
    responses.
18. Apply vCardSelector filtering, counting and pagination in all
    backends.
19. Send only PHOTO properties in the Contact Image Default Format and
    advertise it in the PSE feature mask.
20. Update the SDP supported features and PBAP profile version.

Changes for V2
 - Rebased the patch.
 - Patches 1-2: Add the PRODID fixes.
 - Patch 7: Also remove the unindented BASE64 body of an unselected
   PHOTO property from the dummy backend responses.
 - Patch 16: Do not refresh the folder version counters for
   PullvCardEntry.
 - Patch 18: Use PRODID in the vCardSelector property table.
 - Patch 19: Send only PHOTO properties that are BASE64 encoded JPEG
   images of at most 300x300 pixels and 50 KiB.
 - V1 link
   https://lore.kernel.org/all/20261104083429.4111632-1-jinwang.li@oss.qualcomm.com/

Jinwang Li (20):
  obexd/pbap: Fix PRODID property name
  doc: Fix PRODID property name
  obexd/pbap: Fix crash counting vCards in dummy backend
  obexd/pbap: Fix Tracker contact grouping across requests
  obexd/pbap: Include directly bound Tracker telephone numbers
  obexd/pbap: Reject PullvCardListing without Name in telecom folder
  obexd/pbap: Fix unselected PHOTO in dummy responses
  obexd/pbap: Refresh cache for each vCard listing
  obexd/pbap: Limit NewMissedCalls to mch responses
  obexd/pbap: Add NewMissedCalls to vCard listing responses
  obexd/pbap: Separate vCard entry response handling
  obexd: Parse application parameters in CONNECT request
  obexd/pbap: Parse PbapSupportedFeatures in connect request
  obexd/pbap: Add persistent database identifier
  obexd/pbap: Include database identifier in responses
  obexd/pbap: Add folder version counters
  obexd/pbap: Include folder version counters in responses
  obexd/pbap: Add vCardSelector support
  obexd/pbap: Support Contact Image Default Format
  profile: Update PBAP PSE version and features

 Makefile.obexd                         |   2 +
 doc/org.bluez.obex.PhonebookAccess.rst |   2 +-
 obexd/client/pbap.c                    |   2 +-
 obexd/plugins/pbap.c                   | 565 ++++++++++++++++++++++---
 obexd/plugins/phonebook-dummy.c        | 548 ++++++++++++++++++++++--
 obexd/plugins/phonebook-ebook.c        |  88 +++-
 obexd/plugins/phonebook-metadata.c     | 257 +++++++++++
 obexd/plugins/phonebook-metadata.h     |  52 +++
 obexd/plugins/phonebook-tracker.c      | 367 +++++++++-------
 obexd/plugins/phonebook.c              |   8 +
 obexd/plugins/phonebook.h              |  84 +++-
 obexd/plugins/vcard.c                  |  71 +++-
 obexd/plugins/vcard.h                  |   2 +
 obexd/src/obex.c                       |  42 +-
 src/profile.c                          |   4 +-
 15 files changed, 1808 insertions(+), 286 deletions(-)
 create mode 100644 obexd/plugins/phonebook-metadata.c
 create mode 100644 obexd/plugins/phonebook-metadata.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11 12:15   ` obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 bluez.test.bot
  2026-11-11 15:50   ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Luiz Augusto von Dentz
  2026-11-11  9:44 ` [PATCH BlueZ v2 02/20] doc: " Jinwang Li
                   ` (18 subsequent siblings)
  19 siblings, 2 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, jinwang.li, xiuzhuo.shang

Use the standard PRODID spelling in the PBAP property tables and filter
mask so PropertySelector and vCardSelector bit 25 match the property.

Fixes: 3a1e6b3e8dc1 ("obexd: Implement Filter related function for PBAP client")
Fixes: 4f805f5e09f3 ("obexd: Add EVCard filter for PullPhoneBook")
Fixes: c8e98708ea3a ("obexd: Add support to all fields required by vCard 3.0")
Assisted-by: Claude:claude-sonnet-5-5
---
 obexd/client/pbap.c             | 2 +-
 obexd/plugins/phonebook-ebook.c | 2 +-
 obexd/plugins/vcard.c           | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/obexd/client/pbap.c b/obexd/client/pbap.c
index 1970d3357..fbd843a4a 100644
--- a/obexd/client/pbap.c
+++ b/obexd/client/pbap.c
@@ -107,7 +107,7 @@ static const char *filter_list[] = {
 	"KEY",
 	"NICKNAME",
 	"CATEGORIES",
-	"PROID",
+	"PRODID",
 	"CLASS",
 	"SORT-STRING",
 	"X-IRMC-CALL-DATETIME",
diff --git a/obexd/plugins/phonebook-ebook.c b/obexd/plugins/phonebook-ebook.c
index beb72b22b..d8621b8f9 100644
--- a/obexd/plugins/phonebook-ebook.c
+++ b/obexd/plugins/phonebook-ebook.c
@@ -81,7 +81,7 @@ static const char *attribute_mask[] = {
 		"KEY",
 		"NICKNAME",
 /* 24 */	"CATEGORIES",
-		"PROID",
+		"PRODID",
 		"CLASS",
 		"SORT-STRING",
 /* 28 */	"X-IRMC-CALL-DATETIME",
diff --git a/obexd/plugins/vcard.c b/obexd/plugins/vcard.c
index 0a727b729..3827602cc 100644
--- a/obexd/plugins/vcard.c
+++ b/obexd/plugins/vcard.c
@@ -54,7 +54,7 @@
 #define FILTER_KEY (1 << 22)
 #define FILTER_NICKNAME (1 << 23)
 #define FILTER_CATEGORIES (1 << 24)
-#define FILTER_PROID (1 << 25)
+#define FILTER_PRODID (1 << 25)
 #define FILTER_CLASS (1 << 26)
 #define FILTER_SORT_STRING (1 << 27)
 #define FILTER_X_IRMC_CALL_DATETIME (1 << 28)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH BlueZ v2 02/20] doc: Fix PRODID property name
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 03/20] obexd/pbap: Fix crash counting vCards in dummy backend Jinwang Li
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, jinwang.li, xiuzhuo.shang

Use the standard PRODID spelling in the PhonebookAccess filter fields.

Assisted-by: Claude:claude-sonnet-5-5
---
 doc/org.bluez.obex.PhonebookAccess.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/org.bluez.obex.PhonebookAccess.rst b/doc/org.bluez.obex.PhonebookAccess.rst
index 5b22d67f5..eb5142d86 100644
--- a/doc/org.bluez.obex.PhonebookAccess.rst
+++ b/doc/org.bluez.obex.PhonebookAccess.rst
@@ -329,7 +329,7 @@ Possible return:
 :"KEY":
 :"NICKNAME":
 :"CATEGORIES":
-:"PROID":
+:"PRODID":
 :"CLASS":
 :"SORT-STRING":
 :"X-IRMC-CALL-DATETIME":
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH BlueZ v2 03/20] obexd/pbap: Fix crash counting vCards in dummy backend
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 02/20] doc: " Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 04/20] obexd/pbap: Fix Tracker contact grouping across requests Jinwang Li
                   ` (16 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 04/20] obexd/pbap: Fix Tracker contact grouping across requests
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (2 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 03/20] obexd/pbap: Fix crash counting vCards in dummy backend Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 05/20] obexd/pbap: Include directly bound Tracker telephone numbers Jinwang Li
                   ` (15 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 05/20] obexd/pbap: Include directly bound Tracker telephone numbers
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (3 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 04/20] obexd/pbap: Fix Tracker contact grouping across requests Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 06/20] obexd/pbap: Reject PullvCardListing without Name in telecom folder Jinwang Li
                   ` (14 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 06/20] obexd/pbap: Reject PullvCardListing without Name in telecom folder
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (4 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 05/20] obexd/pbap: Include directly bound Tracker telephone numbers Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 07/20] obexd/pbap: Fix unselected PHOTO in dummy responses Jinwang Li
                   ` (13 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 07/20] obexd/pbap: Fix unselected PHOTO in dummy responses
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (5 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 06/20] obexd/pbap: Reject PullvCardListing without Name in telecom folder Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 08/20] obexd/pbap: Refresh cache for each vCard listing Jinwang Li
                   ` (12 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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 | 105 ++++++++++++++++++++++++++++----
 1 file changed, 93 insertions(+), 12 deletions(-)

diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index 14075b502..6b91c199c 100644
--- a/obexd/plugins/phonebook-dummy.c
+++ b/obexd/plugins/phonebook-dummy.c
@@ -193,10 +193,85 @@ 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(copy, "\n", -1);
+	result = g_string_new(NULL);
+
+	for (i = 0; lines[i]; i++) {
+		char *line = lines[i];
+		const char *end;
+		const char *name;
+		size_t len = strlen(line);
+
+		if (len && line[len - 1] == '\r')
+			line[len - 1] = '\0';
+
+		/* Nothing follows the last line break. */
+		if (!lines[i + 1] && !line[0])
+			break;
+
+		if (!include) {
+			/*
+			 * Folded lines, and the unfolded BASE64 lines and the
+			 * terminating blank line of vCard 2.1, belong to the
+			 * PHOTO value.
+			 */
+			if (!line[0]) {
+				include = TRUE;
+				continue;
+			}
+
+			if (line[0] == ' ' || line[0] == '\t' ||
+						!strchr(line, ':'))
+				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 +282,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 +320,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 +410,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 +424,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] 28+ messages in thread

* [PATCH BlueZ v2 08/20] obexd/pbap: Refresh cache for each vCard listing
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (6 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 07/20] obexd/pbap: Fix unselected PHOTO in dummy responses Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 09/20] obexd/pbap: Limit NewMissedCalls to mch responses Jinwang Li
                   ` (11 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 09/20] obexd/pbap: Limit NewMissedCalls to mch responses
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (7 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 08/20] obexd/pbap: Refresh cache for each vCard listing Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 10/20] obexd/pbap: Add NewMissedCalls to vCard listing responses Jinwang Li
                   ` (10 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 10/20] obexd/pbap: Add NewMissedCalls to vCard listing responses
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (8 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 09/20] obexd/pbap: Limit NewMissedCalls to mch responses Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 11/20] obexd/pbap: Separate vCard entry response handling Jinwang Li
                   ` (9 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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 6b91c199c..59fc77d6d 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;
@@ -402,7 +403,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;
 }
@@ -674,6 +675,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 d8621b8f9..07bf98678 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] 28+ messages in thread

* [PATCH BlueZ v2 11/20] obexd/pbap: Separate vCard entry response handling
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (9 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 10/20] obexd/pbap: Add NewMissedCalls to vCard listing responses Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 12/20] obexd: Parse application parameters in CONNECT request Jinwang Li
                   ` (8 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 12/20] obexd: Parse application parameters in CONNECT request
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (10 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 11/20] obexd/pbap: Separate vCard entry response handling Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 13/20] obexd/pbap: Parse PbapSupportedFeatures in connect request Jinwang Li
                   ` (7 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 13/20] obexd/pbap: Parse PbapSupportedFeatures in connect request
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (11 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 12/20] obexd: Parse application parameters in CONNECT request Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 14/20] obexd/pbap: Add persistent database identifier Jinwang Li
                   ` (6 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 14/20] obexd/pbap: Add persistent database identifier
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (12 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 13/20] obexd/pbap: Parse PbapSupportedFeatures in connect request Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 15/20] obexd/pbap: Include database identifier in responses Jinwang Li
                   ` (5 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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 59fc77d6d..f97693d0f 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] 28+ messages in thread

* [PATCH BlueZ v2 15/20] obexd/pbap: Include database identifier in responses
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (13 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 14/20] obexd/pbap: Add persistent database identifier Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 16/20] obexd/pbap: Add folder version counters Jinwang Li
                   ` (4 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 16/20] obexd/pbap: Add folder version counters
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (14 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 15/20] obexd/pbap: Include database identifier in responses Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 17/20] obexd/pbap: Include folder version counters in responses Jinwang Li
                   ` (3 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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    | 246 +++++++++++++++++++++++++++++
 obexd/plugins/phonebook-metadata.c |  54 +++++++
 obexd/plugins/phonebook-metadata.h |   8 +-
 obexd/plugins/phonebook.h          |   2 +
 4 files changed, 309 insertions(+), 1 deletion(-)

diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c
index f97693d0f..72d7ce622 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;
@@ -599,6 +838,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)) {
@@ -681,6 +925,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] 28+ messages in thread

* [PATCH BlueZ v2 17/20] obexd/pbap: Include folder version counters in responses
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (15 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 16/20] obexd/pbap: Add folder version counters Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 18/20] obexd/pbap: Add vCardSelector support Jinwang Li
                   ` (2 subsequent siblings)
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* [PATCH BlueZ v2 18/20] obexd/pbap: Add vCardSelector support
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (16 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 17/20] obexd/pbap: Include folder version counters in responses Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 19/20] obexd/pbap: Support Contact Image Default Format Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 20/20] profile: Update PBAP PSE version and features Jinwang Li
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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,
 						&param->liststartoffset);
 		g_obex_apparam_get_uint64(apparam, FILTER_TAG, &param->filter);
+		g_obex_apparam_get_uint64(apparam, VCARD_SELECTOR_TAG,
+						&param->selector);
+		g_obex_apparam_get_uint8(apparam,
+						VCARD_SELECTOR_OPERATOR_TAG,
+						&param->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 72d7ce622..3c3ae709a 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,
@@ -537,6 +643,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
@@ -555,41 +678,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);
 
@@ -602,6 +727,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);
@@ -614,6 +740,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("");
@@ -648,7 +776,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 07bf98678..d2acc4d9b 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..60aa433d2 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",
+	"PRODID",
+	"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 3827602cc..3f3f833dc 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] 28+ messages in thread

* [PATCH BlueZ v2 19/20] obexd/pbap: Support Contact Image Default Format
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (17 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 18/20] obexd/pbap: Add vCardSelector support Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  2026-11-11  9:44 ` [PATCH BlueZ v2 20/20] profile: Update PBAP PSE version and features Jinwang Li
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, jinwang.li, xiuzhuo.shang

Add DEFAULT_IMAGE_FEATURE to PSE_SUPPORTED_FEATURES. Only send PHOTO
properties that are BASE64 encoded JPEG images of at most 300x300
pixels and 50 KiB, as required by this feature.

Assisted-by: Claude:claude-sonnet-5-5
---
 obexd/plugins/pbap.c | 283 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 280 insertions(+), 3 deletions(-)

diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c
index dba05b038..b4e6e5eb7 100644
--- a/obexd/plugins/pbap.c
+++ b/obexd/plugins/pbap.c
@@ -63,11 +63,17 @@
 #define DATABASEID_FEATURE	0x00000004
 #define FOLDER_VERSION_FEATURE	0x00000008
 #define VCARD_SELECTING_FEATURE	0x00000010
+#define DEFAULT_IMAGE_FEATURE	0x00000200
+
+#define DEFAULT_IMAGE_MAX_SIZE		(50 * 1024)
+#define DEFAULT_IMAGE_MAX_DIMENSION	300
+/* Number of BASE64 characters encoding DEFAULT_IMAGE_MAX_SIZE bytes */
+#define DEFAULT_IMAGE_MAX_BASE64	((DEFAULT_IMAGE_MAX_SIZE + 2) / 3 * 4)
 
 #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;
@@ -263,10 +269,278 @@ static void phonebook_size_result(const char *buffer, size_t bufsize,
 	obex_object_set_io_flags(pbap->obj, G_IO_IN, 0);
 }
 
+/* Returns whether a JPEG image is at most 300x300 pixels. */
+static gboolean jpeg_is_default_image(const uint8_t *image, size_t length)
+{
+	size_t offset = 2;
+
+	if (length < 2 || image[0] != 0xff || image[1] != 0xd8)
+		return FALSE;
+
+	while (offset + 4 <= length) {
+		uint16_t segment_length, height, width;
+		uint8_t marker;
+
+		if (image[offset] != 0xff)
+			return FALSE;
+
+		marker = image[offset + 1];
+
+		/* Fill byte */
+		if (marker == 0xff) {
+			offset++;
+			continue;
+		}
+
+		/* Start of scan or end of image before the frame header */
+		if (marker == 0xda || marker == 0xd9)
+			return FALSE;
+
+		segment_length = (image[offset + 2] << 8) | image[offset + 3];
+		if (segment_length < 2 || offset + 2 + segment_length > length)
+			return FALSE;
+
+		/* Start of frame markers, except DHT, JPG and DAC */
+		if (marker >= 0xc0 && marker <= 0xcf && marker != 0xc4 &&
+					marker != 0xc8 && marker != 0xcc) {
+			if (segment_length < 7)
+				return FALSE;
+
+			height = (image[offset + 5] << 8) | image[offset + 6];
+			width = (image[offset + 7] << 8) | image[offset + 8];
+
+			return height && width &&
+				height <= DEFAULT_IMAGE_MAX_DIMENSION &&
+				width <= DEFAULT_IMAGE_MAX_DIMENSION;
+		}
+
+		offset += 2 + segment_length;
+	}
+
+	return FALSE;
+}
+
+/* Returns whether a value is valid BASE64 with correct padding. */
+static gboolean base64_is_valid(const GString *value)
+{
+	gsize padding = 0;
+	gsize i;
+
+	if (!value->len || value->len % 4)
+		return FALSE;
+
+	for (i = 0; i < value->len; i++) {
+		char c = value->str[i];
+
+		if (c == '=') {
+			padding++;
+			continue;
+		}
+
+		/* Padding is only allowed at the end */
+		if (padding)
+			return FALSE;
+
+		if (!g_ascii_isalnum(c) && c != '+' && c != '/')
+			return FALSE;
+	}
+
+	return padding <= 2;
+}
+
+static gboolean photo_is_default_image(const GString *value)
+{
+	guchar *image;
+	gsize length;
+	gboolean valid;
+
+	/* Reject images that are too large without decoding them. */
+	if (value->len > DEFAULT_IMAGE_MAX_BASE64)
+		return FALSE;
+
+	if (!base64_is_valid(value))
+		return FALSE;
+
+	image = g_base64_decode(value->str, &length);
+	valid = length <= DEFAULT_IMAGE_MAX_SIZE &&
+				jpeg_is_default_image(image, length);
+	g_free(image);
+
+	return valid;
+}
+
+/* Returns whether the parameters declare the BASE64 encoding. */
+static gboolean params_are_base64(const char *params, const char *end)
+{
+	const char *p = params;
+
+	while (p < end) {
+		const char *next;
+		size_t len;
+
+		/* Skip the ';' separator */
+		p++;
+
+		next = memchr(p, ';', end - p);
+		if (!next)
+			next = end;
+
+		len = next - p;
+
+		/* Check the value of ENCODING=, or a vCard 2.1 bare value */
+		if (len > 9 && g_ascii_strncasecmp(p, "ENCODING=", 9) == 0) {
+			p += 9;
+			len -= 9;
+
+			/* vCard 3.0 ENCODING=b */
+			if (len == 1 && g_ascii_tolower(*p) == 'b')
+				return TRUE;
+		}
+
+		/* vCard 2.1 ENCODING=BASE64 or BASE64 */
+		if (len == 6 && g_ascii_strncasecmp(p, "BASE64", 6) == 0)
+			return TRUE;
+
+		p = next;
+	}
+
+	return FALSE;
+}
+
+/* Returns whether a line starts a PHOTO property. */
+static gboolean line_is_photo(const char *line, size_t length,
+				size_t *value_offset, gboolean *base64)
+{
+	const char *colon;
+	const char *end;
+	const char *name;
+	const char *p;
+
+	if (!length || line[0] == ' ' || line[0] == '\t')
+		return FALSE;
+
+	colon = memchr(line, ':', length);
+	if (!colon)
+		return FALSE;
+
+	end = memchr(line, ';', colon - line);
+	if (!end)
+		end = colon;
+
+	/* Skip the group prefix */
+	for (name = p = line; p < end; p++) {
+		if (*p == '.')
+			name = p + 1;
+	}
+
+	if (end - name != 5 || g_ascii_strncasecmp(name, "PHOTO", 5) != 0)
+		return FALSE;
+
+	*value_offset = colon - line + 1;
+	*base64 = params_are_base64(end, colon);
+
+	return TRUE;
+}
+
+/* Appends a part of a PHOTO value without any space or tab. */
+static void append_value(GString *value, const char *part, size_t length)
+{
+	size_t i;
+
+	for (i = 0; i < length; i++) {
+		/* Larger values are rejected without being kept. */
+		if (value->len > DEFAULT_IMAGE_MAX_BASE64)
+			return;
+
+		if (part[i] != ' ' && part[i] != '\t')
+			g_string_append_c(value, part[i]);
+	}
+}
+
+static void append_photo(GString *result, const char *start, const char *end,
+						GString *value, gboolean base64)
+{
+	if (base64 && photo_is_default_image(value))
+		g_string_append_len(result, start, end - start);
+
+	g_string_truncate(value, 0);
+}
+
+/*
+ * Drops PHOTO properties that are not BASE64 encoded JPEG images of at
+ * most 300x300 pixels and 50 KiB, as required by the Contact Image
+ * Default Format.
+ */
+static char *filter_default_images(const char *buffer, size_t length,
+						size_t *filtered_length)
+{
+	const char *photo = NULL;
+	const char *line;
+	const char *end;
+	gboolean base64 = FALSE;
+	GString *result;
+	GString *value;
+
+	if (!buffer || !length) {
+		*filtered_length = 0;
+		return g_strdup("");
+	}
+
+	end = buffer + length;
+	result = g_string_sized_new(length);
+	value = g_string_new(NULL);
+
+	for (line = buffer; line < end;) {
+		const char *next = memchr(line, '\n', end - line);
+		size_t len, offset;
+
+		next = next ? next + 1 : end;
+		len = next - line;
+
+		while (len && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+			len--;
+
+		if (photo) {
+			/*
+			 * Folded lines, and the unfolded BASE64 lines and the
+			 * terminating blank line of vCard 2.1, belong to the
+			 * PHOTO value.
+			 */
+			if (!len || line[0] == ' ' || line[0] == '\t' ||
+						!memchr(line, ':', len)) {
+				append_value(value, line, len);
+				line = next;
+				continue;
+			}
+
+			append_photo(result, photo, line, value, base64);
+			photo = NULL;
+		}
+
+		if (line_is_photo(line, len, &offset, &base64)) {
+			photo = line;
+			append_value(value, line + offset, len - offset);
+		} else
+			g_string_append_len(result, line, next - line);
+
+		line = next;
+	}
+
+	if (photo)
+		append_photo(result, photo, end, value, base64);
+
+	g_string_free(value, TRUE);
+
+	*filtered_length = result->len;
+	return g_string_free(result, FALSE);
+}
+
 /* 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)
 {
+	char *filtered;
+	size_t filtered_length;
 	gboolean first;
 
 	if (pbap->obj->request && lastpart) {
@@ -282,12 +556,15 @@ static int vobject_append(struct pbap_session *pbap, const char *buffer,
 	}
 
 	first = !pbap->obj->buffer;
+	filtered = filter_default_images(buffer, bufsize, &filtered_length);
 
 	if (first)
-		pbap->obj->buffer = g_string_new_len(buffer, bufsize);
+		pbap->obj->buffer = g_string_new_len(filtered, filtered_length);
 	else
 		pbap->obj->buffer = g_string_append_len(pbap->obj->buffer,
-							buffer,	bufsize);
+						filtered, filtered_length);
+
+	g_free(filtered);
 
 	return first;
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH BlueZ v2 20/20] profile: Update PBAP PSE version and features
  2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
                   ` (18 preceding siblings ...)
  2026-11-11  9:44 ` [PATCH BlueZ v2 19/20] obexd/pbap: Support Contact Image Default Format Jinwang Li
@ 2026-11-11  9:44 ` Jinwang Li
  19 siblings, 0 replies; 28+ messages in thread
From: Jinwang Li @ 2026-11-11  9:44 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
  Cc: cheng.jiang, wei.deng, shuai.zhang, 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] 28+ messages in thread

* RE: obexd/pbap: Update PSE support from PBAP 1.1 to 1.2
  2026-11-11  9:44 ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Jinwang Li
@ 2026-11-11 12:15   ` bluez.test.bot
  2026-11-11 15:50   ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Luiz Augusto von Dentz
  1 sibling, 0 replies; 28+ messages in thread
From: bluez.test.bot @ 2026-11-11 12:15 UTC (permalink / raw)
  To: linux-bluetooth, jinwang.li

[-- Attachment #1: Type: text/plain, Size: 31945 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/1208162/

---Test result---

Test Summary:
CheckPatch                    PASS      8.03 seconds
GitLint                       FAIL      4.37 seconds
BuildEll                      PASS      15.90 seconds
BluezMake                     PASS      225.12 seconds
MakeCheck                     PASS      13.65 seconds
MakeDistcheck                 PASS      115.56 seconds
CheckValgrind                 PASS      274.50 seconds
CheckSmatch                   PASS      224.49 seconds
bluezmakeextell               PASS      74.26 seconds
TestFunctional                FAIL      3064.97 seconds
IncrementalBuild              PASS      277.39 seconds
ScanBuild                     PASS      825.36 seconds

Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,v2,06/20] obexd/pbap: Reject PullvCardListing without Name in telecom folder

1: T1 Title exceeds max length (83>80): "[BlueZ,v2,06/20] obexd/pbap: Reject PullvCardListing without Name in telecom folder"
##############################
Test: TestFunctional - FAIL
Desc: Run test-functional
Output:
FAIL functional.test_adv_monitor::test_adv_monitor_GHSA_hhgc_hfgf_8m4x[hosts1-vm1]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bluetoothctl::test_bluetoothctl_show[hosts16-vm1]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bluetoothctl::test_bluetoothctl_list[hosts16-vm1]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bluetoothctl::test_bluetoothctl_script_show[hosts16-vm1]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bluetoothctl::test_bluetoothctl_script_list[hosts16-vm1]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_btmgmt::test_btmgmt_info[hosts19-vm1]: AssertionError: assert 'addr 00:00:00:00:00:00' in '\x1b[0;91mReading hci0 info failed with status 0x11 (Invalid Index)\n\x1b[0m'
     +  where '\x1b[0;91mReading hci0 info failed with status 0x11 (Invalid Index)\n\x1b[0m' = CompletedProcess(args=['/home/runner/work/bluez/bluez/src/src/tools/btmgmt', '--index', '0', 'info'], returncode=0, stdout='\x1b[0;91mReading hci0 info failed with status 0x11 (Invalid Index)\n\x1b[0m').stdout
FAIL functional.test_a2dp::test_a2dp_transport_created[hosts0-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_a2dp::test_a2dp_transport_acquire[hosts0-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_a2dp::test_a2dp_disconnect_during_setconf[hosts0-vm2-accept]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_a2dp::test_a2dp_disconnect_during_setconf[hosts0-vm2-reject]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_a2dp::test_a2dp_reconnect_pending_disconnect[hosts0-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bearer::test_bearer_le_connect[hosts12-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bearer::test_bearer_le_conn_params[hosts12-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bearer::test_bearer_le_connectable[hosts12-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bearer::test_bearer_le_pair[hosts12-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bearer::test_bearer_bredr_pair[hosts13-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bearer::test_bearer_le_conn_update[hosts14-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bearer::test_bearer_le_not_connectable[hosts15-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bluetoothctl::test_bluetoothctl_pair_bredr[hosts17-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bluetoothctl::test_bluetoothctl_pair_le[hosts18-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_agent::test_agent_pair_bredr[accept-hosts2-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_agent::test_agent_pair_bredr[reject-hosts2-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_hog::test_hog[no-sci-hosts20-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_hog::test_hog[sci-hosts20-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_mpris_proxy::test_mpris_proxy_playback_control[bredr-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_mpris_proxy::test_mpris_proxy_track_control[bredr-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_obex::test_obex_ftp_list[hosts24-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_obex::test_obex_ftp_get[hosts24-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_obex::test_obexctl_list[hosts24-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_avrcp::test_avrcp_GHSA_m2vx_pw5f_rc8v[hosts3-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_transport_created[hosts4-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_transport_acquire[hosts4-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_transport_resume[0-hosts4-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_transport_resume[0.1-hosts4-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_transport_resume[1.0-hosts4-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_reconfigure_metadata[empty-hosts5-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_reconfigure_metadata[media-hosts5-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_desync[hosts6-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_duplex_enable_one[hosts6-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_broadcast_transport_created[lc3-hosts7-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_broadcast_transport_created[pbp-hosts7-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_broadcast_transport_acquire[lc3-hosts7-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_broadcast_transport_acquire[pbp-hosts7-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bass_past_transport_acquire[hosts9-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_set_transport_created[hosts10-vm3-sc]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_set_transport_created[hosts10-vm3-legacy]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_unicast_set_transport_acquire[hosts10-vm3]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bass_past_earbuds_transport_acquire[hosts11-vm3]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_broadcast_earbuds_transport_created[hosts8-vm3]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_bap::test_bap_broadcast_earbuds_transport_acquire[hosts8-vm3]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_mpris_proxy::test_mpris_proxy_playback_control[le-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"
FAIL functional.test_mpris_proxy::test_mpris_proxy_track_control[le-vm2]: failed on setup with "pytest_bluezenv.rpc.RemoteError: load failed
    Remote traceback:
        Traceback (most recent call last):
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/rpc.py", line 102, in server_stream
            result = method(*msg["a"], **msg["kw"])
          File "/usr/local/lib/python3.14/dist-packages/pytest_bluezenv/../pytest_bluezenv/env.py", line 238, in wait_load
            raise RuntimeError("load failed")
        RuntimeError: load failed"


https://github.com/bluez/bluez/pull/2794

---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name
  2026-11-11  9:44 ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Jinwang Li
  2026-11-11 12:15   ` obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 bluez.test.bot
@ 2026-11-11 15:50   ` Luiz Augusto von Dentz
  1 sibling, 0 replies; 28+ messages in thread
From: Luiz Augusto von Dentz @ 2026-11-11 15:50 UTC (permalink / raw)
  To: Jinwang Li
  Cc: linux-bluetooth, cheng.jiang, wei.deng, shuai.zhang,
	xiuzhuo.shang

Hi Jinwang,

On Wed, Nov 11, 2026 at 4:44 AM Jinwang Li <jinwang.li@oss.qualcomm.com> wrote:
>
> Use the standard PRODID spelling in the PBAP property tables and filter
> mask so PropertySelector and vCardSelector bit 25 match the property.
>
> Fixes: 3a1e6b3e8dc1 ("obexd: Implement Filter related function for PBAP client")
> Fixes: 4f805f5e09f3 ("obexd: Add EVCard filter for PullPhoneBook")
> Fixes: c8e98708ea3a ("obexd: Add support to all fields required by vCard 3.0")
> Assisted-by: Claude:claude-sonnet-5-5
> ---
>  obexd/client/pbap.c             | 2 +-
>  obexd/plugins/phonebook-ebook.c | 2 +-
>  obexd/plugins/vcard.c           | 2 +-
>  3 files changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/obexd/client/pbap.c b/obexd/client/pbap.c
> index 1970d3357..fbd843a4a 100644
> --- a/obexd/client/pbap.c
> +++ b/obexd/client/pbap.c
> @@ -107,7 +107,7 @@ static const char *filter_list[] = {
>         "KEY",
>         "NICKNAME",
>         "CATEGORIES",
> -       "PROID",
> +       "PRODID",
>         "CLASS",
>         "SORT-STRING",
>         "X-IRMC-CALL-DATETIME",
> diff --git a/obexd/plugins/phonebook-ebook.c b/obexd/plugins/phonebook-ebook.c
> index beb72b22b..d8621b8f9 100644
> --- a/obexd/plugins/phonebook-ebook.c
> +++ b/obexd/plugins/phonebook-ebook.c
> @@ -81,7 +81,7 @@ static const char *attribute_mask[] = {
>                 "KEY",
>                 "NICKNAME",
>  /* 24 */       "CATEGORIES",
> -               "PROID",
> +               "PRODID",
>                 "CLASS",
>                 "SORT-STRING",
>  /* 28 */       "X-IRMC-CALL-DATETIME",
> diff --git a/obexd/plugins/vcard.c b/obexd/plugins/vcard.c
> index 0a727b729..3827602cc 100644
> --- a/obexd/plugins/vcard.c
> +++ b/obexd/plugins/vcard.c
> @@ -54,7 +54,7 @@
>  #define FILTER_KEY (1 << 22)
>  #define FILTER_NICKNAME (1 << 23)
>  #define FILTER_CATEGORIES (1 << 24)
> -#define FILTER_PROID (1 << 25)
> +#define FILTER_PRODID (1 << 25)
>  #define FILTER_CLASS (1 << 26)
>  #define FILTER_SORT_STRING (1 << 27)
>  #define FILTER_X_IRMC_CALL_DATETIME (1 << 28)
> --
> 2.34.1
>

Copilot flagged some problems:

https://github.com/bluez/bluez/pull/2794#pullrequestreview-5861421304

Also next time lease send with a cover letter and a summary of the
changes since the last version to make easier to follow what has been
addressed.

-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2026-11-11 15:50 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11  9:44 [PATCH BlueZ v2 00/20] obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Jinwang Li
2026-11-11 12:15   ` obexd/pbap: Update PSE support from PBAP 1.1 to 1.2 bluez.test.bot
2026-11-11 15:50   ` [PATCH BlueZ v2 01/20] obexd/pbap: Fix PRODID property name Luiz Augusto von Dentz
2026-11-11  9:44 ` [PATCH BlueZ v2 02/20] doc: " Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 03/20] obexd/pbap: Fix crash counting vCards in dummy backend Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 04/20] obexd/pbap: Fix Tracker contact grouping across requests Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 05/20] obexd/pbap: Include directly bound Tracker telephone numbers Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 06/20] obexd/pbap: Reject PullvCardListing without Name in telecom folder Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 07/20] obexd/pbap: Fix unselected PHOTO in dummy responses Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 08/20] obexd/pbap: Refresh cache for each vCard listing Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 09/20] obexd/pbap: Limit NewMissedCalls to mch responses Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 10/20] obexd/pbap: Add NewMissedCalls to vCard listing responses Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 11/20] obexd/pbap: Separate vCard entry response handling Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 12/20] obexd: Parse application parameters in CONNECT request Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 13/20] obexd/pbap: Parse PbapSupportedFeatures in connect request Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 14/20] obexd/pbap: Add persistent database identifier Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 15/20] obexd/pbap: Include database identifier in responses Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 16/20] obexd/pbap: Add folder version counters Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 17/20] obexd/pbap: Include folder version counters in responses Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 18/20] obexd/pbap: Add vCardSelector support Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 19/20] obexd/pbap: Support Contact Image Default Format Jinwang Li
2026-11-11  9:44 ` [PATCH BlueZ v2 20/20] profile: Update PBAP PSE version and features Jinwang Li
  -- strict thread matches above, loose matches on Subject: below --
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 ` 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
2026-11-09 14:31       ` Luiz Augusto von Dentz
2026-11-10  2:00         ` Jinwang Li

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox