Commit Graph

34149 Commits

Author SHA1 Message Date
George Joseph
1f52ae3582 app_mixmonitor: Update the documentation concerning the "D" option.
When using the "D" option to output interleaved audio, the file extension
must be ".raw".  That info wasn't being properly rendered in the markdown
and HTML on the documentation site.  The XML was updated to move the
note in the option section to a warning in the description.

Resolves: #1269
2025-07-17 15:07:44 +00:00
Naveen Albert
8ef3e4fb3d sig_analog: Properly handle STP, ST2P, and ST3P for fgccamamf.
Previously, we were only using # (ST) as a terminator, and not handling
A (STP), B (ST2P), or C (ST3P), which erroneously led to it being
treated as part of the dialed number. Parse any of these as the start
digit.

Resolves: #1301
2025-07-15 15:16:06 +00:00
Naveen Albert
5de45323c7 res_musiconhold: Remove options that were deprecated in Asterisk 14.
Commit 9c1f34c7e9 added dedicated options
for random sorting functionality and deprecated older options that
now duplicated these capabilities. Remove these deprecated options.

Resolves: #1296

UpgradeNote: The deprecated random and application=r options have
been removed; use sort=random instead.
2025-07-15 15:12:17 +00:00
Naveen Albert
c12f4a886d res_agi: Remove deprecated DeadAGI application.
DeadAGI was deprecated 7 years ago, in Asterisk 15,
as it duplicates functionality in the AGI app.
This removes the application.

Resolves: #258

UpgradeNote: The DeadAGI application, which was
deprecated in Asterisk 15, has now been removed.
The same functionality is available in the AGI app.
2025-07-15 13:57:39 +00:00
kodokaii
f9219284cb chan_websocket: Reset frame_queue_length to 0 after FLUSH_MEDIA
In the WebSocket channel driver, the FLUSH_MEDIA command clears all frames from
the queue but does not reset the frame_queue_length counter.

As a result, the driver incorrectly thinks the queue is full after flushing,
which prevents new multimedia frames from being sent, especially after multiple
flush commands.

This fix sets frame_queue_length to 0 after flushing, ensuring the queue state
is consistent with its actual content.

Fixes: #1304
2025-07-15 13:46:10 +00:00
George Joseph
0086f4a0b8 channelstorage_cpp_map_name_id: Fix callback returning non-matching channels.
When the callback() API was invoked but no channel passed the test, callback
would return the last channel tested instead of NULL.  It now correctly
returns NULL when no channel matches.

Resolves: #1288
2025-07-10 15:07:21 +00:00
Martin Tomec
9e7788ad21 chan_pjsip.c: Change SSRC after media source change
When the RTP media source changes, such as after a blind transfer, the new source introduces a discontinuous timestamp. According to RFC 3550, Section 5.1, an RTP stream's timestamp for a given SSRC must increment monotonically and linearly.
To comply with the standard and avoid a large timestamp jump on the existing SSRC, a new SSRC is generated for the new media stream.
This change resolves known interoperability issues with certain SBCs (like Sonus/Ribbon) that stop forwarding media when they detect such a timestamp violation. This code uses the existing implementation from chan_sip.

Resolves: #927
2025-07-10 14:49:00 +00:00
Naveen Albert
415daae95f users.conf: Remove deprecated users.conf integration.
users.conf was deprecated in Asterisk 21 and is now being removed
for Asterisk 23, in accordance with the Asterisk deprecation policy.

This consists of:
* Removing integration with app_directory, app_voicemail, chan_dahdi,
  chan_iax2, and AMI.
* users.conf was also partially used for res_phoneprov, and this remaining
  functionality is consolidated to a separate phoneprov_users.conf,
  used only by res_phoneprov.

Resolves: #1292

UpgradeNote: users.conf has been removed and all channel drivers must
be configured using their specific configuration files. The functionality
previously in users.conf for res_phoneprov is now in phoneprov_users.conf.
2025-07-10 14:47:38 +00:00
George Joseph
5963e624e2 Media over Websocket Channel Driver
* Created chan_websocket which can exchange media over both inbound and
outbound websockets which the driver will frame and time.
See http://s.asterisk.net/mow for more information.

* res_http_websocket: Made defines for max message size public and converted
a few nuisance verbose messages to debugs.

