Skip to content

Support basic Busy Lamp Field (BLF) function as server (#4474)#4891

Open
alfedotov wants to merge 2 commits intopjsip:masterfrom
alfedotov:pr-4474-dialog-info-blf
Open

Support basic Busy Lamp Field (BLF) function as server (#4474)#4891
alfedotov wants to merge 2 commits intopjsip:masterfrom
alfedotov:pr-4474-dialog-info-blf

Conversation

@alfedotov
Copy link
Copy Markdown

No description provided.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 26, 2026

CLA assistant check
All committers have signed the CLA.

@alfedotov alfedotov changed the title Pr 4474 dialog info blf Support basic Busy Lamp Field (BLF) function as server (#4474) Mar 26, 2026
@alfedotov alfedotov force-pushed the pr-4474-dialog-info-blf branch from 7b055fd to 08bdaf8 Compare March 26, 2026 13:37
@sauwming
Copy link
Copy Markdown
Member

This doesn't seem like a complete server implementation. So perhaps you can describe the objective and scope of this PR in the PR description.

@alfedotov
Copy link
Copy Markdown
Author

Correct, this is not complete server implementation with subscribe to dialog states.

I've found that modern yealink phones has no Presence option in setup for programmable buttons, but only BLF. My PR simply adds minimal support for BLF needed for correct busy/available indication.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds initial support for serving Busy Lamp Field (BLF)-style subscriptions by allowing dialog event subscriptions and generating application/dialog-info+xml bodies derived from existing presence status.

Changes:

  • Extend presence UAS handling to recognize Event: dialog and negotiate application/dialog-info+xml.
  • Add a new public utility API to generate dialog-info bodies from pjsip_pres_status.
  • Adjust dialog-info XML construction logic to support the new output shape.

Reviewed changes

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

File Description
pjsip/src/pjsip-simple/presence.c Accepts Event: dialog, negotiates dialog-info content type, and routes NOTIFY body creation accordingly.
pjsip/src/pjsip-simple/presence_body.c Implements pjsip_pres_create_dialog_info() to produce a dialog-info XML body from presence status.
pjsip/src/pjsip-simple/dialog_info.c Modifies dialog-info XML node construction and where dialog state is represented.
pjsip/include/pjsip-simple/presence.h Exposes the new dialog-info body creation helper as a public API.

Comment on lines 272 to 284
/* Check that Event header contains "presence" */
event = (pjsip_event_hdr*)
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
if (!event) {
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
}
if (pj_stricmp(&event->event_type, &STR_PRESENCE) != 0) {
}
else if (pj_stricmp(&event->event_type, &STR_DIALOG) != 0) {
}
else {
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT);
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The Event header validation logic is broken: the current if/else-if/else blocks are empty, so unsupported Event types will never trigger PJSIP_SC_BAD_EVENT. This should reject when event_type is neither "presence" nor "dialog" (e.g., combine conditions with && and return BAD_EVENT in that case).

Copilot uses AI. Check for mistakes.
Comment on lines +316 to +320

pjrpid_element rpid = status->info[0].rpid;
pj_bool_t basic_open = status->info[0].basic_open;

const pj_str_t* state;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This function reads status->info[0] unconditionally, which will access out of bounds when status->info_cnt==0 (e.g., when no tuple/person info is present). Please guard on info_cnt first and choose a safe default state when no info is available.

Suggested change
pjrpid_element rpid = status->info[0].rpid;
pj_bool_t basic_open = status->info[0].basic_open;
const pj_str_t* state;
pjrpid_element rpid;
pj_bool_t basic_open;
const pj_str_t* state;
pj_bzero(&rpid, sizeof(rpid));
basic_open = PJ_FALSE;
if (status->info_cnt > 0) {
rpid = status->info[0].rpid;
basic_open = status->info[0].basic_open;
}

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +44
static pj_str_t STR_CONFIRMED = { "confirmed", 9 };
static pj_str_t STR_TERMINATED = { "terminated", 10 };
static pj_str_t STR_VERSION = { "1.0", 3 };
static pj_str_t STR_EMPTY_STRING = { NULL, 0 };
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Several of these new static pj_str_t values are mutable even though they are constants, and STR_EMPTY_STRING is currently unused (will trigger -Wunused-variable under -Wall). Consider making the constants static const pj_str_t and removing STR_EMPTY_STRING (and any other unused globals) to keep builds warning-free.

Suggested change
static pj_str_t STR_CONFIRMED = { "confirmed", 9 };
static pj_str_t STR_TERMINATED = { "terminated", 10 };
static pj_str_t STR_VERSION = { "1.0", 3 };
static pj_str_t STR_EMPTY_STRING = { NULL, 0 };
static const pj_str_t STR_CONFIRMED = { "confirmed", 9 };
static const pj_str_t STR_TERMINATED = { "terminated", 10 };
static const pj_str_t STR_VERSION = { "1.0", 3 };

Copilot uses AI. Check for mistakes.
Comment on lines +322 to +337
if (basic_open) {
if (rpid.activity == PJRPID_ACTIVITY_BUSY) {
state = &STR_CONFIRMED;
}
else {
state = &STR_TERMINATED;
}
}
else {
//pjsip_dlg_info_dialog_set_state(pool, dlginfo, &STR_TERMINATED);
state = &STR_TERMINATED;
}

/* Create DIALOG-INFO document. */
dlginfo = pjsip_dlg_info_create(pool, &STR_VERSION, state, entity);

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

pjsip_dlg_info_create()/pjsip_dlg_info_dialog_info_construct() expects state to be the dialog-info document state attribute (per existing get_state()/set_state() which look for an attribute), but here state is being passed a call-state value (confirmed/terminated). This mismatch will generate a dialog-info document that doesn't conform to the current API/RFC expectations; consider passing a valid document state (e.g., full/partial) and setting the dialog's separately.

Copilot uses AI. Check for mistakes.
Comment on lines 528 to +542
xml_init_node(pool, dialog_info, &DIALOG_INFO, NULL);
attr = xml_create_attr(pool, &VERSION, version);
pj_xml_add_attr(dialog_info, attr);

attr = xml_create_attr(pool, &STATE, state);
pj_xml_add_attr(dialog_info, attr);

attr = xml_create_attr(pool, &ENTITY, entity);
pj_xml_add_attr(dialog_info, attr);

dialog = PJ_POOL_ALLOC_T(pool, pjsip_dlg_info_dialog);
pjsip_dlg_info_local_construct(pool, dialog);
pjsip_dlg_info_dialog_construct(pool, dialog, NULL);
pj_xml_add_node(dialog_info, dialog);

node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
xml_init_node(pool, node, &STATE, state);
pj_xml_add_node(dialog, node);

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

dialog-info construction no longer adds the dialog-info "state" attribute, but pjsip_dlg_info_dialog_info_get_state()/set_state() still operate on that attribute. As a result, the constructed document will have an empty state via the existing API and may be non-compliant; restore the STATE attribute on (and use the per- element for call state if needed).

Copilot uses AI. Check for mistakes.

dialog = PJ_POOL_ALLOC_T(pool, pjsip_dlg_info_dialog);
pjsip_dlg_info_local_construct(pool, dialog);
pjsip_dlg_info_dialog_construct(pool, dialog, NULL);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

pjsip_dlg_info_dialog_info_construct() creates a with id=NULL, which means the required "id" attribute is omitted. This can produce invalid dialog-info (RFC 4235 requires dialog id) and also breaks pjsip_dlg_info_dialog_set_id() which asserts the id attr exists. Consider always creating the id attribute (or generate a default id) when constructing a dialog.

Suggested change
pjsip_dlg_info_dialog_construct(pool, dialog, NULL);
pjsip_dlg_info_dialog_construct(pool, dialog, &EMPTY_STRING);

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +55
if (name) node->name = *name;
else node->name.ptr=NULL, node->name.slen=0;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The else node->name.ptr=NULL, node->name.slen=0; uses the comma operator and lacks braces, which is easy to misread and brittle for future edits. Prefer a braced else block with two explicit statements assigning ptr and slen.

Suggested change
if (name) node->name = *name;
else node->name.ptr=NULL, node->name.slen=0;
if (name) {
node->name = *name;
} else {
node->name.ptr = NULL;
node->name.slen = 0;
}

Copilot uses AI. Check for mistakes.
Comment on lines +419 to +422
PJ_DECL(pj_status_t) pjsip_pres_create_dialog_info(pj_pool_t* pool,
const pjsip_pres_status* status,
const pj_str_t* entity,
pjsip_msg_body** p_body);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The new public API declaration uses a different pointer formatting style (pj_pool_t*, pjsip_pres_status*, etc.) than the rest of this header (e.g., pjsip_pres_create_pidf() uses pj_pool_t *pool). For consistency with existing public headers, please match the surrounding spacing convention.

Suggested change
PJ_DECL(pj_status_t) pjsip_pres_create_dialog_info(pj_pool_t* pool,
const pjsip_pres_status* status,
const pj_str_t* entity,
pjsip_msg_body** p_body);
PJ_DECL(pj_status_t) pjsip_pres_create_dialog_info(pj_pool_t *pool,
const pjsip_pres_status *status,
const pj_str_t *entity,
pjsip_msg_body **p_body);

Copilot uses AI. Check for mistakes.
Comment on lines 286 to 304
/* Check that request contains compatible Accept header. */
accept = (pjsip_accept_hdr*)
pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
if (accept) {
unsigned i;
for (i=0; i<accept->count; ++i) {
if (pj_stricmp(&accept->values[i], &STR_APP_PIDF_XML)==0) {
content_type = CONTENT_TYPE_PIDF;
break;
} else
if (pj_stricmp(&accept->values[i], &STR_APP_XPIDF_XML)==0) {
content_type = CONTENT_TYPE_XPIDF;
break;
} else
if (pj_stricmp(&accept->values[i], &STR_APP_DIALOG_INFO_XML) == 0) {
content_type = CONTENT_TYPE_DIALOG_INFO;
break;
}
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Accept header negotiation now allows application/dialog-info+xml unconditionally. This means a normal "presence" subscription with Accept: application/dialog-info+xml would be treated as acceptable and later generate a dialog-info body, which doesn't match the presence event package. Consider only accepting dialog-info when Event is "dialog" (and keep presence limited to PIDF/XPIDF).

Copilot uses AI. Check for mistakes.
@sauwming
Copy link
Copy Markdown
Member

sauwming commented Apr 6, 2026

Thank you for the contribution, but at its current state, I am leaning towards rejecting this since the server support seems to be still basic, and I'm not sure how it can be useful for PJSIP users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants