PJSIP_AOR(aor_id, contact) - very strange result for contact

I am an Asterisk amateur trying to understand the behaviour of aor and some related functions.
I am trying to use a dialplan function PJSIP_AOR to retrieve contact information without success. I am hoping that someone can help.
Below are the details.

I am running: Asterisk 20.7.0 built by pi @ raspberrypi on aarch64 running Linux built on 2024-04-02 16:50:39 UT
This example is PJSIP_AOR. The documentation reads:

PJSIP_AOR(name,field)
Arguments¶
name - The name of the AOR to query.
field - The configuration option for the AOR to query for. Supported options are those fields on the aor object in pjsip.conf.
contact - Permanent contacts assigned to AoR
…etc.

The relevant fragment of my pjsip.conf is:

[who-endpoint2]
type=endpoint
context = who-Incoming
allow = !all,g722,alaw
outbound_auth = who-auth2
aors = who-aor2
direct_media = no
from_domain = sip.who.com

[who-auth2]
type = auth
auth_type = userpass
(username and password omitted)
realm = sip.who.com

[who-aor2]
type = aor
contact = sip:sip.who.com

That being loaded. With asterisk running:

raspberrypi*CLI> pjsip show aor who-aor2

 Aor:  <Aor..............................................>  <MaxContact>

Contact: <Aor/ContactUri…> <Hash…> <RTT(ms)…>

 Aor:  who-aor2                                             0

Contact: who-aor2/sip:sip.who.com cf882623ee NonQual nan

ParameterName : ParameterValue

authenticate_qualify : false
contact : sip:sip.who.com
default_expiration : 3600
mailboxes :
max_contacts : 0
maximum_expiration : 7200
minimum_expiration : 60
outbound_proxy :
qualify_frequency : 0
qualify_timeout : 3.000000
remove_existing : false
remove_unavailable : false
support_path : false
voicemail_extension :

The above shows the contact correctly as it appears in pjsip.conf

I make a call from extension 14 to sip:someone%40sip.aphoneco.org@sip.en.my-home.com
(sip.en.my-home.com is not on the internet - just the local IP of my test server)

This triggers a pattern match with the following EXTEN match:

exten => _[a-z].,1,Verbose(1,“hard to believe exten=${EXTEN} and domain =${SIPDOMAIN}”)
same = n,Verbose(1, “pjsip_AOR for who-aor2 returns ${PJSIP_AOR(who-aor2,contact)}”)
same = n,Hangup()

which is supposed to do nothing other than print out the value of the contact for who-aor2.
It triggers but not with the expected result.
Below is the output from asterisk -r -ddddvvvv

– Executing [someone@sip.aphoneco.org@Long-Distance:1] Verbose(“PJSIP/14-00000000”, “1,“hard to believe exten=someone@sip.aphoneco.org and domain =sip.en.my-home.com””) in new stack
“hard to believe exten=someone@sip.aphoneco.org and domain =sip.en.my-home.com
– Executing [someone@sip.aphoneco.org@Long-Distance:2] Verbose(“PJSIP/14-00000000”, "1, “pjsip_AOR for who-aor2 contact returns who-aor2@@cf882623eedc97f7d90f6860b85549e4"”) in new stack
“pjsip_AOR for who-aor2 contact returns who-aor2@@cf882623eedc97f7d90f6860b85549e4
– Executing [someone@sip.aphoneco.org@Long-Distance:4] Hangup(“PJSIP/14-00000000”, “”) in new stack
== Spawn extension (Long-Distance, someone@sip.aphoneco.org, 4) exited non-zero on ‘PJSIP/14-00000000’

If I do the same query for maximum_expiration I get pjsip_AOR for who-aor2 maximum_expiration returns 7200""
Which gives a correct entry for maximum_expiration of 7200 but who-aor2@@cf882623eedc97f7d90f6860b85549e4 in place of sip:sip.who.com for the contact.