* main/channel.c: Changed an obsolete nuisance error to a debug.

* ARI channels: Updated externalMedia to include chan_websocket as a supported
transport.

UserNote: A new channel driver "chan_websocket" is now available. It can
exchange media over both inbound and outbound websockets and will both frame
and re-time the media it receives.
See http://s.asterisk.net/mow for more information.

UserNote: The ARI channels/externalMedia API now includes support for the
WebSocket transport provided by chan_websocket.
2025-07-09 17:42:22 +00:00
George Joseph
d5bd2b3ce9 channelstorage: Rename callbacks that conflict with DEBUG_FD_LEAKS.
DEBUG_FD_LEAKS replaces calls to "open" and "close" with functions that keep
track of file descriptors, even when those calls are actually callbacks
defined in structures like ast_channelstorage_instance->open and don't touch
file descriptors.  This causes compilation failures.  Those callbacks
have been renamed to "open_instance" and "close_instance" respectively.

Resolves: #1287
2025-07-08 15:22:45 +00:00
Stanislav Abramenkov
b158011c4e bundled_pjproject: Avoid deadlock between transport and transaction
Backport patch from upstream
* Avoid deadlock between transport and transaction
https://github.com/pjsip/pjproject/commit/edde06f261ac

Issue described in
https://github.com/pjsip/pjproject/issues/4442
2025-07-03 14:34:13 +00:00
Michal Hajek
4522eb1222 audiohook.c: Improve frame pairing logic to avoid MixMonitor breakage with mixed codecs
This patch adjusts the read/write synchronization logic in audiohook_read_frame_both()
to better handle calls where participants use different codecs or sample sizes
(e.g., alaw vs G.722). The previous hard threshold of 2 * samples caused MixMonitor
recordings to break or stutter when frames were not aligned between both directions.

The new logic uses a more tolerant limit (1.5 * samples), which prevents audio tearing
without causing excessive buffer overruns. This fix specifically addresses issues
with MixMonitor when recording directly on a channel in a bridge using mixed codecs.

Reported-by: Michal Hajek <michal.hajek@daktela.com>

Resolves: #1276
Resolves: #1279
2025-07-02 14:34:42 +00:00
Sean Bright
574ddb9eae channelstorage_makeopts.xml: Remove errant XML character.
Resolves: #1282
2025-07-01 14:02:39 +00:00
mkmer
d5ef09ea0c utils.h: Add rounding to float conversion to int.
Quote from an audio engineer NR9V:
There is a minor issue of a small amount of crossover distortion though as a result of `ast_slinear_saturated_multiply_float()` not rounding the float. This could result in some quiet but potentially audible distortion artifacts in lower volume parts of the signal. If you have for example a sign wave function with a max amplitude of just a few samples, all samples between -1 and 1 will be truncated to zero, resulting in the waveform no longer being a sine wave and in harmonic distortion.

Resolves: #1176
2025-06-27 15:38:36 +00:00
Tinet-mucw
8149554e90 pbx.c: when set flag AST_SOFTHANGUP_ASYNCGOTO, ast_explicit_goto should return -1.
Under certain circumstances the context/extens/prio are set in the ast_async_goto, for example action Redirect.
In the situation that action Redirect is broken by GotoIf this info is changed.
that will causes confusion in dialplan execution.

Resolves: #1273
2025-06-27 15:37:38 +00:00
Sean Bright
fa50490b13 res_musiconhold.c: Ensure we're always locked around music state access. 2025-06-27 14:01:24 +00:00
Sean Bright
0e9e381e0d res_musiconhold.c: Annotate when the channel is locked. 2025-06-27 14:01:24 +00:00
Jaco Kroon
014da68306 res_musiconhold: Appropriately lock channel during start.
This relates to #829

This doesn't sully solve the Ops issue, but it solves the specific crash
there.  Further PRs to follow.

In the specific crash the generator was still under construction when
moh was being stopped, which then proceeded to close the stream whilst
it was still in use.

Signed-off-by: Jaco Kroon <jaco@uls.co.za>
2025-06-27 14:01:24 +00:00
George Joseph
8b8a8c1475 res_stir_shaken.so: Handle X5U certificate chains.
The verification process will now load a full certificate chain retrieved
via the X5U URL instead of loading only the end user cert.

