Age | Commit message (Collapse) | Author |
|
Updated the AMI version for the following reason (see CHANGES for more details):
The 'PJSIPShowEndpoint' command's response event of 'IdentifyDetail' now
contains a new optional parameter, 'MatchHeader'.
Change-Id: I9aeac4decc89f9b464b3f026e97c7ef1acc79242
|
|
|
|
Support for RFC3578 overlap dialling (i.e. 484 Response to partially matched
destinations) as currently provided by chan_sip is missing from res_pjsip.
This patch adds a new endpoint attribute (allow_overlap) [defaults to yes]
which when set to yes enables 484 responses to partial destination
matches rather than the current 404.
ASTERISK-26864
Change-Id: Iea444da3ee7c7d4f1fde1d01d138a3d7b0fe40f6
|
|
references." into 13
|
|
Rather than hard-coding UDP, allow consumers of the HEP API to specify
which protocol is in use. Update the PJSIP provider to pass in the
current protocol type.
ASTERISK-26850 #close
Change-Id: I54bbb0a001cfe4c6a87ad4b6f2014af233349978
|
|
POSIX does not require getprotobyname() to be thread safe and some
implementations use static memory which causes issues when multiple
threads are used.
Further, our usage of it today is just to ultimately get IPPROTO_TCP
for calls to setsockopt(). So instead we just use IPPROTO_TCP directly.
Change-Id: I2e14e58674808f7ce99b2f5e900d0f90d0d8da48
|
|
A new transport parameter 'symmetric_transport' has been added.
When a request from a dynamic contact comes in on a transport with
this option set to 'yes', the transport name will be saved and used
for subsequent outgoing requests like OPTIONS, NOTIFY and INVITE.
It's saved as a contact uri parameter named 'x-ast-txp' and will
display with the contact uri in CLI, AMI, and ARI output. On the
outgoing request, if a transport wasn't explicitly set on the
endpoint AND the request URI is not a hostname, the saved transport
will be used and the 'x-ast-txp' parameter stripped from the
outgoing packet.
* config_transport was modified to accept and store the new parameter.
* config_transport/transport_apply was updated to store the transport
name in the pjsip_transport->info field using the pjsip_transport->pool
on UDP transports.
* A 'multihomed_on_rx_message' function was added to
pjsip_message_ip_updater that, for incoming requests, retrieves the
transport name from pjsip_transport->info and retrieves the transport.
If transport->symmetric_transport is set, an 'x-ast-txp' uri parameter
containing the transport name is added to the incoming Contact header.
* An 'ast_sip_get_transport_name' function was added to res_pjsip.
It takes an ast_sip_endpoint and a pjsip_sip_uri and returns a
transport name if endpoint->transport is set or if there's an
'x-ast-txp' parameter on the uri and the uri host is an ipv4 or
ipv6 address. Otherwise it returns NULL.
* An 'ast_sip_dlg_set_transport' function was added to res_pjsip
which takes an ast_sip_endpoint, a pjsip_dialog, and an optional
pjsip_tpselector. It calls ast_sip_get_transport_name() and if
a non-NULL is returned, sets the selector and sets the transport
on the dialog. If a selector was passed in, it's updated.
* res_pjsip/ast_sip_create_dialog_uac and ast_sip_create_dialog_uas
were modified to call ast_sip_dlg_set_transport() instead of their
original logic.
* res_pjsip/create_out_of_dialog_request was modified to call
ast_sip_get_transport_name() and pjsip_tx_data_set_transport()
instead of its original logic.
* Existing transport logic was removed from endpt_send_request
since that can only be called after a create_out_of_dialog_request.
* res_pjsip/ast_sip_create_rdata was converted to a wrapper around
a new 'ast_sip_create_rdata_with_contact' function which allows
a contact_uri to be specified in addition to the existing
parameters. (See below)
* res_pjsip_pubsub/internal_pjsip_evsub_send_request was eliminated
since all it did was transport selection and that is now done in
ast_sip_create_dialog_uac and ast_sip_create_dialog_uas.
* 'contact_uri' was added to subscription_persistence. This was
necessary because although the parsed rdata contact header has the
x-ast-txp parameter added (if appropriate),
subscription_persistence_update stores the raw packet which
doesn't have it. subscription_persistence_recreate was then
updated to call ast_sip_create_rdata_with_contact with the
persisted contact_uri so the recreated subscription has the
correct transport info to send the NOTIFYs.
* res_pjsip_session/internal_pjsip_inv_send_msg was eliminated since
all it did was transport selection and that is now done in
ast_sip_create_dialog_uac.
* pjsip_message_ip_updater/multihomed_on_tx_message was updated
to remove all traces of the x-ast-txp parameter from the
outgoing headers.
NOTE: This change does NOT modify the behavior of permanent
contacts specified on an aor. To do so would require that the
permanent contact's contact uri be updated with the x-ast-txp
parameter and the aor sorcery object updated. If we need to
persue this, we need to think about cloning permanent contacts into
the same store as the dynamic ones on an aor load so they can be
updated without disturbing the originally configured value.
You CAN add the x-ast-txp parameter to a permanent contact's uri
but it would be much simpler to just set endpoint->transport.
Change-Id: I4ee1f51473da32ca54b877cd158523efcef9655f
|
|
Dereferencing struct ast_autochan.chan without first calling
ast_autochan_channel_lock() is unsafe because the pointer could change at
any time due to a masquerade. Unfortunately, ast_autochan_channel_lock()
itself uses struct ast_autochan.chan unsafely and can result in a deadlock
if the original channel happens to get destroyed after a masquerade in
addition to the pointer getting changed.
The problem is more likely to happen with v11 and earlier because
masquerades are used to optimize out local channels on those versions.
However, it could still happen on newer versions if the channel is
executing a dialplan application when the channel is transferred or
redirected. In this situation a masquerade still must be used.
* Added a lock to struct ast_autochan to safely be able to use
ast_autochan.chan while trying to get the channel lock in
ast_autochan_channel_lock(). The locking order is the channel lock then
the autochan lock. Locking in the other direction requires deadlock
avoidance.
* Fix unsafe ast_autochan.chan usages in app_mixmonitor.c.
* Fix unsafe ast_autochan.chan usages in app_chanspy.c.
* app_chanspy.c: Removed unused autochan parameter from next_channel().
ASTERISK-26867
Change-Id: Id29dd22bc0f369b44e23ca423d2f3657187cc592
|
|
This commit adds support for RFC 5761: Multiplexing RTP Data and Control
Packets on a Single Port. Specifically, it enables the feature when
using chan_pjsip.
A new option, "rtcp_mux" has been added to endpoint configuration in
pjsip.conf. If set, then Asterisk will attempt to use rtcp-mux with
whatever it communicates with. Asterisk follows the rules set forth in
RFC 5761 with regards to falling back to standard RTCP behavior if the
far end does not indicate support for rtcp-mux.
The lion's share of the changes in this commit are in
res_rtp_asterisk.c. This is because it was pretty much hard wired to
have an RTP and an RTCP transport. The strategy used here is that when
rtcp-mux is enabled, the current RTCP transport and its trappings (such
as DTLS SSL session) are freed, and the RTCP session instead just
mooches off the RTP session. This leads to a lot of specialized if
statements throughout.
ASTERISK-26732 #close
Reported by Dan Jenkins
Change-Id: If46a93ba1282418d2803e3fd7869374da8b77ab5
|
|
The bridge_native_rtp module did not properly handle the case where
a smart bridge operation occurs while a channel is suspended. In this
scenario the module would incorrectly set up local or remote RTP
bridging despite the media having to flow through Asterisk. The remote
endpoint would see two media streams and experience wonky audio.
The module has been changed so that it ensures both channels are
not suspended when performing the native RTP bridging and this
requirement has been documented in the bridge technology.
ASTERISK-26781
Change-Id: Id4022d73ace837d4a293106445e3ade10dbc7c7c
|
|
|
|
All of the realtime backends create artificial ast_categorys to pass
back into the core as query results. These categories have no filename
or line number information associated with them and the backends differ
slightly on how they create them. So create a couple helper macros to
help make things more consistent.
Also updated the call sites to remove redundant error messages about
memory allocation failure.
Note that res_config_ldap sets the category filename to the 'table name'
but that is not read by anything in the core, so I've dropped it.
Change-Id: I3a1fd91e0c807dea1ce3b643b0a6fe5be9002897
|
|
When PJPROJECT needs to do a DNS resolution and there is not a cached
entry available, the SIP request message goes out on the PJSIP monitor
thread instead of the original serializer thread. Thus when the response
comes back it does not get processed by the original sending serializer.
This patch records the serializer on tdata before passing a request
message to PJPROJECT where it can in Asterisk code. There are several
places in PJPROJECT for outbound registration and publishing support that
would need to record the serializer. Unfortunately, without replacing the
PJPROJECT DNS resolver as was done in v14 we cannot fix those without
modifying PJPROJECT.
Even if we backported the DNS resolver from v14, the outbound registration
refresh timer does not go out on a serializer thread but the PJSIP monitor
thread. Fortunately, Asterisk's outbound publish support doesn't use the
auto refresh timer that would also not go out under the serializer thread.
This patch is v13 only.
ASTERISK-26669
ASTERISK-26738
Change-Id: I9997b9ed6dbcebd2c37d6a67dc6dcee9c78914a4
|
|
This patch fixes 2 original issues and more that those 2 exposed.
* When we send a NOTIFY, and the client either doesn't respond or
responds with a non OK, pjproject only calls our
pubsub_on_evsub_state callback, no others. Since
pubsub_on_evsub_state (which does the sub_tree cleanup) does not
expect to be called back without the other callbacks being called
first, it just returns leaving the sub_tree orphaned. Now
pubsub_on_evsub_state checks the event for PJSIP_EVENT_TSX_STATE
which is what pjproject will set to tell us that it was the
transaction that timed out or failed and not the subscription
itself timing our or being terminated by the client. If is
TSX_STATE, pubsub_on_evsub_state now does the proper cleanup
regardless of the state of the subscription.
* When a client renews a subscription, we don't update the
persisted subscription with the new expires timestamp. This causes
subscription_persistence_recreate to prune the subscription if/when
asterisk restarts. Now, pubsub_on_rx_refresh calls
subscription_persistence_update to apply the new expires timestamp.
This exposed other issues however...
* When creating a dialog from rdata (which sub_persistence_recreate
does from the packet buffer) there must NOT be a tag on the To
header (which there will be when a client refreshes a
subscription). If there is one, pjsip_dlg_create_uas will fail.
To address this, subscription_persistence_update now accepts a flag
that indicates that the original packet buffer must not be updated.
New subscribes don't set the flag and renews do. This makes sure
that when the rdata is recreated on asterisk startup, it's done
from the original subscribe packet which won't have the tag on To.
* When creating a dialog from rdata, we were setting the dialog's
remote (SUBSCRIBE) cseq to be the same as the local (NOTIFY) cseq.
When the client tried to resubscribe after a restart with the
correct cseq, we'd reject the request with an Invalid CSeq error.
* The acts of creating a dialog and evsub by themselves when
recreating a subscription does NOT restart pjproject's subscription
timer. The result was that even if we did correctly recreate the
subscription, we never removed it if the client happened to go away
or send a non-OK response to a NOTIFY. However, there is no
pjproject function exposed to just set the timer on an evsub that
wasn't created by an incoming subscribe request. To address this,
we create our own timer using ast_sip_schedule_task. This timer is
used only for re-establishing subscriptions after a restart.
An earlier approach was to add support for setting pjproject's
timer (via a pjproject patch) and while that patch is still included
here, we don't use that call at the moment.
While addressing these issues, additional debugging was added and
some existing messages made more useful. A few formatting changes
were also made to 'pjsip show scheduled tasks' to make displaying
the subscription timers a little more friendly.
ASTERISK-26696
ASTERISK-26756
Change-Id: I8c605fc1e3923f466a74db087d5ab6f90abce68e
|
|
In Asterisk 11, if the 'Originate' AMI command failed to connect the provided
Channel while in extension mode, a 'failed' extension would be looked up and
run. This was, I believe, unintentionally removed in 51b6c49. This patch
restores that behavior.
This also adds an enum for the various 'synchronous' modes in an attempt to
make them meaningful.
ASTERISK-26115 #close
Reported by: Nasir Iqbal
Change-Id: I8afbd06725e99610e02adb529137d4800c05345d
|
|
A dialplan intercept routine is equivalent to an interrupt routine. As
such, the routine must be done quickly and you do not have access to the
media stream. These restrictions are necessary because the media stream
is the responsibility of some other code and interfering with or delaying
that processing is bad. A possible future dialplan processing
architecture change may allow the interception routine to run in a
different thread from the main thread handling the media and remove the
execution time restriction.
* Made res_agi.c:run_agi() running an AGI in an interception routine run
in DeadAGI mode. No touchy channel frames.
ASTERISK-25951
ASTERISK-26343
ASTERISK-26716
Change-Id: I638f147ca7a7f2590d7194a8ef4090eb191e4e43
|
|
There are several issues with deferring frames that are caused by the
refactoring.
1) The code deferring frames mishandles adding a deferred frame to the
deferred queue. As a result the deferred queue can only be one frame
long.
2) Deferrable frames can come directly from the channel driver as well as
the read queue. These frames need to be added to the deferred queue.
3) Whoever is deferring frames is really only doing the __ast_read() to
collect deferred frames and doesn't care about the returned frames except
to detect a hangup event. When frame deferral is completed we must make
the normal frame processing see the hangup as a frame anyway. As such,
there is no need to have varying hangup frame deferral methods. We also
need to be aware of the AST_SOFTHANGUP_ASYNCGOTO hangup that isn't real.
That fake hangup is to cause the PBX thread to break out of loops to go
execute a new dialplan location.
4) To properly deal with deferrable frames from the channel driver as
pointed out by (2) above, means that it is possible to process a dialplan
interception routine while frames are deferred because of the
AST_CONTROL_READ_ACTION control frame. Deferring frames is not
implemented as a re-entrant operation so you could have the unsupported
case of two sections of code thinking they have control of the media
stream.
A worse problem is because of the bad implementation of the AMI PlayDTMF
action. It can cause two threads to be deferring frames on the same
channel at the same time. (ASTERISK_25940)
* Rather than fix all these problems simply revert the API refactoring as
there is going to be only autoservice and safe_sleep deferring frames
anyway.
ASTERISK-26343
ASTERISK-26716 #close
Change-Id: I45069c779aa3a35b6c863f65245a6df2c7865496
|
|
|
|
|
|
mod_format.h: Note ast_filestream.fr holds a format ref.
translate.h: Note ast_trans_pvt.f holds a format ref.
Change-Id: I86bda354d725207b41e08920355d7c31b2d7f749
|
|
The 'ari set debug' command has been enhanced to accept 'all' as an
application name. This allows dumping of all apps even if an app
hasn't registered yet. To accomplish this, a new global_debug global
variable was added to res/stasis/app.c and new APIs were added to
set and query the value.
'ari set debug' now displays requests and responses as well as events.
This required refactoring the existing debug code.
* The implementation for 'ari set debug' was moved from stasis/cli.{c,h}
to ari/cli.{c,h}, and stasis/cli.{c,h} were deleted.
* In order to print the body of incoming requests even if a request
failed, the consumption of the body was moved from the ari stubs
to ast_ari_callback in res_ari.c and the moustache templates were
then regenerated. The body is now passed to ast_ari_invoke and then
on to the handlers. This results in code savings since that template
was inserted multiple times into all the stubs.
An additional change was made to the ao2_str_container implementation
to add partial key searching and a sort function. The existing cli
code assumed it was already there when it wasn't so the tab completion
was never working.
Change-Id: Ief936f747ce47f1fb14035fbe61152cf766406bf
|
|
The mechanism used for detecting the maximum log level compiled into the
linked pjproject did not work. The API call simply stores the requested
level into an integer and does no range checking. Asterisk was assuming
that there was range checking and limited the new value to the allowable
range. To get the actual maximum log level compiled into the linked
pjproject we need to get and save off the initial set log level from
pjproject. This is the maximum log level supported.
* Get and save off the initial log level setting before altering it to the
desired level on startup. This has to be done by a macro rather than
calling a core function to avoid incorrectly linking pjproject.
* Split the initial log level warning messages to warn if the linked
pjproject cannot support the requested startup level and if it is too low
to get the pjproject buildopts for "pjproject show buildopts".
* Adjust the CLI "pjproject set log level" to check the saved max log
level and to generate normal output messages instead of a warning message.
ASTERISK-26743 #close
Change-Id: I40aa76653e2a1dece66c3f8734594b4f0471cfb4
|
|
It was possible for a frame to be re-inserted into a jitter buffer after it
had been removed from it. A case when this happened was if a frame was read
out of the jitterbuffer, passed to the translation core, and then multiple
frames were returned from said translation core. Upon multiple frames being
returned the first is passed on, but sebsequently "chained" frames are put
back into the read queue. Thus it was possible for a frame to go back into
the jitter buffer where this would cause problems.
This patch adds a flag to frames that are inserted into the channel's read
queue after translation. The abstract jitter buffer code then checks for this
flag and ignores any frames marked as such.
Change-Id: I276c44edc9dcff61e606242f71274265c7779587
|
|
Function CHANNEL(rtcp,all_rtt) CHANNEL(rtcp,all_loss) CHANNEL(rtcp,all_jitter)
always return 0.0 due to wrong define of macro "AST_RTP_SATA_SET" and
"AST_RTP_STAT_STRCPY".
It should compare "combined" with "stat" not "current_stat".
ASTERISK-26710 #close
Reported-by: Aaron An
Tested-by: AaronAn
Change-Id: Id4140fafbf92e2db689dac5b17d9caa009028a15
|
|
ASTERISK-25083
Change-Id: Id54baa57a8dbca84e29f28bcd2ffc0a5ac12d8b2
|
|
The PJSIPShowRegistrationsInbound AMI command was just dumping out
all AORs which was pretty useless and resource heavy since it had
to get all endpoints, then all aors for each endpoint, then all
contacts for each aor.
PJSIPShowRegistrationInboundContactStatuses sends ContactStatusDetail
events which meets the intended purpose of the other command and has
significantly less overhead. Also, some additional fields that were
added to Contact since the original creation of the ContactStatusDetail
event have been added to the end of the event.
For compatibility purposes, PJSIPShowRegistrationsInbound is left
intact.
ASTERISK-26644 #close
Change-Id: I326f12c9ecb52bf37ba03f0748749de4da01490a
|
|
|
|
|
|
The latest Release candidate fails to create RTP streams when IPv6
is not available. Due to the changes made in September the ast_sockaddr
structure passed around to create these streams is always of AF_INET6
type, causing failure when used for IPv4. This patch adds a utility
function to check for availability of IPv6 and applies such check
at startup to determine how to create the ast_sockaddr structures.
ASTERISK-26617 #close
Change-Id: I627a4e91795e821111e1cda523f083a40d0e0c3e
|
|
Use of the new logging is as simple as issuing the new CLI command or
setting the new pjproject.conf option.
Other options that can affect the logging are how you have the pjproject
log levels mapped to Asterisk log types in pjproject.conf and if you have
configured Asterisk to log the DEBUG type messages. Altering the
pjproject.conf level mapping shouldn't be necessary for most installations
as the default mapping is sensible. Configuring Asterisk to log the DEBUG
message type is standard practice for collecting debug information.
* Added CLI "pjproject set log level" command to dynamically adjust the
maximum pjproject log message level.
* Added CLI "pjproject show log level" command to see the currently set
maximum pjproject log message level.
* Added pjproject.conf startup section "log_level" option to set the
initial maximum pjproject log message level so all messages could be
captured from initialization.
* Set PJ_LOG_MAX_LEVEL to 6 to compile in all defined logging levels into
bundled pjproject. Pjproject will use the currently set run time log
level to determine if a log message is generated just like Asterisk
verbose and debug logging levels.
* In log_forwarder(), made always log enabled and mapped pjproject log
messages. DEBUG mapped log messages are no longer gated by the current
Asterisk debug logging level.
* Removed RAII_VAR() from res_pjproject.c:get_log_level().
ASTERISK-26630 #close
Change-Id: I6dca12979f482ffb0450aaf58db0fe0f6d2e5389
|
|
into 13
|
|
Previously, a TLS server socket would only be restarted upon sip reload if the
bind address had changed. This commit adds checking for changes to TLS
parameters like certificate, ciphers, etc. so they get picked up without
requiring a reload of the entire chan_sip module. This does not affect open
connections in any way, but new connections will use the new TLS parameters.
The changes also apply to HTTP and Manager.
ASTERISK-26604 #close
Change-Id: I169e86cefc6dcd627c915134015a6a1ab1aadbe6
|
|
A while back, a master-only change was made to check for librt which
should probably have been cherry-picked to 13 at that time. Sometime
between then and now, part of that change did make it into 13 but it
was incomplete and non-functional. This patch backports the rest
of the librt check and allows the link of libasteriskpj to use the
results.
Change-Id: I1424008fd8c90f389dda53162ec4a340b253a3c1
|
|
Based on bridge video AMI event changes, bump the minor version of AMI.
Change-Id: I02586bd6cafc0baa33ea98c2f75356c0f5e03435
|
|
|
|
ASTERISK-26343
Change-Id: I06dbf7366e26028251964143454a77d017bb61c8
|
|
source" into 13
|
|
|
|
verification." into 13
|
|
Responding to authentication challenges leaks PJSIP memory pools.
The leak was introduced with a pjproject 2.5.5 API change.
https://trac.pjsip.org/repos/ticket/1929 changed the API usage of
pjsip_auth_clt_init() to require the new API pjsip_auth_clt_deinit() to
clean up cached authentication allocations that get allocated with
pjsip_auth_clt_reinit_req().
ASTERISK-26516 #close
Change-Id: I4473141b8c3961d0dc91c382beb3876b3efb45c8
|
|
The sending codec is switched to the receiving codec and then
is switched back to the best native codec on EVERY receiving RTP packets.
This is because after call of ast_channel_set_rawwriteformat there is call
of ast_set_write_format which calls set_format which sets rawwriteformat
to the best native format.
This patch adds a new function ast_set_write_format_path which set
specific write path on channel and uses this function to switch
the sending codec.
ASTERISK-26603 #close
Change-Id: I5b7d098f8b254ce8f45546e6c36e5d324737f71d
|
|
verification.
This way one X.509 certificate can be used for hosts that
can be reached under multiple DNS names or for multiple hosts.
Signed-off-by: Maciej Szmigiero <mail@maciej.szmigiero.name>
ASTERISK-25063 #close
Change-Id: I13302c80490a0b44c43f1b45376c9bd7b15a538f
|
|
In multi-party bridges, Asterisk currently supports two video modes:
* Follow the talker, in which the speaker with the most energy is shown
to all participants but the speaker, and the speaker sees the
previous video source
* Explicitly set video sources, in which all participants see a locked
video source
Prior to this patch, ARI had no ability to manipulate the video source.
This isn't important for two-party bridges, in which Asterisk merely
relays the video between the participants. However, in a multi-party
bridge, it can be advantageous to allow an external application to
manipulate the video source.
This patch provides two new routes to accomplish this:
(1) setVideoSource: POST /bridges/{bridgeId}/videoSource/{channelId}
Sets a video source to an explicit channel
(2) clearVideoSource: DELETE /bridges/{bridgeId}/videoSource
Removes any explicit video source, and sets the video mode to talk
detection
ASTERISK-26595 #close
Change-Id: I98e455d5bffc08ea5e8d6b84ccaf063c714e6621
|
|
This reverts commit 6b5a7ced136b7178ae0b2ba39221eba1cd2e37c9.
Change-Id: I61d1dbb2e69e1977f684b7dfc8e98211024e1cd1
|
|
Libedit 3.1 is not build with unicode on as a default and so the
prototype for the el_gets callback changed from expecting a char buffer
to accepting a wchar buffer. If ast_el_read_char isn't changed,
the cli reads garbage from teh terminal.
Added a configure test for (*el_rfunc_t)(EditLine *, wchar_t *) and
updated ast_el_read_char to use the HAVE_ define to detemrine whether
to use char or wchar.
ASTERISK-26592 #close
Change-Id: I9099b46f68e06d0202ff80e53022a2b68b08871a
|
|
|
|
This reverts commit 9231a56cf3d6f5eca1bf2d37d827453400690773.
Multiple testsuite failures were detected after the fact.
Change-Id: I3bac8d7c3ddb69a4ddf6c5d6de0ffa5ff7ff3af7
|
|
|
|
function." into 13
|
|
There are several places in Asterisk that have duplicated logic
for deferring important frames until later.
This commit adds a couple of API calls to facilitate this automatically.
ast_channel_start_defer_frames(): Future reads of deferrable frames on
this channel will be deferred until later.
ast_channel_stop_defer_frames(): Any frames that have been deferred get
requeued onto the channel.
ASTERISK-26343
Change-Id: I3e1b87bc6796f222442fa6f7d1b6a4706fb33641
|