Support basic Busy Lamp Field (BLF) function as server (#4474)#4891
Support basic Busy Lamp Field (BLF) function as server (#4474)#4891alfedotov wants to merge 2 commits intopjsip:masterfrom
Conversation
Tested with Yealink phones (models T77U, T34W, T31P, T30P)
7b055fd to
08bdaf8
Compare
|
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. |
|
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. |
There was a problem hiding this comment.
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: dialogand negotiateapplication/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. |
| /* 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); | ||
| } |
There was a problem hiding this comment.
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).
|
|
||
| pjrpid_element rpid = status->info[0].rpid; | ||
| pj_bool_t basic_open = status->info[0].basic_open; | ||
|
|
||
| const pj_str_t* state; |
There was a problem hiding this comment.
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.
| 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; | |
| } |
| 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 }; |
There was a problem hiding this comment.
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.
| 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 }; |
| 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); | ||
|
|
There was a problem hiding this comment.
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.
| 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); | ||
|
|
There was a problem hiding this comment.
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).
|
|
||
| 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); |
There was a problem hiding this comment.
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.
| pjsip_dlg_info_dialog_construct(pool, dialog, NULL); | |
| pjsip_dlg_info_dialog_construct(pool, dialog, &EMPTY_STRING); |
| if (name) node->name = *name; | ||
| else node->name.ptr=NULL, node->name.slen=0; |
There was a problem hiding this comment.
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.
| 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; | |
| } |
| 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); |
There was a problem hiding this comment.
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.
| 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); |
| /* 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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).
|
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. |
No description provided.