* Renamed crypto_load_cert_from_file() and crypto_load_cert_from_memory()
to crypto_load_cert_chain_from_file() and crypto_load_cert_chain_from_memory()
respectively.

* The two load functions now continue to load certs from the file or memory
PEMs and store them in a separate stack of untrusted certs specific to the
current verification context.

* crypto_is_cert_trusted() now uses the stack of untrusted certs that were
extracted from the PEM in addition to any untrusted certs that were passed
in from the configuration (and any CA certs passed in from the config of
course).

Resolves: #1272

UserNote: The STIR/SHAKEN verification process will now load a full
certificate chain retrieved via the X5U URL instead of loading only
the end user cert.
2025-06-25 13:02:07 +00:00
George Joseph
fe341c2b0f res_stir_shaken: Add "ignore_sip_date_header" config option.
UserNote: A new STIR/SHAKEN verification option "ignore_sip_date_header" has
been added that when set to true, will cause the verification process to
not consider a missing or invalid SIP "Date" header to be a failure.  This
will make the IAT the sole "truth" for Date in the verification process.
The option can be set in the "verification" and "profile" sections of
stir_shaken.conf.

Also fixed a bug in the port match logic.

Resolves: #1251
Resolves: #1271
2025-06-18 15:26:47 +00:00
Naveen Albert
2198126231 app_record: Add RECORDING_INFO function.
Add a function that can be used to retrieve info
about a previous recording, such as its duration.

This is being added as a function to avoid possibly
trampling on dialplan variables, and could be extended
to provide other information in the future.

Resolves: #548

UserNote: The RECORDING_INFO function can now be used
to retrieve the duration of a recording.
2025-06-17 14:23:23 +00:00
Itzanh
dbe7c0c5b3 app_sms.c: Fix sending and receiving SMS messages in protocol 2
This fixes bugs in SMS messaging to SMS-capable analog phones that prevented app_sms.c from talking to phones using SMS protocol 2.

- Fix MORX message reception (from phone to Asterisk) in SMS protocol 2
- Fix MTTX message transmission (from Asterisk to phone) in SMS protocol 2

One of the bugs caused messages to have random characters and junk appended at the end up to the character limit. Another bug prevented Asterisk from sending messages from Asterisk to the phone at all. A final bug caused the transmission from Asterisk to the phone to take a long time because app_sms.c did not hang up after correctly sending the message, causing the phone to have to time out and hang up in order to complete the message transmission.

This was tested with a Linksys PAP2T and with a GrandStream HT814, sending and receiving messages with Telefónica DOMO Mensajes phones from Telefónica Spain. I had to play with both the network jitter buffer and the dB gain to get it to work. One of my phones required the gain to be set to +3dB for it to work, while another required it to be set to +6dB.

Only MORX and MTTX were tested, I did not test sending and receiving messages to a TelCo SMSC.
2025-06-12 12:18:56 +00:00
phoneben
c753fe44d1 app_queue: queue rules – Add support for QUEUE_RAISE_PENALTY=rN to raise penalties only for members within min/max range
This update adds support for a new QUEUE_RAISE_PENALTY format: rN

When QUEUE_RAISE_PENALTY is set to rN (e.g., r4), only members whose current penalty
is greater than or equal to the defined min_penalty and less than or equal to max_penalty
will have their penalty raised to N.

Members with penalties outside the min/max range remain unchanged.

Example behaviors:

QUEUE_RAISE_PENALTY=4     → Raise all members with penalty < 4 (existing behavior)
QUEUE_RAISE_PENALTY=r4    → Raise only members with penalty in [min_penalty, max_penalty] to 4

Implementation details:

Adds parsing logic to detect the r prefix and sets the raise_respect_min flag

Modifies the raise logic to skip members outside the defined penalty range when the flag is active

UserNote: This change introduces QUEUE_RAISE_PENALTY=rN, allowing selective penalty raises
only for members whose current penalty is within the [min_penalty, max_penalty] range.
Members with lower or higher penalties are unaffected.
This behavior is backward-compatible with existing queue rule configurations.
2025-06-11 16:23:51 +00:00
George Joseph
a14d585127 res_websocket_client: Add more info to the XML documentation.
Added "see-also" links to chan_websocket and ARI Outbound WebSocket and
added an example configuration for each.
2025-06-11 16:15:43 +00:00
Jaco Kroon
0c68306b7c res_odbc: cache_size option to limit the cached connections.
Signed-off-by: Jaco Kroon <jaco@uls.co.za>