endpoint 12, which has two contacts shown in the cli query “pjsip show aor 12 contact” as:

Contact:12/sip:12@192.168.222.242:5060;transport=UDP 2ac3651ea1
Contact: 12/sip:someone@sip.aphoneco.org 61ec8bc9af

PJSIP_AOR(12,contact) returns 12;@2ac3651ea193f1ff958ba224f4a6d9cc,12@@61ec8bc9af0090abc1794763c2788405

I noticed that the first 10 hex digits of both of these results are visible on the cli command “pjsip show aor 12 contact”
So why does the request for maximum_expiration work but the request for contact produce lots of hexadecimal preceded by @@ or ;@ ? I am baffled.

What you’re seeing from PJSIP_AOR is the internal ID of the contact. If you pass that to PJSIP_CONTACT you can get the URI…

Verbose(${PJSIP_CONTACT(${PJSIP_AOR(who-aor2,contact)},uri)})

If you execute config show help res_pjsip contact from the CLI, you can see all the fields you can retrieve from a contact.

*CLI> config show help res_pjsip contact
contact: [category !~ /.?/]

A way of creating an aliased name to a SIP URI

 Contacts are a way to hide SIP URIs from the dialplan directly. They are also
used to make a group of contactable parties when in use with 'AoR' lists. 

type                      -- Must be of type 'contact'.                                                                                              
uri                       -- SIP URI to contact peer                                                                                                 
expiration_time           -- Time to keep alive a contact                                                                                            
qualify_frequency         -- Interval at which to qualify a contact                                                                                  
qualify_timeout           -- Timeout for qualify                                                                                                     
authenticate_qualify      -- Authenticates a qualify challenge response if needed                                                                    
outbound_proxy            -- Outbound proxy used when sending OPTIONS request                                                                        
path                      -- Stored Path vector for use in Route headers on outgoing requests.                                                       
user_agent                -- User-Agent header from registration.                                                                                    
endpoint                  -- Endpoint name                                                                                                           
reg_server                -- Asterisk Server name                                                                                                    
via_addr                  -- IP-address of the last Via header from registration.                                                                    
via_port                  -- IP-port of the last Via header from registration.                                                                       
call_id                   -- Call-ID header from registration.                                                                                       
prune_on_boot             -- A contact that cannot survive a restart/boot.               

I’ll update the help for PJSIP_AOR to note that requesting contact gets you the internal ID and that you have to pass that to PJSIP_CONTACT to get contact details.

Thank you very much for explaining the “internal reference” problem, I am very grateful for your time.
You are shining a very bright light on my lack of understanding of contexts.
I am fumbling around in the dark using examples without really coming to grips with the underlying concepts - sorry.
The problem I am working on is that some folks have both an internal number (e.g. 12) AND a sip number on another sip account such as SIP:someone@sip.asipserver.org
I am trying to include both in a dial group so that they can pick up the call even when not registered to my sip server.

Please note the following before you change the documentation:
Here are all the “contacts” that I have for this test:

Contact: 11/sip:11@192.168.222.239:5060;user=phone;tran 4bc479aca8 NonQual nan
Contact: 12/SIP:someone@sip.asipserver.org 58bd7d0b86 NonQual nan
Contact: 12/sip:12@192.168.222.242:5060;transport=UDP 2ac3651ea1 NonQual nan
Contact: 14/sip:14@192.168.222.244;transport=udp a70dd240bd NonQual nan
Contact: 15/SIP:someone@sip.asipserver.org 58bd7d0b86 NonQual nan
Contact: 18/sip:192.168.222.23;transport=udp 7cf96d4355 NonQual nan
Contact: 20/sip:12@192.168.222.242:5060 701fc1bb40 NonQual nan
Contact: aphoneco-aor/sip:sip.aphoneco.com cf882623ee NonQual nan
Contact: aphoneco-aor2/sip:sip.aphoneco.com cf882623ee NonQual nan