UserNote: New cache_size option for res_odbc to on a per class basis limit the
number of cached connections. Please reference the sample configuration
for details.
2025-06-11 13:00:44 +00:00
Jaco Kroon
497eba4901 res_odbc: cache_type option for res_odbc.
This enables setting cache_type classes to a round-robin queueing system
rather than the historic stack mechanism.

This should result in lower risk of connection drops due to shorter idle
times (the first connection to go onto the stack could in theory never
be used again, ever, but sit there consuming resources, there could be
multiple of these).

And with a queue rather than a stack, dead connections are guaranteed to
be detected and purged eventually.

This should end up better balancing connection_cnt with actual load
over time, assuming the database doesn't keep connections open
excessively long from it's side.

Signed-off-by: Jaco Kroon <jaco@uls.co.za>

UserNote: When using res_odbc it should be noted that back-end
connections to the underlying database can now be configured to re-use
the cached connections in a round-robin manner rather than repeatedly
re-using the same connection.  This helps to keep connections alive, and
to purge dead connections from the system, thus more dynamically
adjusting to actual load.  The downside is that one could keep too many
connections active for a longer time resulting in resource also begin
consumed on the database side.
2025-06-11 13:00:44 +00:00
Sean Bright
c7a82711f6 res_pjsip: Fix empty ActiveChannels property in AMI responses.
The logic appears to have been reversed since it was introduced in
05cbf8df.

Resolves: #1254
2025-06-03 12:55:32 +00:00
George Joseph
c873f2ae7e ARI Outbound Websockets
Asterisk can now establish websocket sessions _to_ your ARI applications
as well as accepting websocket sessions _from_ them.
Full details: http://s.asterisk.net/ari-outbound-ws

Code change summary:
* Added an ast_vector_string_join() function,
* Added ApplicationRegistered and ApplicationUnregistered ARI events.
* Converted res/ari/config.c to use sorcery to process ari.conf.
* Added the "outbound-websocket" ARI config object.
* Refactored res/ari/ari_websockets.c to handle outbound websockets.
* Refactored res/ari/cli.c for the sorcery changeover.
* Updated res/res_stasis.c for the sorcery changeover.
* Updated apps/app_stasis.c to allow initiating per-call outbound websockets.
* Added CLI commands to manage ARI websockets.
* Added the new "outbound-websocket" object to ari.conf.sample.
* Moved the ARI XML documentation out of res_ari.c into res/ari/ari_doc.xml

UserNote: Asterisk can now establish websocket sessions _to_ your ARI applications
as well as accepting websocket sessions _from_ them.
Full details: http://s.asterisk.net/ari-outbound-ws
2025-06-02 16:35:34 +00:00
George Joseph
5a3164c0b2 res_websocket_client: Create common utilities for websocket clients.
Since multiple Asterisk capabilities now need to create websocket clients
it makes sense to create a common set of utilities rather than making
each of those capabilities implement their own.

* A new configuration file "websocket_client.conf" is used to store common
client parameters in named configuration sections.
* APIs are provided to list and retrieve ast_websocket_client objects created
from the named configurations.
* An API is provided that accepts an ast_websocket_client object, connects
to the remote server with retries and returns an ast_websocket object. TLS is
supported as is basic authentication.
* An observer can be registered to receive notification of loaded or reloaded
client objects.
* An API is provided to compare an existing client object to one just
reloaded and return the fields that were changed. The caller can then decide
what action to take based on which fields changed.

Also as part of thie commit, several sorcery convenience macros were created
to make registering common object fields easier.

UserNote: A new module "res_websocket_client" and config file
"websocket_client.conf" have been added to support several upcoming new
capabilities that need common websocket client configuration.
2025-06-02 15:15:15 +00:00
George Joseph
3a5ffe2842 asterisk.c: Add option to restrict shell access from remote consoles.
UserNote: A new asterisk.conf option 'disable_remote_console_shell' has
been added that, when set, will prevent remote consoles from executing
shell commands using the '!' prefix.

Resolves: #GHSA-c7p6-7mvq-8jq2
2025-05-22 14:39:18 +00:00
George Joseph
67360eb671 res_pjsip_messaging.c: Mask control characters in received From display name
Incoming SIP MESSAGEs will now have their From header's display name
sanitized by replacing any characters < 32 (space) with a space.

Resolves: #GHSA-2grh-7mhv-fcfw
2025-05-22 14:24:35 +00:00
mkmer
abf3f78c81 frame.c: validate frame data length is less than samples when adjusting volume
Resolves: #1230
2025-05-20 13:54:08 +00:00
Sven Kube
3924a828ff res_audiosocket.c: Add retry mechanism for reading data from AudioSocket
The added retry mechanism addresses an issue that arises when fragmented TCP
packets are received, each containing only a portion of an AudioSocket packet.
This situation can occur if the external service sending the AudioSocket data
has Nagle's algorithm enabled.
2025-05-20 13:23:01 +00:00
Sven Kube
c4db87ae53 res_audiosocket.c: Set the TCP_NODELAY socket option
Disable Nagle's algorithm by setting the TCP_NODELAY socket option.
This reduces latency by preventing delays caused by packet buffering.
2025-05-20 13:04:51 +00:00
Thomas B. Clark
65db98af89 menuselect: Fix GTK menu callbacks for Fedora 42 compatibility
This patch resolves a build failure in `menuselect_gtk.c` when running
`make menuconfig` on Fedora 42. The new version of GTK introduced stricter
type checking for callback signatures.

Changes include:
- Add wrapper functions to match the expected `void (*)(void)` signature.
- Update `menu_items` array to use these wrappers.

Fixes: #1243
2025-05-19 13:17:27 +00:00
Stanislav Abramenkov
8d7c91bc4c jansson: Upgrade version to jansson 2.14.1
UpgradeNote: jansson has been upgraded to 2.14.1. For more
information visit jansson Github page: https://github.com/akheron/jansson/releases/tag/v2.14.1

Resolves: #1178
2025-05-16 16:22:50 +00:00
Joe Searle
6c234df7e7 pjproject: Increase maximum SDP formats and attribute limits
Since Chrome 136, using Windows, when initiating a video call the INVITE SDP exceeds the maximum number of allowed attributes, resulting in the INVITE being rejected. This increases the attribute limit and the number of formats allowed when using bundled pjproject.

Fixes: #1240
2025-05-15 15:07:38 +00:00
Nathan Monfils
7805f2892a manager.c: Invalid ref-counting when purging events
We have a use-case where we generate a *lot* of events on the AMI, and
then when doing `manager show eventq` we would see some events which
would linger for hours or days in there. Obviously something was leaking.
Testing allowed us to track down this logic bug in the ref-counting on
the event purge.

Reproducing the bug was not super trivial, we managed to do it in a
production-like load testing environment with multiple AMI consumers.

The race condition itself:

1. something allocates and links `session`
2. `purge_sessions` iterates over that `session` (takes ref)
3. `purge_session` correctly de-referencess that session
4. `purge_session` re-evaluates the while() loop, taking a reference
5. `purge_session` exits (`n_max > 0` is false)
6. whatever allocated the `session` deallocates it, but a reference is
   now lost since we exited the `while` loop before de-referencing.
7. since the destructor is never called, the session->last_ev->usecount
   is never decremented, leading to events lingering in the queue

The impact of this bug does not seem major. The events are small and do
not seem, from our testing, to be causing meaningful additional CPU
usage. Mainly we wanted to fix this issue because we are internally
adding prometheus metrics to the eventq and those leaked events were
causing the metrics to show garbage data.
2025-05-13 16:52:14 +00:00
Mike Bradeen
67ab64f773 res_pjsip_nat.c: Do not overwrite transfer host
When a call is transfered via dialplan behind a NAT, the
host portion of the Contact header in the 302 will no longer
be over-written with the external NAT IP and will retain the
hostname.

Fixes: #1141
2025-05-13 16:48:07 +00:00
Mike Bradeen
579cd0b8bb chan_pjsip: Serialize INVITE creation on DTMF attended transfer
When a call is transfered via DTMF feature code, the Transfer Target and
Transferer are bridged immediately.  This opens the possibilty of a race
condition between the creation of an INVITE and the bridge induced colp
update that can result in the set caller ID being over-written with the
transferer's default info.

Fixes: #1234
2025-05-13 12:52:15 +00:00
George Joseph
8f1982c4d6 Alternate Channel Storage Backends
Full details: http://s.asterisk.net/dc679ec3