Now, extension 12 is registered on the local asterisk server. Extension 20 isn’t.
Extension 12, has a line in the [12] AOR context reading: contact = SIP:someone@sip.asipserver.org
i.e. I am treating “contact” as a variable of the [12] context AOR and not a type of its own.
So, when I execute:

same = n,Verbose(1, “pjsip_AOR for 12 contact returns ${PJSIP_AOR(12,contact)}”)
I get:
“pjsip_AOR for 12 contact returns 12;@2ac3651ea193f1ff958ba224f4a6d9cc,12@@58bd7d0b863bc906b445459eef84ef8b
i.e. both contacts. But when I execute:
same = n,Verbose(1, “pjsip_CONTACT for 12 returns ${PJSIP_CONTACT(${PJSIP_AOR(12,contact)},uri)}”)
I get:
[Nov 10 11:13:28] WARNING[11324][C-00000001]: func_pjsip_contact.c:180 pjsip_contact_function_read: Unknown property ‘12@@58bd7d0b863bc906b445459eef84ef8b,uri’ for PJSIP contact
i.e. PJSIP_CONTACT directly operating on the result from PJSIP_AOR will only work for AOR with a single contact.

I would have to split the result fom PJSIP_AOR at the commas and pass them one at a time to PJSIP_CONTACT
I have no experience (yet) of string manipulation in a dialplan.
So, I respectfully suggest that you will have to be careful about the documentation change for PJSIP_AOR

On the other hand I am probably doing this all wrong. What I actually wanted to use was:
same = n,Dial(PJSIP_DIAL_CONTACTS(12))
Dial complains that PJSIP_DIAL_CONTACTS returned an invalid dial string.
i.e. the following code:

same = n,Verbose(1, “pjsip_dial_contacts for 12 contact returns ${PJSIP_DIAL_CONTACTS(12)}”)
yields:
“pjsip_dial_contacts for 12 contact returns PJSIP/12/sip:12@192.168.222.242:5060;transport=UDP&PJSIP/12/SIP:someone@sip.asipserver.org”
which gets rejected by the Dial application because (I think) the syntax for dialing external SIP identifiers is totally different from dialing internal numbers.
I think that the valid syntax for dialing 12 locally and a sip: remotely should be PJSIP/12&PJSIP/aphoneco-endpoint2/sip:someone@sip.asipserver.org
But I am probably wrong. I might report that on a different thread.

I think that your answer shows that I can make an entirely new context of type “contact”.
e.g.

[contactann]
type = contact
uri = SIP:annlastname@sip.somesipserver.org
endpoint = 12

I presume all such type “contact” contexts pointing to an endpoint then become contacts of the endpoint that they point to.
Did I understand correctly?

I am really grateful for your help and the time spent responding to amateurish questions.

Correct, if multiple contacts were returned, you’d have to split them which you can do with the SHIFT dialplan function.

The string that was returned from PJSIP_DIAL_CONTACTS looks correct at first glance so I’m not sure why Dial is complaining. I’d have to see the exact output. PJSIP_DIAL_CONTACTS() is going to be problematic anyway because the same endpoint would be used for both contacts. This wouldn’t work if asipserver.org required authentication or needed an outbound proxy for instance.

No. You can’t define standalone contacts in pjsip.conf.

I think what you’d need to do is iterate over the contacts returned from PJSIP_DIAL_CONTACTS(<endpoint>) with the SHIFT function and create your own dial string. If the contact contained asipserver.org, call STRREPLACE to replace PJSIP/<endpoint>/ with PJSIP/aphoneco-endpoint2/ and append
&<new_contact> otherwise just append &<original_contact>

In the end, your dial string might look like
&PJSIP/12/sip:12@192.168.222.242:5060;transport=UDP&PJSIP/aphoneco-endpoint2/SIP:someone@sip.asipserver.org
Dial() will ignore the leading &

Others may have better ideas.