The previous proof-of-concept showed that the cpp_map_name_id alternate
storage backed performed better than all the others so this final PR
adds only that option.  You still need to enable it in menuselect under
the "Alternate Channel Storage Backends" category.

To select which one is used at runtime, set the "channel_storage_backend"
option in asterisk.conf to one of the values described in
asterisk.conf.sample.  The default remains "ao2_legacy".

UpgradeNote: With this release, you can now select an alternate channel
storage backend based on C++ Maps.  Using the new backend may increase
performance and reduce the chances of deadlocks on heavily loaded systems.
For more information, see http://s.asterisk.net/dc679ec3
2025-05-07 16:47:06 +00:00
Naveen Albert
2ced79259a sig_analog: Add Call Waiting Deluxe support.
Adds support for Call Waiting Deluxe options to enhance
the current call waiting feature.

As part of this change, a mechanism is also added that
allows a channel driver to queue an audio file for Dial()
to play, which is necessary for the announcement function.

ASTERISK-30373 #close

Resolves: #271

UserNote: Call Waiting Deluxe can now be enabled for FXS channels
by enabling its corresponding option.
2025-05-05 14:10:17 +00:00
Naveen Albert
89f7d5a471 app_sms: Ignore false positive vectorization warning.
Ignore gcc warning about writing 32 bytes into a region of size 6,
since we check that we don't go out of bounds for each byte.
This is due to a vectorization bug in gcc 15, stemming from
gcc commit 68326d5d1a593dc0bf098c03aac25916168bc5a9.

Resolves: #1088
2025-05-05 13:44:22 +00:00
George Joseph
6fa0e264df lock.h: Add include for string.h when DEBUG_THREADS is defined.
When DEBUG_THREADS is defined, lock.h uses strerror(), which is defined
in the libc string.h file, to print warning messages. If the including
source file doesn't include string.h then strerror() won't be found and
and compile errors will be thrown. Since lock.h depends on this, string.h
is now included from there if DEBUG_THREADS is defined.  This way, including
source files don't have to worry about it.
2025-05-03 16:22:25 +00:00
Naveen Albert
754dea319e res_pjsip_caller_id: Also parse URI parameters for ANI2.
If the isup-oli was sent as a URI parameter, rather than a header
parameter, it was not being parsed. Make sure we parse both if
needed so the ANI2 is set regardless of which type of parameter
the isup-oli is sent as.

Resolves: #1220
2025-04-30 12:47:39 +00:00
Naveen Albert
923e6d471a func_callerid: Always format ANI2 as two digits.
ANI II is always supposed to be formatted as two digits,
so zero pad when formatting it if necessary.

Resolves: #1222
2025-04-29 12:13:48 +00:00
Naveen Albert
f6ceeff523 app_meetme: Remove inaccurate removal version from xmldocs.
app_meetme is deprecated but wasn't removed as planned in 21,
so remove the inaccurate removal version.

Resolves: #1224
2025-04-28 19:13:58 +00:00
Luz Paz
9ca3233aea docs: Fix typos in apps/
Found via codespell
2025-04-28 16:30:09 +00:00
Mike Bradeen
8924258639 stasis/control.c: Set Hangup Cause to No Answer on Dial timeout
Other Dial operations (dial, app_dial) use Q.850 cause 19 when a dial timeout occurs,
but the Dial command via ARI did not set an explicit reason. This resulted in a
CANCEL with Normal Call Clearing and corresponding ChannelDestroyed.

This change sets the hangup cause to AST_CAUSE_NO_ANSWER to be consistent with the
other operations.

Fixes: #963

UserNote:  A Dial timeout on POST /channels/{channelId}/dial will now result in a
CANCEL and ChannelDestroyed with cause 19 / User alerting, no answer.  Previously
no explicit cause was set, resulting in a cause of 16 / Normal Call Clearing.
2025-04-22 16:57:51 +00:00
Naveen Albert
d60bcc56a7 chan_iax2: Minor improvements to documentation and warning messages.
* Update Dial() documentation for IAX2 to include syntax for RSA
  public key names.
* Add additional details to a couple warnings to provide more context
  when an undecodable frame is received.

Resolves: #1206
2025-04-21 14:48:25 +00:00